| # | Instruction | Who pays rent |
|---|---|---|
| 0 | SetComputeUnitLimit | — |
| 1 | SetComputeUnitPrice | — |
| 2 | CreateAssociatedTokenAccountIdempotent — seller’s payTo ATA | Facilitator |
| 3 | CreateAssociatedTokenAccountIdempotent — fee vault ATA (only when a fee leg is present) | Facilitator |
| 4 | TransferChecked(mint, amount) — seller leg | Buyer delegate |
| 5 | TransferChecked(mint, fee) — protocol fee leg (only when extra['leash.fee'] is present) | Buyer delegate |
| 6 | Memo | — |
TransferChecked legs share the same buyer signature, blockhash,
and tx_sig. Either both land or neither does.
This is the only economic hook in the entire stack. There is no
per-call subscription, no facilitator markup, no per-agent
infra fee, and no spread on simulation. The 1% fee leg is the
revenue model for the hosted Leash facilitators, and the same primitive
anyone running @leash/facilitator can opt into.
The numbers
| Field | Value | Where it lives |
|---|---|---|
| Rate | 100 bps (1.00%) | LEASH_FEE_BPS env on facilitator + API |
| Rounding | Ceiling, in atomic units | computeFeeAtoms in @leash/core/fees |
| Mainnet authority | 3DdcJkvjW7KLtMeko3Zr57jEJWhqRHuPsEBFm1XJYh7W | LEASH_FEE_AUTHORITY_MAINNET (override only if self-hosting your own facilitator) |
| Devnet authority | 3DdcJkvjW7KLtMeko3Zr57jEJWhqRHuPsEBFm1XJYh7W | LEASH_FEE_AUTHORITY_DEVNET |
| Live snapshot | GET /v1/health .protocol_fee block | API + @leash/facilitator /health |
protocol_fee block on /health.
Math worked example
A buyer calls a0.012345 USDC endpoint (atoms 12345, USDC has 6
decimals):
TransferChecked
instructions: 12 345 atoms to the seller, 124 atoms to
Leash-fee-authority's USDC ATA. The seller’s receipt records:
spend receipt carries the same price block (modulo
kind: 'spend').
Wire shape — extra['leash.fee']
Sellers advertise the fee rate in the 402’s accepts[] so buyers can
verify it before signing. The shape is intentionally minimal — the
dynamic fields (feeAtomic, grossAtomic, feeDestination) are
derived on demand by both sides from the static fields below, so
the buyer and facilitator always agree on the destination ATA without
having to coordinate ahead of time.
parseLeashFeeExtra from @leash/core and resolve
the per-request triple (bps, feeAtomic, feeDestination) via
computeLeashFeeForRequirements. Facilitators run the same
function over the same inputs to verify the buyer’s transaction
matches.
If extra['leash.fee'] is absent, both sides treat the settlement
as vanilla x402 (single-leg, no fee enforcement). This keeps non-Leash
sellers and non-Leash facilitators interoperable.
Verifying the live rate
Before any UI signs a buyer’s wallet, fetch the current rate from the facilitator (or the API) — the rate is one source of truth, surfaced identically on both:TransferChecked if the buyer-side computed feeAuthority disagrees
with what the facilitator publishes.
Budgeting allowances and treasuries
Because the buyer signsgross, every spend allowance and every
treasury balance has to be sized in gross, not amount. The two
common foot-guns:
- SPL
Approve(executive allowance). A5 USDCApprove only buys~4.95 USDCof seller-quoted endpoints — the last fee leg pushes you over the cap and the settlement fails withinsufficient_allowance. Either gross-up yourself withapplyFeeGrossUpfrom@leash/core, or passpad_for_protocol_fee: trueonPOST /v1/agents/{mint}/delegation/prepare— the response echoesfee_padding_atomsso you can audit. - Treasury balance. Same reasoning at the SPL balance level. Top
up
5.05 USDC(atoms5_050_000) if you want to comfortably consume5 USDCof seller-quoted endpoints. The web playground’s “Provision stable ATAs” + “Set allowance” buttons both do the gross-up automatically.
What the facilitator does
@leash/facilitator registers two custom schemes —
LeashExactSvmFacilitator (x402 v2) and LeashExactSvmFacilitatorV1
(legacy v1) — instead of the upstream @x402/svm defaults. On every
POST /verify call they:
- Walk past leading
CreateAssociatedTokenAccountIdempotentinstructions (up to two — one for the seller’spayToATA, one for the fee vault ATA). Each is validated: payer must be the facilitator’s fee-payer, mint and token program must match the transfer leg, and each target ATA must be either the seller’spayToor the fee authority’s ATA for the same mint. Unknown ATA creates are rejected. - Identify the seller
TransferCheckedimmediately after any ATA creates. Confirm it debits the buyer’s source ATA and credits the seller’spayToATA for the advertisedamounton the advertisedmint. - If
extra['leash.fee']is present, confirm a secondTransferCheckedfollows: same source ATA, same mint, targets thefeeAuthority’s ATA, exactfeeAtomicas derived fromamount × bps. - Reject (
enforce), warn (warn), or ignore (off) perLEASH_FEE_ENFORCE. The hosted facilitators runenforce;warnis for staged rollouts. - Simulate the fully co-signed transaction against the RPC to catch balance / blockhash problems before broadcast.
/settle) adds the facilitator’s signature as fee payer and
broadcasts. The facilitator pays all SOL transaction fees and ATA rent — buyers
only spend stables.
What the indexer does
Every Leash fee ATA on every supported stable mint is added to the indexer’s watchlist at startup (seedLeashFeeWatchlist in
apps/api/src/indexer/leash-fee-watchlist.ts, kind leash_fee_ata).
Two paths emit protocol.fee.collected events:
- Receipt-side. Whenever a settled
earnreceipt withprice.feeis ingested viaPOST /v1/receipts/{agent},/x/{id}(paywall), or the receipt-pull worker,emitProtocolFeeEventfires once, idempotent on(network, receipt_hash). - On-chain fallback. When the indexer sees a positive
tokenBalanceDeltasto the fee authority on a watched ATA, it emits the same event withmetadata.source = 'on_chain'. This catches the rare case of a fee landing without a receipt (e.g. a buyer that never POSTed the spend receipt).
Net (seller) / Protocol fee / Gross (buyer) / Fee authority.
Self-hosting @leash/facilitator with your own fee
The fee primitive is generic — anyone running their own facilitator can
charge their own protocol fee against their own treasury, or disable
the fee entirely while still benefiting from the rest of the
verification stack:
| Env | Default | Effect |
|---|---|---|
LEASH_FEE_BPS | 100 | Fee rate in basis points. Set to 0 to disable. |
LEASH_FEE_ENFORCE | enforce | enforce rejects bad/missing fee legs; warn logs them; off skips verification. |
LEASH_FEE_AUTHORITY_MAINNET | 3DdcJkvjW7KLtMeko3Zr57jEJWhqRHuPsEBFm1XJYh7W | Pubkey that receives the mainnet fee leg. |
LEASH_FEE_AUTHORITY_DEVNET | 3DdcJkvjW7KLtMeko3Zr57jEJWhqRHuPsEBFm1XJYh7W | Same, for devnet. |
@leash/facilitator with the env you
want, point sellers at LEASH_FACILITATOR_URL=https://your-host, and
the chain of effects above (sellers stamp your feeAuthority →
buyers verify against your /health → indexer watchlists your ATAs
→ explorer aggregates) all wires up automatically.
SDK reference
resolveLeashFeeBps()— current fee rate (env-overridable).resolveLeashFeeAuthority(network)— fee owner pubkey for'mainnet' | 'devnet'.computeFeeAtoms(amount, bps)— pure, ceiling-rounded.applyFeeGrossUp(amount, bps?)— returns{ fee, gross }.buildLeashFeeExtra({ network, bps?, authority? })— synchronous, returns the wire shape sellers stamp onextra['leash.fee'].parseLeashFeeExtra(extra)— parses the wire shape, returnsnullon absence/malformation.computeLeashFeeForRequirements({ network, asset, tokenProgram, amount, extra })— async, derives(bps, feeAtomic, grossAtomic, feeDestination)for a specificpaymentRequirementsentry. Used by both buyer scheme and facilitator verifier.
@solana-program/token / @solana-program/token-2022), so you can
unit-test integrations without booting any Solana RPC.
See also
- Run a facilitator — boot your own and configure the fee env.
- Monetise an API — where the fee fits in the end-to-end seller flow.
- Explorer tracking — how
protocol.fee.collectedevents surface in the UI. - Receipt format — the canonical
price.fee/price.gross/price.feeBps/price.feeAuthorityfields on everyReceiptV1.

