ConceptsTreasury

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/treasury creates a new Treasury account with a fresh master key (DEK)
  • Sub-wallets: POST /v1/wallets derives a sub-wallet (one DEK per wallet); each has its own address and policy
  • Balance: GET /v1/wallets/{id}/balance reads on-chain USDC; falls back to a cache when RPC is down
  • Pay (x402): POST /v1/wallets/{id}/pay policy-checks + signs EIP-3009 + returns (authorization, signature)
  • Transfer (external): POST /v1/wallets/{id}/transfer same as pay but no x402 framing, no capability tag
  • Rotate: POST /v1/wallets/{id}/rotate re-issues the DEK; new address, same id, history preserved
  • Revoke: POST /v1/wallets/{id}/revoke flips 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:

RuleEffect
per_tx_cap_usdReject single payments above N USD
daily_cap_usd / weekly_cap_usd / monthly_cap_usdReject when window total exceeds N
velocity_capReject when N payments fire within a short window
counterparty_allowlistReject when to address is not in the list
counterparty_blocklistReject when to address is in the list
cooling_offReject within N seconds of the last payment
capability_cap_usdReject 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 once

The 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