Treasury
Treasury is Agentagon’s custodial wallet service. It holds encrypted seeds, signs EIP-3009 authorizations, and enforces per-wallet policy (caps, allowlists, capability matching). Buyers can run self-custody (seed on their machine) or managed custody (seed in Treasury, accessed by bearer).
What Treasury does
Treasury is the only component that touches private keys. Everything else (Gate, MCP servers, CLI, SDKs) talks to it over HTTPS with a bearer.
Operations:
- Mint:
POST /v1/treasurycreates a new Treasury account with a fresh master key (DEK) - Sub-wallets:
POST /v1/walletsderives a sub-wallet (one DEK per wallet); each has its own address and policy - Balance:
GET /v1/wallets/{id}/balancereads on-chain USDC; falls back to a cache when RPC is down - Pay (x402):
POST /v1/wallets/{id}/paypolicy-checks + signs EIP-3009 + returns(authorization, signature) - Transfer (external):
POST /v1/wallets/{id}/transfersame as pay but no x402 framing, no capability tag - Rotate:
POST /v1/wallets/{id}/rotatere-issues the DEK; new address, same id, history preserved - Revoke:
POST /v1/wallets/{id}/revokeflips the wallet to revoked; all subsequent ops 410
Treasury never broadcasts to chain. It signs; the caller broadcasts (or hands the signed authorization to a facilitator for x402 settle).
Sub-wallet model
One agent can have many sub-wallets. The ratio is intentional:
- Per-agent: one wallet per autonomous agent, scoped to a budget
- Per-purpose: one wallet per spending category (research, infra, marketing) with different caps
- Per-counterparty: one wallet per major recipient with allowlist rules
Sub-wallets share one Treasury bearer but carry independent policies and balances. A revoked sub-wallet doesn’t affect siblings.
Policy
Each sub-wallet has a policy enforced server-side at sign time:
| Rule | Effect |
|---|---|
per_tx_cap_usd | Reject single payments above N USD |
daily_cap_usd / weekly_cap_usd / monthly_cap_usd | Reject when window total exceeds N |
velocity_cap | Reject when N payments fire within a short window |
counterparty_allowlist | Reject when to address is not in the list |
counterparty_blocklist | Reject when to address is in the list |
cooling_off | Reject within N seconds of the last payment |
capability_cap_usd | Reject when capability matches and amount > cap |
Policy denials return TreasuryAPIError(403, reason="policy_denied", decision_id="pd_xxx"). The decision_id is logged in Treasury’s audit ledger and referenced from the platform’s ledger row.
transfer() (external, no capability) is fail-closed against capability_cap_usd: if any capability cap exists in the policy, transfer() returns 403 with transfer_not_permitted_with_capability_cap. Use per_tx_cap_usd for dollar limits on transfers.
Bootstrap and bearer
A fresh Treasury is minted via POST /v1/treasury:
from agentagon.wallet import create_treasury
result = await create_treasury(
base_url="https://treasury.agentagon.ai",
owner_type="agent",
owner_id="my-agent",
)
print(result.api_key) # save this; shown onceThe api_key is the legacy master shape (ag_treasury_sk_*); it is being phased out. New deployments mint a Treasury-scoped PAT instead via POST /v1/treasuries/{id}/pats.
After bootstrap, every call uses the bearer:
from agentagon.wallet import Treasury
t = Treasury.connect(api_key=result.api_key)The SDK doesn’t care which shape the bearer takes; it just sets Authorization: Bearer <token>.
Stale balances
Treasury reads on-chain on every balance() call. When the upstream RPC is unreachable it falls back to its cache and sets stale=true with the original updated_at. Surface the staleness to your users; the cached figure can be many minutes old during a sustained outage.
See also
agentagon.wallet: Python client@agentagon/sdk/wallet: Node client- Authentication: how Treasury bearers differ from Gate PATs
- x402 wire format: what
pay()actually signs