The agent owner is sovereign. Withdraws bypass the executive delegation entirely — the owner signs onempl-core::Execute(...)that CPI-signs the transfer on behalf of the agent’s Asset Signer PDA. v0.2 ships the SPL stable path in@leash/registry-utils; v0.2.1 adds the native-SOL path so creator fees from a Genesis token launch can be drained the same way.
What withdrawing actually does
The agent treasury is owned on-chain by the Asset Signer PDA derived from the agent’s Core asset. Only the asset’sowner field can authorise
an mpl-core::Execute instruction, and Execute is the only path that
lets a CPI signer act on behalf of the PDA.
Concretely, every withdraw is one transaction with a single instruction:
TransferChecked (discriminator 12) is preferred over plain Transfer
because Token-2022 mints with a transfer-fee extension reject the legacy
opcode. Same wire-shape works for both classic SPL Token and Token-2022
mints, so a USDC withdraw today and a future Token-2022 stable withdraw
share one code path.
If the destination wallet doesn’t have an ATA for the mint yet, the
helper bundles a CreateIdempotent in front (the executive pays the
~2k lamport rent). That means “send to a fresh wallet” Just Works — no
“create your ATA first” round-trip.
SDK usage
umi.identity by default. In the playground
that’s the connected Privy embedded wallet — pass authority /
payer overrides explicitly if you need them split.
Returned shape
Reading the balance first (UI gating)
Withdrawing native SOL (Genesis creator fees)
Agent token launches via Metaplex Genesis route creator fees as raw lamports to the same Asset Signer PDA that holds the treasury’s SPL stables. To withdraw those lamports we need a path that doesn’t rely on SPL —withdrawTreasurySol{,All} wraps a hand-rolled
SystemProgram.Transfer in mpl-core::Execute so the PDA can sign via
CPI exactly the way the SPL flow does.
Via the Leash HTTP API
Every SDK helper above has a 1:1 HTTP equivalent onapi.leash.market — useful when the signer
lives somewhere the TypeScript SDK can’t reach (Python service, Privy
embedded wallet, TEE, hardware key behind a bastion). The API never
sees the secret material; it only builds the unsigned transaction,
returns it, and broadcasts the signed bytes you hand back.
withdraw-all/prepare,
withdraw-sol/prepare, and withdraw-sol-all/prepare. The full
treasury surface (request fields, response shape, no-op semantics, and
the matching agent.treasury.* event kinds) is documented in the API
reference at Treasury endpoints. Two reference scripts
live at apps/api/scripts/:
pnpm --filter @leash/api e2e:devnet— drives the full payment-link flow plus a realagent.treasury.provisionround-trip.pnpm --filter @leash/api withdraw— withdraws a configurable amount of USDG from a target agent through the API; defaults exactly match this guide’s99 USDGexample.
The web playground flow
The agent detail page (/agents/<mint>) ships a Withdraw treasury
card right under the spend allowance section.
- Currency dropdown lets the owner pick any stablecoin in
KNOWN_STABLE_SYMBOLS(USDC, USDT, USDG) orSOL (native)for Genesis creator fees. The selected token’s mint, decimals, and program (classic SPL Token vs Token-2022 vs native System) are wired through to the SDK automatically — no separate UI for Token-2022 mints, and no separate “withdraw SOL” card. Picking SOL hides the ATA copy in the destination hint (System transfers don’t need one). - Destination input defaults to the connected owner wallet. Edit it to any valid Solana address; the UI flips to a yellow “external” badge and requires you to tick a confirmation box before the button unlocks. This is the rescue path for compromised executives — withdraw to a fresh wallet you control before rotating.
- Amount in the selected token (decimal). The badge below shows the current treasury balance for that token, so “what’s the max I can pull” is a glance away. Switching the currency dropdown updates both the input symbol and the available-balance pill in place.
- Withdraw all reads the on-chain balance and drains it in one tx. Disabled when the treasury is at zero for the selected token.
Withdraw vs revoke
These solve different problems and you usually want both:| Action | What it does | When to use |
|---|---|---|
revokeSpendDelegation | Drops the executive’s SPL delegation. Funds stay on the treasury PDA. | Lock down spending without moving money. |
withdrawTreasury | Moves stablecoins out of the treasury to a destination wallet. | Agent retired; you want the SPL stables back. |
withdrawTreasurySol | Moves native SOL out of the treasury to a destination wallet. | Drain Genesis creator fees / reclaim rent. |
What’s not covered yet
- Closing empty ATAs. The treasury holds onto its empty ATAs after a
full withdraw (~2k lamports of rent each). Closing requires a
separate
mpl-core::Execute(SPL.CloseAccount)— coming in v0.3. - Owner transfer. This guide assumes the asset owner stays the same
wallet. Transferring ownership of the Core asset is a separate
mpl-core::Transferflow — see the Metaplex Core docs.

