Source Code

;; Task Escrow Contract
;; Manages STX payments in escrow for tasks

;; Constants
(define-constant CONTRACT-OWNER tx-sender)
(define-constant ERR-NOT-AUTHORIZED (err u200))
(define-constant ERR-ESCROW-NOT-FOUND (err u201))
(define-constant ERR-ALREADY-RELEASED (err u202))
(define-constant ERR-INSUFFICIENT-FUNDS (err u203))
(define-constant ERR-INVALID-AMOUNT (err u204))
(define-constant ERR-DISPUTE-WINDOW-EXPIRED (err u205))
(define-constant ERR-DISPUTE-ALREADY-OPENED (err u206))
(define-constant ERR-INVALID-PERCENTAGE (err u207))
(define-constant ERR-TRANSFER-FAILED (err u208))

;; Platform fee: 2.5% of task reward
(define-constant PLATFORM-FEE-PERCENT u250) ;; 2.5% = 250/10000

;; Minimum and maximum task rewards (in microSTX)
(define-constant MIN-TASK-REWARD u100000) ;; 0.1 STX
(define-constant MAX-TASK-REWARD u100000000000) ;; 100,000 STX

;; Dispute window: 72 hours in blocks (assuming ~10 min blocks)
(define-constant DISPUTE-WINDOW-BLOCKS u432) ;; 72 hours * 6 blocks/hour

;; Data Variables
(define-data-var platform-fees-collected uint u0)
(define-data-var task-counter uint u0)

;; Data Maps
(define-map escrows
    { task-id: uint }
    {
        creator: principal,
        worker: (optional principal),
        amount: uint,
        deposited-at: uint,
        released: bool,
        dispute-opened: bool,
        dispute-opened-at: (optional uint),
        dispute-resolved: bool
    }
)

;; Authorization map for allowed contract callers
(define-map authorized-contracts principal bool)

;; Initialize authorization
(map-set authorized-contracts CONTRACT-OWNER true)

;; Read-only functions

(define-read-only (get-escrow (task-id uint))
    (ok (map-get? escrows { task-id: task-id }))
)

(define-read-only (get-platform-fees)
    (ok (var-get platform-fees-collected))
)

(define-read-only (calculate-platform-fee (amount uint))
    (ok (/ (* amount PLATFORM-FEE-PERCENT) u10000))
)

(define-read-only (calculate-total-deposit (amount uint))
    (let (
        (platform-fee (/ (* amount PLATFORM-FEE-PERCENT) u10000))
    )
    (ok (+ amount platform-fee)))
)

(define-read-only (is-dispute-window-open (task-id uint))
    (match (map-get? escrows { task-id: task-id })
        escrow-data
        (if (get dispute-opened escrow-data)
            (ok true)
            (ok (< (- block-height (get deposited-at escrow-data)) DISPUTE-WINDOW-BLOCKS))
        )
        (ok false)
    )
)

(define-read-only (is-authorized-contract (contract principal))
    (ok (default-to false (map-get? authorized-contracts contract)))
)

;; Private functions

(define-private (validate-amount (amount uint))
    (and (>= amount MIN-TASK-REWARD) (<= amount MAX-TASK-REWARD))
)

;; Public functions

