Source Code

;; nexus-escrow - Trustless Escrow Protocol
;; Secure multi-party payment escrow

(define-constant CONTRACT-OWNER tx-sender)
(define-constant PLATFORM-FEE u100)
(define-constant FEE-DENOMINATOR u10000)
(define-constant DISPUTE-PERIOD u1008)
(define-constant ERR-NOT-FOUND (err u100))
(define-constant ERR-NOT-AUTHORIZED (err u101))
(define-constant ERR-ALREADY-COMPLETE (err u102))
(define-constant ERR-DISPUTE-PERIOD (err u103))
(define-constant ERR-WRONG-AMOUNT (err u104))
(define-constant ERR-ZERO-AMOUNT (err u105))

(define-map escrows uint
  { buyer: principal, seller: principal, amount: uint,
    description: (string-utf8 256), created-at: uint,
    released: bool, disputed: bool, canceled: bool,
    arbiter: (optional principal) })

(define-data-var escrow-count uint u0)
(define-data-var total-locked uint u0)
(define-data-var fees-collected uint u0)

(define-public (create-escrow
    (seller principal) (amount uint)
    (description (string-utf8 256))
    (arbiter (optional principal)))
  (let ((eid (+ (var-get escrow-count) u1)))
    (asserts! (> amount u0) ERR-ZERO-AMOUNT)
    (try! (stx-transfer? amount tx-sender (as-contract tx-sender)))
    (map-set escrows eid
      { buyer: tx-sender, seller: seller, amount: amount,
        description: description, created-at: block-height,
        released: false, disputed: false, canceled: false,
        arbiter: arbiter })
    (var-set escrow-count eid)
    (var-set total-locked (+ (var-get total-locked) amount))
    (ok eid)))

(define-public (release (escrow-id uint))
  (let ((e (unwrap! (map-get? escrows escrow-id) ERR-NOT-FOUND)))
    (asserts! (is-eq tx-sender (get buyer e)) ERR-NOT-AUTHORIZED)
    (asserts! (not (get released e)) ERR-ALREADY-COMPLETE)
    (asserts! (not (get canceled e)) ERR-ALREADY-COMPLETE)
    (let ((fee (/ (* (get amount e) PLATFORM-FEE) FEE-DENOMINATOR))
          (net (- (get amount e) fee)))
      (try! (as-contract (stx-transfer? net tx-sender (get seller e))))
      (try! (as-contract (stx-transfer? fee tx-sender CONTRACT-OWNER)))
      (map-set escrows escrow-id (merge e { released: true }))
      (var-set total-locked (- (var-get total-locked) (get amount e)))
      (var-set fees-collected (+ (var-get fees-collected) fee))
      (ok true))))

(define-public (raise-dispute (escrow-id uint))
  (let ((e (unwrap! (map-get? escrows escrow-id) ERR-NOT-FOUND)))
    (asserts! (or (is-eq tx-sender (get buyer e)) (is-eq tx-sender (get seller e))) ERR-NOT-AUTHORIZED)
    (asserts! (not (get released e)) ERR-ALREADY-COMPLETE)
    (asserts! (<= block-height (+ (get created-at e) DISPUTE-PERIOD)) ERR-DISPUTE-PERIOD)
    (ok (map-set escrows escrow-id (merge e { disputed: true })))))

(define-public (resolve-dispute (escrow-id uint) (favor-buyer bool))
  (let ((e (unwrap! (map-get? escrows escrow-id) ERR-NOT-FOUND)))
    (asserts! (is-eq (some tx-sender) (get arbiter e)) ERR-NOT-AUTHORIZED)
    (asserts! (get disputed e) ERR-NOT-FOUND)
    (let ((recipient (if favor-buyer (get buyer e) (get seller e))))
      (try! (as-contract (stx-transfer? (get amount e) tx-sender recipient)))
      (map-set escrows escrow-id (merge e { released: true }))
      (ok true))))

(define-read-only (get-escrow (escrow-id uint)) (ok (map-get? escrows escrow-id)))
(define-read-only (get-escrow-count) (ok (var-get escrow-count)))
(define-read-only (get-platform-stats) (ok { locked: (var-get total-locked), fees: (var-get fees-collected) }))

Functions (7)

FunctionAccessArgs
create-escrowpublicseller: principal, amount: uint, description: (string-utf8 256
releasepublicescrow-id: uint
raise-disputepublicescrow-id: uint
resolve-disputepublicescrow-id: uint, favor-buyer: bool
get-escrowread-onlyescrow-id: uint
get-escrow-countread-only
get-platform-statsread-only