Source Code


;; title: TrustB Escrow
;; version: 1.0.0
;; summary: Secure escrow contract requiring mutual approval for release or cancellation.
;; description: Allows a buyer to lock STX for a seller. Funds are released only when both parties approve. Funds are refunded if both parties cancel.

;; traits
;;

;; token definitions
;;

;; constants
(define-constant ERR-NOT-AUTHORIZED (err u100))
(define-constant ERR-ALREADY-EXISTS (err u101))
(define-constant ERR-NOT-FOUND (err u102))
(define-constant ERR-ALREADY-COMPLETED (err u103))

(define-constant STATUS-OPEN u1)
(define-constant STATUS-RELEASED u2)
(define-constant STATUS-REFUNDED u3)

;; data vars
(define-data-var escrow-id-nonce uint u0)

;; data maps
(define-map escrows 
    uint 
    {
        buyer: principal,
        seller: principal,
        amount: uint,
        buyer-approved: bool,
        seller-approved: bool,
        buyer-cancelled: bool,
        seller-cancelled: bool,
        status: uint
    }
)

;; public functions

;; @desc Create a new escrow by locking STX
;; @param seller The principal who will receive the funds if released
;; @param amount The amount of uSTX to lock
(define-public (create-escrow (seller principal) (amount uint))
    (let
        (
            (escrow-id (+ (var-get escrow-id-nonce) u1))
            (buyer tx-sender)
        )
        (try! (stx-transfer? amount buyer (as-contract tx-sender)))
        (map-set escrows escrow-id {
            buyer: buyer,
            seller: seller,
            amount: amount,
            buyer-approved: false,
            seller-approved: false,
            buyer-cancelled: false,
            seller-cancelled: false,
            status: STATUS-OPEN
        })
        (var-set escrow-id-nonce escrow-id)
        (ok escrow-id)
    )
)

;; @desc Confirm release of funds (Buyer or Seller)
;; @param escrow-id The ID of the escrow
(define-public (confirm-release (escrow-id uint))
    (let
        (
            (escrow (unwrap! (map-get? escrows escrow-id) ERR-NOT-FOUND))
            (caller tx-sender)
            (is-buyer (is-eq caller (get buyer escrow)))
            (is-seller (is-eq caller (get seller escrow)))
        )
        (asserts! (or is-buyer is-seller) ERR-NOT-AUTHORIZED)
        (asserts! (is-eq (get status escrow) STATUS-OPEN) ERR-ALREADY-COMPLETED)

        ;; Update approvals
        (let
            (
                (new-buyer-approved (if is-buyer true (get buyer-approved escrow)))
                (new-seller-approved (if is-seller true (get seller-approved escrow)))
                (updated-escrow (merge escrow {
                    buyer-approved: new-buyer-approved,
                    seller-approved: new-seller-approved
                }))
            )
            (map-set escrows escrow-id updated-escrow)
            
            ;; Check if both approved
            (if (and new-buyer-approved new-seller-approved)
                (begin
                    (try! (as-contract (stx-transfer? (get amount escrow) tx-sender (get seller escrow))))
                    (map-set escrows escrow-id (merge updated-escrow { status: STATUS-RELEASED }))
                    (ok true)
                )
                (ok false)
            )
        )
    )
)

;; @desc Confirm cancellation of escrow (Buyer or Seller)
;; @param escrow-id The ID of the escrow
(define-public (confirm-cancel (escrow-id uint))
    (let
        (
            (escrow (unwrap! (map-get? escrows escrow-id) ERR-NOT-FOUND))
            (caller tx-sender)
            (is-buyer (is-eq caller (get buyer escrow)))
            (is-seller (is-eq caller (get seller escrow)))
        )
        (asserts! (or is-buyer is-seller) ERR-NOT-AUTHORIZED)
        (asserts! (is-eq (get status escrow) STATUS-OPEN) ERR-ALREADY-COMPLETED)

        ;; Update cancellations
        (let
            (
                (new-buyer-cancelled (if is-buyer true (get buyer-cancelled escrow)))
                (new-seller-cancelled (if is-seller true (get seller-cancelled escrow)))
                (updated-escrow (merge escrow {
                    buyer-cancelled: new-buyer-cancelled,
                    seller-cancelled: new-seller-cancelled
                }))
            )
            (map-set escrows escrow-id updated-escrow)
            
            ;; Check if both cancelled
            (if (and new-buyer-cancelled new-seller-cancelled)
                (begin
                    (try! (as-contract (stx-transfer? (get amount escrow) tx-sender (get buyer escrow))))
                    (map-set escrows escrow-id (merge updated-escrow { status: STATUS-REFUNDED }))
                    (ok true)
                )
                (ok false)
            )
        )
    )
)

;; read only functions

(define-read-only (get-escrow (escrow-id uint))
    (map-get? escrows escrow-id)
)

;; private functions
;;

Functions (4)

FunctionAccessArgs
create-escrowpublicseller: principal, amount: uint
confirm-releasepublicescrow-id: uint
confirm-cancelpublicescrow-id: uint
get-escrowread-onlyescrow-id: uint