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:
| Endpoint | Method | Purpose |
|---|---|---|
GET /v1/agents/{mint}/treasury/balances | read | Native SOL + every SPL token sitting on the PDA right now. |
POST /v1/agents/{mint}/treasury/provision/prepare | write | Pre-create the curated stable ATAs (USDC / USDT / USDG). |
POST /v1/agents/{mint}/treasury/withdraw/prepare | write | Owner moves a specific atomic amount of one SPL mint out. |
POST /v1/agents/{mint}/treasury/withdraw-all/prepare | write | Same shape; drains the full balance (or no_op when zero). |
POST /v1/agents/{mint}/treasury/withdraw-sol/prepare | write | Owner moves a specific lamport amount of native SOL out. |
POST /v1/agents/{mint}/treasury/withdraw-sol-all/prepare | write | Same shape; drains all SOL above a configurable reserve. |
POST /v1/submit.
Read balances
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 isno_op: true and no transaction is constructed.
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":
transaction.base64 (or transaction.message_base64, see
Prepare → Submit) with the owner key, then post the
signed bytes back to POST /v1/submit:
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).
| Field | Required | Notes |
|---|---|---|
payer | yes | Wallet that pays SOL fees + ATA rent. Usually the owner. |
authority | yes | Asset owner. Only the owner can authorise mpl-core::Execute. |
spl_mint | yes | The SPL mint to debit (USDC / USDT / USDG / arbitrary). |
destination | yes | Receiving wallet (its ATA is derived; no need to pre-create). |
amount | yes | Atomic units (USDG has 6 decimals, so 99000000 = 99 USDG). |
token_program | no | "spl" (default) or "token-2022". USDG requires "token-2022". |
decimals | no | Pre-fetched mint decimals — saves an RPC roundtrip in the API. |
create_destination_ata_if_missing | no | Defaults to true. Set false to fail loudly when the destination has no ATA yet. |
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:
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.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:
Watch on the explorer
Every prepared treasury operation creates a row in theevents record.
There are five kinds:
-
agent.treasury.provision— emitted byprovision/prepare(and by the chain indexer when an SDK drove the same flow directly). -
agent.treasury.withdraw— emitted bywithdraw/prepareandwithdraw-all/prepare, plus by the indexer when the treasury PDA is the source of an SPLTransferChecked. -
agent.treasury.withdraw_sol— emitted bywithdraw-sol/prepareandwithdraw-sol-all/prepare, plus by the indexer when the treasury PDA’s lamport balance drops via anExecuteinstruction. -
agent.treasury.fund— emitted only by the indexer when the treasury PDA receives an SPL transfer. There is nofund/prepareendpoint 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 plainTransferCheckeddeposits because the PDA is not in their account list. The API auto-registers each ATA the first time you hitprovision/prepare,withdraw/prepare, ortreasury/balances, and the indexer also self-bootstraps any un-registered ATAs at startup viagetTokenAccountsByOwner. If you previously deposited before any of those calls happened, runGET /v1/agents/{mint}/treasury/balancesonce 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.
/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:
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 realagent.treasury.provisionround-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. Seeapps/api/scripts/withdraw.tsfor theLEASH_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 matchingagent.treasury.fundevent so you can verify indexer visibility end-to-end. Seeapps/api/scripts/fund.tsfor theLEASH_FUND_*env knobs. There is nofund/prepareAPI endpoint — deposits are plain SPL transfers — so the script signs and broadcasts theTransferCheckeddirectly with Umi, then nudges the API once viaGET /v1/agents/{mint}/treasury/balancesto make sure the ATA is on the indexer watchlist.
See also
- Treasury concept — what the Asset Signer PDA is and why withdraws bypass the executive delegation.
- Withdraw treasury funds (guide) — SDK-side walkthrough plus the playground UI flow.
@leash/registry-utils— the same primitives the API wraps; useful when you want client-side custody with no remote dependency.

