@leash/core is the dependency-light primitive used by both the buyer and seller kits. It contains:
  • Agent + treasurytreasuryPda(asset) derives the Asset Signer PDA used as payTo and the on-chain owner of the agent’s funds. deriveAgentTreasuryAddress(asset) and deriveAgentTreasuryAta({ asset, mint }) do the same derivations using @solana/kit only (no Umi runtime), so buyer/seller code can resolve the treasury + token ATA without dragging in Metaplex’s Umi context.
  • Policyevaluate(rules, request, ledger, pause) returns a pure decision (allow / deny + reason). Buyer/seller kits also stamp rejected on receipts when the gate allowed a call but the payment did not settle.
  • Networkscaip2ForNetwork('solana-devnet') returns the wire-level CAIP-2 chain id; the inverse networkFromCaip2('solana:EtWTRABZ…') returns the friendly Leash slug used on receipts.
  • HashingcanonicalJson, sha256Hex, requestHash, paymentRequirementsHash, computeReceiptHash, finalizeReceipt, verifyReceiptChain. All hashing uses @noble/hashes so it runs in any JS environment (Node, browser, edge).
  • x402 client adaptercreateSvmBuyerFetch({ signer, networks, rpcUrl, preferredAsset?, sourceTokenAccount? }) wraps wrapFetchWithPayment from @x402/fetch with ExactSvmScheme (or LeashDelegateExactSvmScheme when sourceTokenAccount is set) and a ClientSvmSigner. Optional preferredAsset (SPL mint string) selects which accepts[] row to pay when the seller lists several stables; if set and no row matches, the client throws (preferred_asset_unavailable) instead of silently paying a different mint. @leash/buyer-kit wires this via preferredCurrency (USDC / USDT / USDG).
  • Payment-link discoveryfetchPaymentLinkMeta() fetches and type-checks the browser-friendly GET /x/<id> metadata payload exposed by the web app; buildPaymentLinkMeta() produces it on the server.
  • Payment envelopebuildLeashEnvelope(receipt, { origin }) produces the compact LeashPaymentEnvelope that gets stamped onto webhooks, response headers, and wrap_receipt bodies after a settled payment.
  • X-Leash-* headersbuildLeashHeaders(envelope, headers) and parseLeashHeaders(response) round-trip the envelope through HTTP responses without sellers / buyers misspelling header names. LEASH_HEADERS is the canonical name map; LEASH_CALLBACK_HEADER is the per-call webhook opt-in (x-leash-callback).
  • Webhook payloadbuildWebhookPayload({ envelope, response }) and parseWebhookPayload(json) produce / validate the JSON body Leash POSTs to endpoint.webhook_url. Versioned (v: '0.1') and discriminated (kind: 'leash.payment.settled') so downstream agents can route safely.
  • Token registryKNOWN_TOKENS, lookupToken(mint, network), lookupTokenBySymbol(symbol, network), KNOWN_STABLE_SYMBOLS, currencyForAsset(mint, network), pinnedMints(network), defaultUsdcMint(network). Stables on mainnet + devnet plus a few bluechips, indexed for UI surfaces.
  • Format helpersatomicToDecimal, decimalToAtomic, formatTokenBalance, formatReceiptPrice, formatReceiptPriceUsd. Use these instead of hand-rolled *1_000_000 math; they pull decimals from the registry so Token-2022 stables don’t silently truncate.
  • Explorer linkstransactionExplorerUrl, addressExplorerUrl, agentExplorerUrl. Solscan by default; pass { provider: 'solana-explorer' } for the official explorer.
  • Treasury balanceslistSplBalances({ owner, rpcUrl, network }) enumerates SPL + Token-2022 balances and pins missing stables with zero so UIs always render them.
  • Kill switchreadPauseFromEnv, createPauseResolver. The buyer’s policy gate honours these so a single env flip (or admin RPC) can halt every spend immediately.
  • Treasury helpersgetTreasuryBalance, buildWithdrawTx (stub for v0.1).

Agent treasury PDA

Resolve an agent’s on-chain treasury (Asset Signer PDA) and its token ATA without dragging in the Umi runtime. Useful inside @leash/buyer-kit’s auto-preflight and for any caller that needs to sign on behalf of an agent with @solana/kit only.
import { deriveAgentTreasuryAddress, deriveAgentTreasuryAta } from '@leash/core';

const treasury = await deriveAgentTreasuryAddress('33QvAYjEiK8UMrmpy3LW6W8v2wpPMahnw7Jvr7JpeQrR');

// Resolve the USDC ATA the agent treasury would receive into.
const { treasury: t, ata } = await deriveAgentTreasuryAta({
  asset: '33QvAYjEiK8UMrmpy3LW6W8v2wpPMahnw7Jvr7JpeQrR',
  mint: '4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU', // USDC devnet
});
The seeds (["mpl-core-execute", asset]) and program id (CoREENxT6tW1HoK8ypY1SxRMZTcVPm7R94rH4PZNhX7d) match Metaplex Core’s findAssetSignerPda exactly. Pass { tokenProgram: TOKEN_2022_PROGRAM_ADDRESS } for Token-2022 mints; the default is the legacy SPL Token program.

Why a separate adapter?

@x402/fetch is transport-agnostic — it doesn’t know which chain or scheme the seller wants. We register ExactSvmScheme once, point it at the user’s RPC, and reuse the same paidFetch for every buyer call. The adapter is also where we set the default network list (solana-devnet, solana-mainnet).
import { createSvmBuyerFetch } from '@leash/core/x402';

const paidFetch = createSvmBuyerFetch({
  signer, // @solana/kit TransactionPartialSigner
  networks: ['solana-devnet'],
  rpcUrl: 'https://api.devnet.solana.com',
  // Optional: SPL mint to prefer when the seller sends multiple `accepts[]`.
  // Must match one of those rows or the next paid fetch throws.
  // preferredAsset: '4F6PM96JJxngmHnZLBh9n58RH4aTVNWvDs2nuwrT5BP7', // devnet USDG
});

const res = await paidFetch('https://api.example.com/quote');
import { fetchPaymentLinkMeta } from '@leash/core';

// Variant 1: full shareable URL
const metaA = await fetchPaymentLinkMeta('https://yourapp.com/x/uhnb6e1zm');

// Variant 2: base host + endpoint id
const metaB = await fetchPaymentLinkMeta('https://yourapp.com', 'uhnb6e1zm');

console.log(metaA.endpoint.price); // "$0.001"
console.log(metaA.endpoint.currency); // "USDC"
console.log(metaA.endpoint.accepts_currencies); // e.g. ["USDG"]
console.log(metaB.endpoint.method); // "POST"
Discovery JSON includes endpoint.currency (primary settlement ticker) and endpoint.accepts_currencies (extra stables the seller added). The playground buyer uses these to pre-check that Pay with matches what the link accepts.

Settlement envelope, headers, and webhooks

After a successful x402 settlement the seller /x/<id> route stamps a compact summary onto the response. Producers + consumers share one type:
import {
  buildLeashEnvelope,
  buildLeashHeaders,
  parseLeashHeaders,
  buildWebhookPayload,
  parseWebhookPayload,
  LEASH_HEADERS,
  LEASH_CALLBACK_HEADER,
} from '@leash/core';

// SELLER side: stamp envelope onto response headers + ship a webhook.
const envelope = buildLeashEnvelope(earnReceipt, { origin: 'https://app.example.com' });
const headers = buildLeashHeaders(envelope, new Headers(res.headers));
const body = JSON.stringify(buildWebhookPayload({ envelope, response: parsedBody }));

// BUYER side: parse the headers off the response without misspelling names.
const parsed = parseLeashHeaders(response);
console.log(parsed.txSig, parsed.receiptHash);

// Per-call webhook opt-in: buyers attach `x-leash-callback: <url>` and the
// seller delivers the same payload to that URL in addition to its own
// `endpoint.webhook_url`.
fetch(url, { headers: { [LEASH_CALLBACK_HEADER]: 'https://my-agent.example/webhook' } });
import {
  KNOWN_TOKENS,
  defaultUsdcMint,
  formatReceiptPriceUsd,
  formatTokenBalance,
  lookupToken,
  listSplBalances,
  transactionExplorerUrl,
} from '@leash/core';

const usdc = defaultUsdcMint('devnet'); // canonical mint
const meta = lookupToken(usdc.mint, 'devnet'); // { decimals: 6, ... }

formatTokenBalance('1500000', usdc.mint, 'devnet'); // "1.5"
formatReceiptPriceUsd({ amount: '2', currency: 'USDC' }); // "$0.000002"

const balances = await listSplBalances({
  owner: 'CTd5VBFYJnGDGv5DbhWfPmrQ96G5ibvmZiyRPURXNyox',
  rpcUrl: 'https://api.devnet.solana.com',
  network: 'devnet',
});

transactionExplorerUrl('5sig', { network: 'devnet' });
// 'https://solscan.io/tx/5sig?cluster=devnet'
See Real x402 on Solana for the protocol walkthrough and a deeper dive on the headers (PAYMENT-REQUIRED, PAYMENT-SIGNATURE, PAYMENT-RESPONSE).