(define-public (authorize-contract (contract principal))
    (begin
        (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
        (ok (map-set authorized-contracts contract true))
    )
)

(define-public (revoke-contract (contract principal))
    (begin
        (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
        (ok (map-delete authorized-contracts contract))
    )
)

(define-public (deposit-escrow (task-id uint) (amount uint) (creator principal))
    (let (
        (platform-fee (/ (* amount PLATFORM-FEE-PERCENT) u10000))
        (total-amount (+ amount platform-fee))
    )
        ;; Only authorized contracts can call this
        (asserts! (default-to false (map-get? authorized-contracts contract-caller)) ERR-NOT-AUTHORIZED)
        
        ;; Validate amount
        (asserts! (validate-amount amount) ERR-INVALID-AMOUNT)
        
        ;; Transfer STX from creator to this contract
        (try! (stx-transfer? total-amount creator (as-contract tx-sender)))
        
        ;; Update platform fees
        (var-set platform-fees-collected (+ (var-get platform-fees-collected) platform-fee))
        
        ;; Create escrow record
        (ok (map-set escrows 
            { task-id: task-id }
            {
                creator: creator,
                worker: none,
                amount: amount,
                deposited-at: block-height,
                released: false,
                dispute-opened: false,
                dispute-opened-at: none,
                dispute-resolved: false
            }
        ))
    )
)

(define-public (set-worker (task-id uint) (worker principal))
    (begin
        ;; Only authorized contracts can call this
        (asserts! (default-to false (map-get? authorized-contracts contract-caller)) ERR-NOT-AUTHORIZED)
        
        (let (
            (escrow-data (unwrap! (map-get? escrows { task-id: task-id }) ERR-ESCROW-NOT-FOUND))
        )
        
        ;; Ensure not already released
        (asserts! (not (get released escrow-data)) ERR-ALREADY-RELEASED)
        
        ;; Update worker
        (ok (map-set escrows 
            { task-id: task-id }
            (merge escrow-data { worker: (some worker) })
        )))
    )
)

(define-public (release-funds (task-id uint))
    (begin
        ;; Only authorized contracts can call this
        (asserts! (default-to false (map-get? authorized-contracts contract-caller)) ERR-NOT-AUTHORIZED)
        
        (let (
            (escrow-data (unwrap! (map-get? escrows { task-id: task-id }) ERR-ESCROW-NOT-FOUND))
            (worker (unwrap! (get worker escrow-data) ERR-NOT-AUTHORIZED))
            (amount (get amount escrow-data))
        )
        
        ;; Ensure not already released
        (asserts! (not (get released escrow-data)) ERR-ALREADY-RELEASED)
        
        ;; Transfer funds to worker
        (try! (as-contract (stx-transfer? amount tx-sender worker)))
        
        ;; Mark as released
        (ok (map-set escrows 
            { task-id: task-id }
            (merge escrow-data { released: true })
        )))
    )
)

(define-public (refund-creator (task-id uint))
    (begin
        ;; Only authorized contracts can call this
        (asserts! (default-to false (map-get? authorized-contracts contract-caller)) ERR-NOT-AUTHORIZED)
        
        (let (
            (escrow-data (unwrap! (map-get? escrows { task-id: task-id }) ERR-ESCROW-NOT-FOUND))
            (creator (get creator escrow-data))
            (amount (get amount escrow-data))
        )
        
        ;; Ensure not already released
        (asserts! (not (get released escrow-data)) ERR-ALREADY-RELEASED)
        
        ;; Transfer funds back to creator
        (try! (as-contract (stx-transfer? amount tx-sender creator)))
        
        ;; Mark as released
        (ok (map-set escrows 
            { task-id: task-id }
            (merge escrow-data { released: true })
        )))
    )
)

(define-public (open-dispute (task-id uint))
    (begin
        ;; Only authorized contracts can call this
        (asserts! (default-to false (map-get? authorized-contracts contract-caller)) ERR-NOT-AUTHORIZED)
        
        (let (
            (escrow-data (unwrap! (map-get? escrows { task-id: task-id }) ERR-ESCROW-NOT-FOUND))
        )
        
        ;; Ensure not already released
        (asserts! (not (get released escrow-data)) ERR-ALREADY-RELEASED)
        
        ;; Ensure dispute not already opened
        (asserts! (not (get dispute-opened escrow-data)) ERR-DISPUTE-ALREADY-OPENED)
        
        ;; Check if within dispute window
        (asserts! (< (- block-height (get deposited-at escrow-data)) DISPUTE-WINDOW-BLOCKS) ERR-DISPUTE-WINDOW-EXPIRED)
        
        ;; Mark dispute as opened
        (ok (map-set escrows 
            { task-id: task-id }
            (merge escrow-data { 
                dispute-opened: true,
                dispute-opened-at: (some block-height)
            })
        )))
    )
)

(define-public (resolve-dispute (task-id uint) (worker-percentage uint))
    (begin
        ;; Only authorized contracts (or owner for MVP) can call this
        (asserts! (or 
            (is-eq tx-sender CONTRACT-OWNER)
            (default-to false (map-get? authorized-contracts contract-caller))
        ) ERR-NOT-AUTHORIZED)
        
        ;; Validate percentage (0-100)
        (asserts! (<= worker-percentage u100) ERR-INVALID-PERCENTAGE)
        
        (let (
            (escrow-data (unwrap! (map-get? escrows { task-id: task-id }) ERR-ESCROW-NOT-FOUND))
            (worker (unwrap! (get worker escrow-data) ERR-NOT-AUTHORIZED))
            (creator (get creator escrow-data))
            (amount (get amount escrow-data))
            (worker-amount (/ (* amount worker-percentage) u100))
            (creator-amount (- amount worker-amount))
        )
        
        ;; Ensure not already released
        (asserts! (not (get released escrow-data)) ERR-ALREADY-RELEASED)
        
        ;; Ensure dispute is opened
        (asserts! (get dispute-opened escrow-data) ERR-NOT-AUTHORIZED)
        
        ;; Transfer funds according to split
        (if (> worker-amount u0)
            (try! (as-contract (stx-transfer? worker-amount tx-sender worker)))
            true
        )
        
        (if (> creator-amount u0)
            (try! (as-contract (stx-transfer? creator-amount tx-sender creator)))
            true
        )
        
        ;; Mark as released and resolved
        (ok (map-set escrows 
            { task-id: task-id }
            (merge escrow-data { 
                released: true,
                dispute-resolved: true
            })
        )))
    )
)

(define-public (withdraw-platform-fees (recipient principal))
    (begin
        ;; Only contract owner can withdraw fees
        (asserts! (is-eq tx-sender CONTRACT-OWNER) ERR-NOT-AUTHORIZED)
        
        (let (
            (fees (var-get platform-fees-collected))
        )
        
        (asserts! (> fees u0) ERR-INSUFFICIENT-FUNDS)
        
        ;; Transfer fees
        (try! (as-contract (stx-transfer? fees tx-sender recipient)))
        
        ;; Reset collected fees
        (var-set platform-fees-collected u0)
        (ok fees))
    )
)

Functions (16)

FunctionAccessArgs
get-escrowread-onlytask-id: uint
get-platform-feesread-only
calculate-platform-feeread-onlyamount: uint
calculate-total-depositread-onlyamount: uint
is-dispute-window-openread-onlytask-id: uint
is-authorized-contractread-onlycontract: principal
validate-amountprivateamount: uint
authorize-contractpubliccontract: principal
revoke-contractpubliccontract: principal
deposit-escrowpublictask-id: uint, amount: uint, creator: principal
set-workerpublictask-id: uint, worker: principal
release-fundspublictask-id: uint
refund-creatorpublictask-id: uint
open-disputepublictask-id: uint
resolve-disputepublictask-id: uint, worker-percentage: uint
withdraw-platform-feespublicrecipient: principal