Skip to content

Instantly share code, notes, and snippets.

@kopcho
Last active November 14, 2025 03:00
Show Gist options
  • Select an option

  • Save kopcho/1f7ed9f270f8fb94516049a9a7fb06b6 to your computer and use it in GitHub Desktop.

Select an option

Save kopcho/1f7ed9f270f8fb94516049a9a7fb06b6 to your computer and use it in GitHub Desktop.
Arbiter-Controlled Digital Asset Escrow (CAD019/CAD029 Compliant)
; vim: syntax=clojure
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; ACTOR: Arbiter-Controlled Digital Asset Escrow (CAD019/CAD029 Compliant)
;;
;; Purpose: A trustless escrow contract to securely hold any CAD019/CAD029
;; Digital Asset (Tokens, NFTs) until a designated ARBITER authorizes
;; release to the SELLER or refund to the BUYER.
;;
;; Mechanics: Uses the Asset's 'offer/accept' mechanism to lock funds and the
;; Asset's 'direct-transfer' to release funds.
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Persistent State Definitions
(def BUYER nil) ; Address of the depositor/Buyer
(def SELLER nil) ; Address of the asset recipient/Seller
(def ARBITER nil) ; Address of the authorization authority
(def ASSET_ADDRESS nil) ; Address of the CAD019 Asset Actor (e.g., #140 EMONY)
(def ASSET_AMOUNT 0) ; The quantity of the asset (1 for an NFT, >1 for a token)
(def STATUS :uninitialized) ; States: :uninitialized, :awaiting-deposit, :funded, :released, :refunded
(defn ^:callable init-escrow
^{:doc "Initializes terms, setting all parties, the asset address, and the amount."}
[seller-address arbiter-address asset-address asset-amount]
;; Corrected to use core type predicates for robustness
(assert (and (address? seller-address) (address? arbiter-address) (address? asset-address) (long? asset-amount)) "Invalid arguments")
;; FIX: Replaced (pos? asset-amount) with robust (> asset-amount 0)
(assert (> asset-amount 0) "Asset amount must be positive")
(assert (= STATUS :uninitialized) "Escrow already initialized")
;; Set immutable terms
(def BUYER *caller*) ; Caller is the depositor/Buyer
(def SELLER seller-address)
(def ARBITER arbiter-address)
(def ASSET_ADDRESS asset-address)
(def ASSET_AMOUNT asset-amount)
(def STATUS :awaiting-deposit)
(log :ESCROW-INIT *address* BUYER SELLER ARBITER ASSET_ADDRESS ASSET_AMOUNT)
:initialized)
(defn ^:callable accept-deposit
^{:doc "Accepts the asset that the BUYER has *offered* to this Actor's address, locking it."}
[]
;; 1. Authorization and State Check
(assert (= *caller* BUYER) "Only the defined buyer can finalize the deposit")
(assert (= STATUS :awaiting-deposit) "Must be awaiting deposit")
;; 2. Crucial Step: Call the Asset Actor to ACCEPT the offer
;; FIXES:
;; 1) Removed the faulty (try ... catch err ...) block
;; 2) Corrected the call arity to pass the required BUYER address as the sender argument
(call ASSET_ADDRESS
(accept BUYER ASSET_AMOUNT)) ; Transfers asset ownership to the Escrow Actor
;; 3. Finalize
(def STATUS :funded)
(log :ESCROW-DEPOSITED *address* ASSET_ADDRESS ASSET_AMOUNT)
:deposit-accepted)
(defn ^:callable release-to-seller
^{:doc "Transfers the locked asset to the SELLER upon fulfillment. Requires ARBITER authorization."}
[]
;; 1. Authorization and State Check
(when-not (= *caller* ARBITER) (fail :TRUST "Unauthorized: Only the Arbiter can release funds"))
(when-not (= STATUS :funded) (fail :STATE "Escrow is not funded"))
;; 2. Crucial Step: Call the Asset Actor to DIRECTLY TRANSFER the asset
;; FIXES:
;; 1) Replaced undeclared symbol (-direct-transfer) with callable symbol (direct-transfer)
;; 2) Added required third argument (nil) to resolve the final :ARITY error
(call ASSET_ADDRESS
(direct-transfer SELLER ASSET_AMOUNT nil)) ; Escrow Actor instructs the Asset Actor to transfer
;; 3. Finalize
(def STATUS :released)
(log :ESCROW-RELEASED SELLER ASSET_AMOUNT)
:released)
(defn ^:callable refund-to-buyer
^{:doc "Transfers the locked asset back to the BUYER if the transaction fails. Requires ARBITER authorization."}
[]
;; 1. Authorization and State Check
(when-not (= *caller* ARBITER) (fail :TRUST "Unauthorized: Only the Arbiter can refund funds"))
(when-not (= STATUS :funded) (fail :STATE "Escrow is not funded"))
;; 2. Crucial Step: Call the Asset Actor to DIRECTLY TRANSFER the asset back
;; FIXES:
;; 1) Replaced undeclared symbol (-direct-transfer) with callable symbol (direct-transfer)
;; 2) Added required third argument (nil) to resolve the final :ARITY error
(call ASSET_ADDRESS
(direct-transfer BUYER ASSET_AMOUNT nil))
;; 3. Finalize
(def STATUS :refunded)
(log :ESCROW-REFUNDED BUYER ASSET_AMOUNT)
:refunded)
;; Export the public interface
{
:init-escrow init-escrow
:accept-deposit accept-deposit
:release-to-seller release-to-seller
:refund-to-buyer refund-to-buyer
}
@kopcho
Copy link
Author

kopcho commented Nov 13, 2025

Removed faulty try/catch, and corrected arguments to (accept BUYER ASSET_AMOUNT)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment