Leash settles every agent ↔ agent (or agent ↔ API) request in real SPL stablecoins on Solana (registry-backed USDC, USDT, USDG) using the x402 HTTP payment protocol and an HTTPS-compatible facilitator. This page documents the wire format, the headers, and how to swap facilitators. If you only want to fire a buyer or stand up a seller, see the SDK pages for @leash/buyer-kit and @leash/seller-kit.

The cast

RoleWhat it signsHolds funds inPays gas
Executive (Privy wallet, Node keypair, TEE)The SPL transfer (as delegate of the buyer agent treasury)Pays the buyer-side fee on the transfer
Buyer agent (Core asset)Nothing — passive ownerAsset Signer PDA → SPL token ATA
Seller (@leash/seller-kit middleware)NothingAsset Signer PDA (payTo)
Facilitator (devnet-facilitator.leash.market devnet, facilitator.leash.market mainnet, or any compatible host)NothingPays the network fee to broadcast payTo
Both parties address themselves via Core Asset Signer PDAs. Funds land in the seller agent’s treasury directly, and they leave the buyer agent’s treasury via a delegated TransferChecked — see “Agent-funded transfers” below.

Agent-funded transfers (the delegate trick)

Out-of-the-box @x402/svm’s ExactSvmScheme derives the source ATA from the signer’s wallet — it assumes the signer also owns the funds. That’s wrong for Leash agents: funds live on the agent treasury PDA, but the PDA can only sign through mpl-core::Execute, which no production facilitator wraps for you. Leash sidesteps this with a one-time delegation:
  1. Owner runs setSpendDelegation (@leash/registry-utils). It wraps an SPL Approve in mpl-core::Execute, signed by the owner; the program debits the asset signer PDA via CPI and authorises the executive as the ATA’s SPL delegate for up to delegated_amount units.
  2. Buyer-kit, when constructed with sourceTokenAccount, registers LeashDelegateExactSvmScheme (@leash/core) instead of ExactSvmScheme. The on-chain transfer is still a vanilla TransferChecked — only the source field switches from “executive’s ATA” to “agent treasury’s ATA”, and authority is the executive (signing as delegate). Every facilitator Leash tested settles it without modification.
  3. The SPL token program automatically reduces delegated_amount after each transfer; once exhausted the next call returns 402 + insufficient_funds. Re-approve to top up; revoke to lock down.
See Fund an agent for the wiring steps.

The headers

x402 uses three HTTP headers, all base64-encoded JSON:

PAYMENT-REQUIRED (server → client, 402)

The seller’s offer. Leash sends an accepts[] with one object per accepted mint on the route (primary currency plus any accepts_currencies from the payment link or createSeller route config). Each row carries the same atomic amount but a different asset mint. Example payload (decoded, two stables):
{
  "x402Version": 2,
  "accepts": [
    {
      "scheme": "exact",
      "network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
      "amount": "1000",
      "asset": "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
      "payTo": "<seller agent's Asset Signer PDA>",
      "feePayer": "devnet-facilitator.leash.market",
      "extra": { "feePayer": "<facilitator pubkey>" },
      "maxTimeoutSeconds": 30,
      "description": "Tag a payload"
    },
    {
      "scheme": "exact",
      "network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
      "amount": "1000",
      "asset": "4F6PM96JJxngmHnZLBh9n58RH4aTVNWvDs2nuwrT5BP7",
      "payTo": "<seller agent's Asset Signer PDA>",
      "feePayer": "devnet-facilitator.leash.market",
      "extra": { "feePayer": "<facilitator pubkey>" },
      "maxTimeoutSeconds": 30,
      "description": "Tag a payload"
    }
  ]
}

Buyer mint choice

@leash/buyer-kit maps preferredCurrency ('USDC' | 'USDT' | 'USDG') to a concrete mint address for the buyer’s configured cluster, then passes it to the x402 client selector as preferredAsset. The selector finds the matching accepts[i] entry by mint and pays that one. Strict, no silent fallback. If the seller’s accepts[] does not contain the chosen mint, the fetch throws with preferred_asset_unavailable: requested <mint>; seller offers <other mints> before building any transaction. The ReceiptV1 records the thrown reason. Receipt reflects the chosen mint. On a failed 402, the buyer-kit decodes the payment-required header and prefers the entry matching the buyer’s mint — so a failed USDG attempt stamps currency: "USDG" and asset: 4F6PM96… on the receipt, not the seller’s primary (often USDC). Validate the seller’s accepts[] before firing via fetchPaymentLinkMeta (@leash/core) or the discovery JSON at the link URL. Notes:
  • network is the CAIP-2 id (e.g. solana:EtWT… for devnet, solana:5eyk… for mainnet). The solana-devnet / solana-mainnet aliases used by Leash configs map to these identifiers internally.
  • amount is in the smallest unit of the SPL token at asset (USDC has 6 decimals → "1000" = 0.001 USDC).
  • payTo is the Asset Signer PDA of the seller’s Core asset, not a wallet pubkey.

PAYMENT-SIGNATURE (client → server, retry)

The buyer’s partially-signed transaction + a copy of the accepted requirement (the middleware does a deep equality check). @leash/core builds and signs this automatically via LeashDelegateExactSvmScheme (agent treasury / delegate flow) or LeashExactSvmScheme (executive’s own wallet). Both are wire-compatible with the exact scheme — the on-chain instruction is always a plain TransferChecked.

PAYMENT-RESPONSE (server → client, 200)

Settlement details emitted after the facilitator broadcasts the transfer. Includes tx_sig and payment_requirements_hash (SHA-256 of the canonical accepts[i] the buyer paid against). Leash copies both into the corresponding ReceiptV1 so explorers can chain the buyer’s spend to the seller’s earn.

Swapping facilitators

Any HTTPS x402 facilitator works. The Leash defaults:
NetworkDefault facilitatorNotes
solana-devnethttps://devnet-facilitator.leash.marketLeash-operated devnet, gas-sponsored, Token-2022 supported.
solana-mainnethttps://facilitator.leash.marketLeash-operated mainnet, gas-sponsored.
Third-party alternatives (all wire-compatible): devnet-facilitator.leash.market (devnet), facilitator.leash.market (mainnet), community hosts, self-hosted @leash/facilitator. Override per-process by setting LEASH_FACILITATOR_URL, or per-seller via facilitator: '…':
import { createSvmResourceServer } from '@leash/seller-kit/x402';

const server = createSvmResourceServer({
  networks: ['solana-mainnet'],
  facilitator: 'https://your-facilitator.example.com',
});
There is no facilitator config required on the buyer side — within the seller’s advertised accepts[], the buyer chooses which row to pay (via preferredCurrency / preferredAsset). The buyer-kit records the facilitator URL on every receipt for explorer cross-checking.

Choosing a network

AliasCAIP-2Default USDC mint
solana-mainnetsolana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdpEPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v
solana-devnetsolana:EtWTRABZaYq6iMfeYKouRu166VU2xqa14zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU
You can pass either form anywhere a network id is accepted. The playground always shows the resolved CAIP-2 id in the receipt.

Devnet faucets

Buyers need devnet SOL and whichever stable they will pay with (commonly USDC). Two faucets we recommend: Fund separate ATAs (or use the playground Provision stable ATAs) if you also pay in USDT / USDG — each mint has its own treasury ATA and delegation. The buyer playground surfaces treasury balance for the selected Pay with currency and links to faucets when it sees zero.

End-to-end flow (sequence)

Buyer                    Seller (createSeller)        Facilitator
  |   POST /tag  ──────────────────────────►               |
  |   ◄──────────  402 + PAYMENT-REQUIRED                  |
  |                                                        |
  |   pick accepts[i] by preferredCurrency / mint          |
  |   build tx: [setLimit, setPrice,                       |
  |     sellerAtaCreate, feeVaultAtaCreate?,                |
  |     TransferChecked(seller),                            |
  |     TransferChecked(fee)?, memo]                        |
  |   partial-sign (buyer delegate)                        |
  |                                                        |
  |   POST /tag (PAYMENT-SIGNATURE) ─────────────────────► |
  |               middleware → POST /verify ─────────────► walk ATAs, check legs, simulate
  |               middleware → POST /settle ─────────────► co-sign (fee payer) + broadcast
  |               ◄───────────────────── tx_sig            |
  |   ◄────  200 + body + PAYMENT-RESPONSE                 |
  |                                                        |
  | finalizeReceipt → spend ReceiptV1 (chosen mint)        |
  | (onAfterSettle on seller → earn ReceiptV1)             |
Both receipts are deterministic (SHA-256 chained, identical hash inputs on both sides) so any third party can cryptographically verify a payment just from the explorer feed.