Source Code

;; EscrowService Contract
;; This contract provides a secure escrow service for transactions between buyers and sellers.
;; It holds funds until the buyer releases them to the seller or a dispute is resolved.

;; --- Constants ---
;; Defines immutable values used throughout the contract for error handling and configuration.

(define-constant CONTRACT_OWNER tx-sender) ;; Sets the contract deployer as the owner.
(define-constant ERR_NOT_AUTHORIZED (err u100)) ;; Error for unauthorized actions.
(define-constant ERR_ESCROW_NOT_FOUND (err u101)) ;; Error when an escrow cannot be found.
(define-constant ERR_ALREADY_RELEASED (err u102)) ;; Error if funds have already been released.
(define-constant ERR_TRANSFER_FAILED (err u103)) ;; Error for failed STX transfers.
(define-constant ERR_INVALID_ESCROW_ID (err u104)) ;; Error for invalid escrow IDs.
(define-constant ERR_INVALID_AMOUNT (err u105)) ;; Error for invalid transaction amounts.
(define-constant ERR_INVALID_SELLER (err u106)) ;; Error for invalid seller principals.
(define-constant ERR_ESCROW_EXPIRED (err u107)) ;; Error for expired escrows.
(define-constant ESCROW_DURATION u1008) ;; The duration of the escrow in blocks (approximately 7 days).

;; --- Data Maps ---
;; Defines the data structures used to store escrow information.

(define-map Escrows
  { escrow-id: uint }
  {
    buyer: principal, ;; The principal of the buyer.
    seller: principal, ;; The principal of the seller.
    amount: uint, ;; The amount of STX held in escrow.
    state: (string-ascii 10), ;; The current state of the escrow (e.g., "locked", "released", "refunded").
    created-at: uint, ;; The block height at which the escrow was created.
    expires-at: uint ;; The block height at which the escrow expires.
  }
)

;; --- Variables ---
;; Defines mutable variables for tracking the contract's state.

(define-data-var last-escrow-id uint u0) ;; Tracks the ID of the last created escrow.

;; --- Helper functions ---

;; Checks if a seller principal is valid.
(define-private (is-valid-seller (seller principal))
  (and 
    (not (is-eq seller tx-sender))
    (not (is-eq seller (as-contract tx-sender)))
  )
)

;; Checks if an escrow ID is valid.
(define-private (is-valid-escrow-id (escrow-id uint))
  (<= escrow-id (var-get last-escrow-id))
)

;; --- Public Functions ---

;; Creates a new escrow.
;; @param seller: The principal of the seller.
;; @param amount: The amount of STX to hold in escrow.
;; @returns (ok uint): The ID of the newly created escrow.
(define-public (create-escrow (seller principal) (amount uint))
  (let
    (
      (escrow-id (+ (var-get last-escrow-id) u1))
      (expires-at (+ stacks-block-height ESCROW_DURATION))
    )
    (asserts! (> amount u0) (err ERR_INVALID_AMOUNT))
    (asserts! (is-valid-seller seller) (err ERR_INVALID_SELLER))
    (match (stx-transfer? amount tx-sender (as-contract tx-sender))
      success
        (begin
          (map-set Escrows
            { escrow-id: escrow-id }
            {
              buyer: tx-sender,
              seller: seller,
              amount: amount,
              state: "locked",
              created-at: stacks-block-height,
              expires-at: expires-at
            }
          )
          (var-set last-escrow-id escrow-id)
          (print {event: "escrow_created", escrow-id: escrow-id, buyer: tx-sender, seller: seller, amount: amount})
          (ok escrow-id)
        )
      error (err ERR_TRANSFER_FAILED)
    )
  )
)

;; Releases funds to the seller.
;; @param escrow-id: The ID of the escrow to release.
;; @returns (ok bool): True if the funds are released successfully.
(define-public (release-funds (escrow-id uint))
  (begin
    (asserts! (is-valid-escrow-id escrow-id) (err ERR_INVALID_ESCROW_ID))
    (let
      (
        (escrow (unwrap! (map-get? Escrows { escrow-id: escrow-id }) (err ERR_ESCROW_NOT_FOUND)))
        (seller (get seller escrow))
        (amount (get amount escrow))
      )
      (asserts! (or (is-eq tx-sender CONTRACT_OWNER) (is-eq tx-sender (get buyer escrow))) (err ERR_NOT_AUTHORIZED))
      (asserts! (is-eq (get state escrow) "locked") (err ERR_ALREADY_RELEASED))
      (asserts! (<= stacks-block-height (get expires-at escrow)) (err ERR_ESCROW_EXPIRED))
      (match (as-contract (stx-transfer? amount tx-sender seller))
        success 
          (begin
            (map-set Escrows
              { escrow-id: escrow-id }
              (merge escrow { state: "released" })
            )
            (print {event: "funds_released", escrow-id: escrow-id, seller: seller, amount: amount})
            (ok true)
          )
        error (err ERR_TRANSFER_FAILED)
      )
    )
  )
)

;; Refunds the buyer.
;; @param escrow-id: The ID of the escrow to refund.
;; @returns (ok bool): True if the refund is successful.
(define-public (refund-buyer (escrow-id uint))
  (begin
    (asserts! (is-valid-escrow-id escrow-id) (err ERR_INVALID_ESCROW_ID))
    (let
      (
        (escrow (unwrap! (map-get? Escrows { escrow-id: escrow-id }) (err ERR_ESCROW_NOT_FOUND)))
        (buyer (get buyer escrow))
        (amount (get amount escrow))
      )
      (asserts! (is-eq tx-sender CONTRACT_OWNER) (err ERR_NOT_AUTHORIZED))
      (asserts! (is-eq (get state escrow) "locked") (err ERR_ALREADY_RELEASED))
      (match (as-contract (stx-transfer? amount tx-sender buyer))
        success 
          (begin
            (map-set Escrows
              { escrow-id: escrow-id }
              (merge escrow { state: "refunded" })
            )
            (print {event: "buyer_refunded", escrow-id: escrow-id, buyer: buyer, amount: amount})
            (ok true)
          )
        error (err ERR_TRANSFER_FAILED)
      )
    )
  )
)

;; --- Read-Only Functions ---

;; Retrieves escrow details by ID.
;; @param escrow-id: The ID of the escrow to retrieve.
;; @returns (ok {<escrow-data>}): The escrow data or an error if not found.
(define-read-only (get-escrow (escrow-id uint))
  (begin
    (asserts! (is-valid-escrow-id escrow-id) (err ERR_INVALID_ESCROW_ID))
    (match (map-get? Escrows { escrow-id: escrow-id })
      escrow (ok escrow)
      (err ERR_ESCROW_NOT_FOUND)
    )
  )
)

;; Retrieves the ID of the last created escrow.
;; @returns (ok uint): The last escrow ID.
(define-read-only (get-last-escrow-id)
  (ok (var-get last-escrow-id))
)

Functions (7)

FunctionAccessArgs
is-valid-sellerprivateseller: principal
is-valid-escrow-idprivateescrow-id: uint
create-escrowpublicseller: principal, amount: uint
release-fundspublicescrow-id: uint
refund-buyerpublicescrow-id: uint
get-escrowread-onlyescrow-id: uint
get-last-escrow-idread-only