Skip to content

agentpay-x402 0.5.0 — Stacks sBTC on mainnet

Latest

Choose a tag to compare

@romudille-bit romudille-bit released this 11 Sep 21:00
· 12 commits to main since this release
v0.5.0
89eda86

Stacks sBTC on mainnet. pip install "agentpay-x402[stacks]" and the
one-liner quickstart(stacks_key="…", prefer_chain="stacks") pay tools in
sBTC against agentpay.tools — sign-don't-broadcast, hard USD cap, receipts
chain-verified on the public ledger. Three mainnet receipts settled before
release (30689b5e…, d1de1a79…, 59ce7014…); the daily flagship agent
runs on the same rail. Everything below is the M2 hardening that went in
between 0.4.0 (testnet) and this release.

Fixed

  • Stacks confirmation never seen — the gateway polled Hiro at
    /extended/v1/tx/<txid> without 0x; Hiro answers that with a 302 the
    client did not follow, so every settle ended "uncertain" even after the
    tx confirmed. The poll asks for the 0x form (found on the first mainnet
    settle, txid 30689b5e…).
  • Uncertain reply swallowed by Cloudflare — the gateway's uncertain
    settle answered 502, which Cloudflare replaces with its own HTML page in
    front of agentpay.tools; the SDK saw no JSON. It is 503 now, and a
    5xx without a body on redeem keeps the redeem context instead of failing.
  • Refund contract reply was a 502 — the same Cloudflare replacement as
    above hid the refund_pending body from the SDK on agentpay.tools; it
    is a 500 now (payment_status unchanged). AGE-155.
  • A gateway's rejected alone no longer zeroes a Stacks leg — the SDK
    checks Hiro first; unless the tx is unknown or aborted on-chain the spend
    stays recorded and the call ends SettlementUncertain (redeemable).
    AGE-152. The gateway also stops treating dropped_* as definitive.
  • Stacks settle wall time is boundedSTACKS_SETTLE_DEADLINE_S
    (75 s) turns a slow broadcast+confirm into a structured uncertain
    before the edge's 100 s cut can strip the body. AGE-150.
  • LEDGER_FLAGSHIP_ADDRESSES rejected Stacks addresses (SP…/ST…), so a
    Stacks payer's receipts could never reach /ledger.
  • Stacks signing with a raw 64-hex key — the presign sighash cleared the
    origin condition with the compressed key-encoding byte regardless of the
    key, so uncompressed keys produced signatures the node rejected
    (SignatureValidation). The sighash now keeps the key's own encoding;
    both encodings are fixture-tested byte-for-byte against stacks.js.

Added

  • pip install "agentpay-x402[stacks]" — the Stacks extra (the core
    install already carries the signing dependency; the extra mirrors [base]).
  • quickstart(stacks_key=…, prefer_chain="stacks") — a Stacks-only
    wallet skips registration and needs no Stellar secret; the session exposes
    stacks_public_key.
  • Flagship analyst on the Stacks railFLAGSHIP_STACKS_KEY puts the
    daily cron's gateway-paid calls on sBTC (FLAGSHIP_RAIL pins stacks,
    base, or alternates by day); an uncertain settle is redeemed once the
    transaction confirms instead of being dropped; the Bazaar keepalive and
    external x402 sellers stay on Base. /ledger.json names the Stacks payer
    under wallets.stacks.
  • agentpay._stacks_tx.verify_origin_signature(signed_tx) — pure check that
    a signed transaction's origin signature recovers to its signer; the
    gateway runs it before consuming replay state or broadcasting.
  • Session.redeem(exc) / AgentPayClient.redeem(exc) — finish a Stacks
    call that ended in SettlementUncertain: wait for the transaction to
    confirm, then re-present the identical signed payment; the gateway
    delivers exactly once per txid. SettlementUncertain.redeem_ctx carries
    what redeem needs.
  • Stacks mainnetAgentWallet(network="mainnet", stacks_key=…) pays
    sBTC on stacks:1 against agentpay.tools; see docs/stacks-mainnet.md.
    examples/stacks_m1_demo.py takes STACKS_NETWORK=mainnet.
  • Session.redeem_txid(txid, tool, params) /
    AgentPayClient.uncertain_from_txid — redeem an uncertain Stacks settle
    from the txid alone when the signing process is gone: the signed bytes
    come from Hiro and the memo is the challenge id. _stacks_tx.memo_of.
    The demo takes STACKS_REDEEM_TXID=<txid>.
  • tools/stacks_derive_key.py — derive STACKS_AGENT_KEY from a Leather
    24-word Secret Key (m/44'/5757'/0'/0/<account>), hidden prompt, key
    written only with --print-key; tested against @stacks/wallet-sdk.

Changed

  • The STX fee a 402 suggests is clamped to STACKS_MAX_FEE_MICROSTX
    (0.05 STX; env can only lower it) before signing — a gateway cannot name
    the payer's fee freely.
  • Review lows (AGE-153): the memo must equal the challenge id cut to 34
    bytes (a prefix rule let a 1-byte memo bind any id); the client floor
    rate is $20,000 on mainnet ($10,000 testnet, raise-only); the gateway
    quotes Hiro's medium fee tier capped at 20,000 µSTX; a replay-store
    outage before broadcast answers rejected with a re-sign hint (the SDK
    verifies absence on Hiro first) instead of an unfulfillable "retry the
    same proof"; the in-memory txid guard is bounded; import os moved to
    module scope.