The agent treasury is the Asset Signer PDA derived from an MPL Core asset. It has no private key — only the asset’s owner can authorise an mpl-core::Execute instruction, and Execute is the only path that can CPI-sign on behalf of the PDA. This page documents the five HTTP routes that drive that PDA:
EndpointMethodPurpose
GET /v1/agents/{mint}/treasury/balancesreadNative SOL + every SPL token sitting on the PDA right now.
POST /v1/agents/{mint}/treasury/provision/preparewritePre-create the curated stable ATAs (USDC / USDT / USDG).
POST /v1/agents/{mint}/treasury/withdraw/preparewriteOwner moves a specific atomic amount of one SPL mint out.
POST /v1/agents/{mint}/treasury/withdraw-all/preparewriteSame shape; drains the full balance (or no_op when zero).
POST /v1/agents/{mint}/treasury/withdraw-sol/preparewriteOwner moves a specific lamport amount of native SOL out.
POST /v1/agents/{mint}/treasury/withdraw-sol-all/preparewriteSame shape; drains all SOL above a configurable reserve.
Every write route follows the standard Prepare → Submit lifecycle: the API never holds your key, you sign locally, then hand the signed bytes to POST /v1/submit.

Read balances

curl -sX GET https://api.leash.market/v1/agents/$MINT/treasury/balances \
  -H "Authorization: Bearer $LSH_TEST_KEY"
{
  "agent_asset": "E1wVJP…",
  "network": "solana-devnet",
  "treasury": "AssetSigner…",
  "sol": {
    "lamports": "12500000",
    "sol": 0.0125,
    "spendable_lamports": "12495000",
    "spendable_sol": 0.012495
  },
  "spl": [
    {
      "mint": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
      "symbol": "USDC",
      "ata": "TreasuryUSDCAta…",
      "token_program": "TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA",
      "amount": "100000",
      "decimals": 6,
      "ui_amount": 0.1
    },
    {
      "mint": "4F6PM96JJxngmHnZLBh9n58RH4aTVNWvDs2nuwrT5BP7",
      "symbol": "USDG",
      "ata": "TreasuryUSDGAta…",
      "token_program": "TokenzQdBNbLqP5VEhdkAS6EPFLC1PHnBqCXEpPxuEb",
      "amount": "0",
      "decimals": 6,
      "ui_amount": 0
    }
  ]
}
spendable_lamports already subtracts a small safety reserve so the PDA keeps enough rent to sign its own future transactions; use it directly to gate “Withdraw all SOL” UI buttons.

Provision stable ATAs

Pre-create the curated stable ATAs so wallets, faucets, and other agents can deposit into the treasury without “recipient has no token account” errors. Idempotent: if every ATA already exists, the response is no_op: true and no transaction is constructed.
curl -sX POST https://api.leash.market/v1/agents/$MINT/treasury/provision/prepare \
  -H "Authorization: Bearer $LSH_TEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "payer": "FFvP…owner",
    "authority": "FFvP…owner"
  }'
