;; Agent Payments - Escrow & settlement for agent-to-agent STX payments
;; Flow: caller deposits STX -> gateway verifies service -> release to provider
;; Integrates with agent-registry for reputation tracking.
(define-constant CONTRACT_OWNER tx-sender)
;; -- errors ---------------------------------------------
(define-constant ERR_OWNER_ONLY (err u400))
(define-constant ERR_NOT_FOUND (err u401))
(define-constant ERR_INSUFFICIENT (err u402))
(define-constant ERR_ALREADY_SETTLED (err u403))
(define-constant ERR_UNAUTHORIZED (err u404))
(define-constant ERR_EXPIRED (err u405))
(define-constant ERR_AGENT_INACTIVE (err u406))
;; Payment expires after ~144 blocks (~24h at 10min/block)
(define-constant PAYMENT_TTL u144)
;; -- state ----------------------------------------------
(define-data-var next-payment-id uint u1)
;; payment-id -> payment data
(define-map payments uint {
caller: principal, ;; agent paying
provider: principal, ;; agent receiving
agent-id: uint, ;; provider's agent-id in registry
amount: uint, ;; micro-STX
status: (string-ascii 10), ;; "pending", "completed", "refunded"
created-at: uint ;; block height
})
;; -- budget tracking (per principal per day-ish) --------
;; Simple daily budget: reset every ~144 blocks
(define-map budgets principal {
daily-limit: uint, ;; max micro-STX per period
spent: uint, ;; current period spending
period-start: uint ;; block height when period started
})
;; -- set budget (agent owner sets their own) ------------
(define-public (set-budget (daily-limit uint))
(begin
(map-set budgets tx-sender {
daily-limit: daily-limit,
spent: u0,
period-start: block-height
})
(print { event: "budget-set", owner: tx-sender, limit: daily-limit })
(ok true)
)
)
;; -- create payment (caller pays STX into contract) -----
(define-public (create-payment (agent-id uint))
(let (
(agent (unwrap! (contract-call? .agent-registry get-agent agent-id) ERR_NOT_FOUND))
(amount (get price-per-call agent))
(id (var-get next-payment-id))
)
;; Agent must be active
(asserts! (get active agent) ERR_AGENT_INACTIVE)
;; Budget check
(match (map-get? budgets tx-sender)
budget
(let (
(period-age (- block-height (get period-start budget)))
(current-spent (if (>= period-age PAYMENT_TTL)
u0 ;; period reset
(get spent budget)))
(new-spent (+ current-spent amount))
)
(asserts! (<= new-spent (get daily-limit budget)) ERR_INSUFFICIENT)
(map-set budgets tx-sender {
daily-limit: (get daily-limit budget),
spent: new-spent,
period-start: (if (>= period-age PAYMENT_TTL)
block-height
(get period-start budget))
})
)
true ;; no budget set = unlimited
)
;; Transfer STX from caller to this contract
(try! (stx-transfer? amount tx-sender (as-contract tx-sender)))
;; Record payment
(map-set payments id {
caller: tx-sender,
provider: (get owner agent),
agent-id: agent-id,
amount: amount,
status: "pending",
created-at: block-height
})
(var-set next-payment-id (+ id u1))
(print { event: "payment-created", id: id, caller: tx-sender, agent-id: agent-id, amount: amount })
(ok id)
)
)
;; -- complete payment (release to provider) -------------
;; Called by gateway/admin after service delivery confirmed
(define-public (complete-payment (payment-id uint))
(let ((pmt (unwrap! (map-get? payments payment-id) ERR_NOT_FOUND)))
;; Only contract owner (gateway) can complete
(asserts! (is-eq tx-sender CONTRACT_OWNER) ERR_UNAUTHORIZED)
(asserts! (is-eq (get status pmt) "pending") ERR_ALREADY_SETTLED)
;; Transfer STX from contract to provider
(try! (as-contract (stx-transfer? (get amount pmt) tx-sender (get provider pmt))))
;; Update status
(map-set payments payment-id (merge pmt { status: "completed" }))
;; Record success in registry
(try! (contract-call? .agent-registry record-call-success
(get agent-id pmt) (get amount pmt)))
(print { event: "payment-completed", id: payment-id, provider: (get provider pmt), amount: (get amount pmt) })
(ok true)
)
)
;; -- refund expired payment (caller reclaims) -----------
(define-public (refund-payment (payment-id uint))
(let ((pmt (unwrap! (map-get? payments payment-id) ERR_NOT_FOUND)))
(asserts! (is-eq (get status pmt) "pending") ERR_ALREADY_SETTLED)
;; Only caller or admin can refund
(asserts! (or (is-eq tx-sender (get caller pmt))
(is-eq tx-sender CONTRACT_OWNER)) ERR_UNAUTHORIZED)
;; Must be expired
(asserts! (>= (- block-height (get created-at pmt)) PAYMENT_TTL) ERR_EXPIRED)
;; Refund STX to caller
(try! (as-contract (stx-transfer? (get amount pmt) tx-sender (get caller pmt))))
;; Update status
(map-set payments payment-id (merge pmt { status: "refunded" }))
;; Record failure in registry
(try! (contract-call? .agent-registry record-call-failure (get agent-id pmt)))
(print { event: "payment-refunded", id: payment-id, caller: (get caller pmt), amount: (get amount pmt) })
(ok true)
)
)
;; -- read-only ------------------------------------------
(define-read-only (get-payment (payment-id uint))
(map-get? payments payment-id)
)
(define-read-only (get-budget (owner principal))
(map-get? budgets owner)
)
(define-read-only (get-total-payments)
(- (var-get next-payment-id) u1)
)