mints is optional. Omit it to provision the network’s curated stable list (USDC on devnet; USDC + USDT + USDG on mainnet); pass an explicit list to provision arbitrary mints. USDG is a Token-2022 mint, so its entry must include "token_program": "token-2022":
{
  "payer": "FFvP…owner",
  "authority": "FFvP…owner",
  "mints": [
    { "mint": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU", "symbol": "USDC" },
    {
      "mint": "4F6PM96JJxngmHnZLBh9n58RH4aTVNWvDs2nuwrT5BP7",
      "symbol": "USDG",
      "token_program": "token-2022"
    }
  ]
}
A non-trivial response looks like:
{
  "event_id": "01J…",
  "network": "solana-devnet",
  "transaction": {
    "base64": "AQABAv…",
    "message_base64": "AQABAv…",
    "recent_blockhash": "GZNb…",
    "last_valid_block_height": 320489201,
    "fee_payer": "FFvP…owner",
    "signers": ["FFvP…owner"]
  },
  "echo": {
    "treasury": "AssetSigner…",
    "atas": [
      {
        "mint": "4zMMC…",
        "symbol": "USDC",
        "address": "TreasuryUSDCAta…",
        "token_program": "Tokenkeg…",
        "created": true
      }
    ]
  }
}
Sign transaction.base64 (or transaction.message_base64, see Prepare → Submit) with the owner key, then post the signed bytes back to POST /v1/submit:
curl -sX POST https://api.leash.market/v1/submit \
  -H "Authorization: Bearer $LSH_TEST_KEY" \
  -H "Content-Type: application/json" \
  -d "{ \"event_id\": \"$EVENT_ID\", \"transaction_base64\": \"$SIGNED\" }"
The “no-op” envelope skips the round-trip entirely:
{
  "event_id": null,
  "network": "solana-devnet",
  "transaction": null,
  "echo": {
    "treasury": "AssetSigner…",
    "atas": [
      {
        "mint": "4zMMC…",
        "symbol": "USDC",
        "address": "TreasuryUSDCAta…",
        "token_program": "Tokenkeg…",
        "created": false
      }
    ]
  },
  "no_op": true
}

Withdraw an SPL stable

destination is a wallet address, not an ATA — the API derives the destination’s ATA for spl_mint and prepends a CreateIdempotent instruction when it doesn’t exist (will_create_destination_ata: true in the response).
curl -sX POST https://api.leash.market/v1/agents/$MINT/treasury/withdraw/prepare \
  -H "Authorization: Bearer $LSH_TEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "payer": "FFvP…owner",
    "authority": "FFvP…owner",
    "spl_mint": "4F6PM96JJxngmHnZLBh9n58RH4aTVNWvDs2nuwrT5BP7",
    "destination": "FFvP…owner",
    "amount": "99000000",
    "token_program": "token-2022",
    "decimals": 6,
    "create_destination_ata_if_missing": true
  }'
FieldRequiredNotes
payeryesWallet that pays SOL fees + ATA rent. Usually the owner.
authorityyesAsset owner. Only the owner can authorise mpl-core::Execute.
spl_mintyesThe SPL mint to debit (USDC / USDT / USDG / arbitrary).
destinationyesReceiving wallet (its ATA is derived; no need to pre-create).
amountyesAtomic units (USDG has 6 decimals, so 99000000 = 99 USDG).
token_programno"spl" (default) or "token-2022". USDG requires "token-2022".
decimalsnoPre-fetched mint decimals — saves an RPC roundtrip in the API.
create_destination_ata_if_missingnoDefaults to true. Set false to fail loudly when the destination has no ATA yet.
Response shape:
{
  "event_id": "01J…",
  "network": "solana-devnet",
  "transaction": {
    "base64": "AQABAv…",
    "message_base64": "…",
    "recent_blockhash": "…",
    "fee_payer": "FFvP…",
    "signers": ["FFvP…"]
  },
  "echo": {
    "treasury": "AssetSigner…",
    "source_token_account": "TreasuryUSDGAta…",
    "destination_token_account": "OwnerUSDGAta…",
    "amount": "99000000",
    "destination": "FFvP…owner",
    "will_create_destination_ata": false,
    "decimals": 6
  }
}
The “drain everything” variant has the same shape minus amount. It returns no_op: true when the treasury balance is zero, so you can wire it to a one-click button without a balance pre-flight:
curl -sX POST https://api.leash.market/v1/agents/$MINT/treasury/withdraw-all/prepare \
  -H "Authorization: Bearer $LSH_TEST_KEY" \
  -d '{ "payer": "...", "authority": "...", "spl_mint": "...", "destination": "..." }'

Withdraw native SOL

Drains lamports from the treasury PDA. Useful for reclaiming Genesis creator fees from an agent token launch — fees route to the same PDA as the treasury’s SPL ATAs.
curl -sX POST https://api.leash.market/v1/agents/$MINT/treasury/withdraw-sol/prepare \
  -H "Authorization: Bearer $LSH_TEST_KEY" \
  -d '{
    "payer": "FFvP…owner",
    "authority": "FFvP…owner",
    "destination": "FFvP…owner",
    "lamports": "1000000"
  }'
The “drain everything” variant accepts an optional reserve_lamports field (defaults to a tiny safety reserve so the PDA keeps enough SOL for its own future tx fees) and returns no_op: true when the balance is at or below the reserve:
curl -sX POST https://api.leash.market/v1/agents/$MINT/treasury/withdraw-sol-all/prepare \
  -H "Authorization: Bearer $LSH_TEST_KEY" \
  -d '{
    "payer": "FFvP…owner",
    "authority": "FFvP…owner",
    "destination": "FFvP…owner",
    "reserve_lamports": "100000"
  }'

Watch on the explorer

Every prepared treasury operation creates a row in the events record. There are five kinds:
  • agent.treasury.provision — emitted by provision/prepare (and by the chain indexer when an SDK drove the same flow directly).
  • agent.treasury.withdraw — emitted by withdraw/prepare and withdraw-all/prepare, plus by the indexer when the treasury PDA is the source of an SPL TransferChecked.
  • agent.treasury.withdraw_sol — emitted by withdraw-sol/prepare and withdraw-sol-all/prepare, plus by the indexer when the treasury PDA’s lamport balance drops via an Execute instruction.
  • agent.treasury.fund — emitted only by the indexer when the treasury PDA receives an SPL transfer. There is no fund/prepare endpoint because deposits are plain SPL transfers initiated by anyone (the owner topping up, an x402 payment landing, a third-party donation). The fund row is what makes those visible on the explorer feed. Detection requires the indexer to be watching the treasury’s ATA, not just the PDA — getSignaturesForAddress(pda) does not return plain TransferChecked deposits because the PDA is not in their account list. The API auto-registers each ATA the first time you hit provision/prepare, withdraw/prepare, or treasury/balances, and the indexer also self-bootstraps any un-registered ATAs at startup via getTokenAccountsByOwner. If you previously deposited before any of those calls happened, run GET /v1/agents/{mint}/treasury/balances once to backfill the watch rows.
  • agent.treasury.fund_sol — same idea for native SOL transfers landing in the treasury PDA. Sub-fee-amount wobbles (under 5 000 lamports) are filtered out as noise.
The withdraw + provision rows fire from both the API (via /prepare → /submit) and the chain indexer when matching on-chain activity is observed for a watched agent — so the explorer shows every treasury action regardless of whether the API or an SDK driver initiated it. Fund rows are indexer-only: there’s no API call that “deposits” to a treasury, and the chain is the canonical source of truth for who paid in. You can query them directly:
curl -sG https://api.leash.market/v1/events \
  -H "Authorization: Bearer $LSH_TEST_KEY" \
  --data-urlencode "kind=agent.treasury.withdraw" \
  --data-urlencode "agent=$MINT" \
  --data-urlencode "limit=5"

curl -sG https://api.leash.market/v1/events \
  -H "Authorization: Bearer $LSH_TEST_KEY" \
  --data-urlencode "kind=agent.treasury.fund" \
  --data-urlencode "agent=$MINT" \
  --data-urlencode "limit=5"

Reference scripts

The repository ships three end-to-end scripts that drive these endpoints against real devnet:
  • pnpm --filter @leash/api e2e:devnet — full payment-link / paywall / receipt flow, including a real agent.treasury.provision round-trip that exercises prepare → sign → submit → confirm. Note that this script is focused on x402 surfaces; it does not by itself generate withdraw or fund events on the explorer (see the dedicated scripts below).
  • pnpm --filter @leash/api withdraw — one-shot script that withdraws a configurable amount of USDG from a target agent treasury through the public API. See apps/api/scripts/withdraw.ts for the LEASH_WITHDRAW_* env knobs.
  • pnpm --filter @leash/api fund — one-shot script that deposits SPL (USDG by default) into a target agent treasury, then polls for the matching agent.treasury.fund event so you can verify indexer visibility end-to-end. See apps/api/scripts/fund.ts for the LEASH_FUND_* env knobs. There is no fund/prepare API endpoint — deposits are plain SPL transfers — so the script signs and broadcasts the TransferChecked directly with Umi, then nudges the API once via GET /v1/agents/{mint}/treasury/balances to make sure the ATA is on the indexer watchlist.
For the full “which endpoint surfaces which event on the explorer” matrix, see Explorer tracking.

See also