Skip to content

Pay any x402 server (the exact rail)

By default a PipRailClient pays only PipRail’s native onchain-proof rail — the backendless scheme where the client pays first and proves it with a tx ref. That covers every PipRail gate, but most of the public x402 web (the dominant exact-on-Base flow) speaks the ratified exact scheme instead. Opt into it and the same client can pay any standard x402 server.

import { PipRailClient } from '@piprail/sdk'
const client = new PipRailClient({
chain: 'base',
wallet: { key: process.env.AGENT_KEY! },
schemes: ['onchain-proof', 'exact'], // pay PipRail rails AND standard exact rails
})

With onchain-proof, the client broadcasts the payment itself and proves it. With exact, the buyer signs with its own wallet and someone else broadcasts it — the merchant’s relayer, or a merchant-chosen facilitator (keyless on EVM EIP-3009, Solana, and Algorand). So the buyer spends roughly zero gas — only the token funds the payment — and PipRail hosts and settles nothing. The buyer is gasless either way: how the merchant settles (its own relayer vs a facilitator) is the merchant’s call and invisible to the buyer. When the merchant points settlement at a free facilitator like PayAI, no one runs a gas-funded key at all — settlement is fully gasless end to end (see Gasless payments).

onchain-proof (default)exact (opt-in)
Who broadcastsThe clientThe server / facilitator
Buyer pays gasYes (native coin)No (~0)
Pays which serversPipRail gatesAny standard x402 server
ProofTx ref, verified locallyA signed EIP-3009 authorization, a Permit2 witness, a partial-signed Solana transaction, an Algorand fee-pooled ASA group, an Aptos sponsored (fee-payer) transaction, or a NEAR NEP-366 SignedDelegateAction

The exact rail works on EVM, Solana, Algorand, Aptos, and NEAR, via one of six on-chain methods. The 402’s rail names which one (extra.assetTransferMethod), and the client picks the matching signer automatically:

  • eip3009 (EVM) — canonical USDC/EURC and other tokens exposing transferWithAuthorization. The client re-derives the token’s EIP-712 domain on-chain before signing, so a lying or absent server-supplied domain can’t produce a silently-invalid signature. Fully gasless for the buyer.
  • permit2 (EVM) — any ERC-20 without EIP-3009, most notably Binance-Peg USDC/USDT on BNB Chain (no native Circle USDC exists on BNB). The client signs a Permit2 PermitWitnessTransferFrom whose spender is the canonical x402ExactPermit2Proxy and whose witness.to binds the recipient (so a relayer can’t redirect funds). Gasless per-payment too — after a one-time approve(Permit2) the SDK does lazily the first time you pay that token.
  • svm (Solana) — any SPL token (USDC, USDT, …). The client builds the SPL TransferChecked with the merchant as the transaction fee payer, adds a spec-required SPL-Memo instruction (the rail’s extra.memo, else a random hex nonce) for transaction uniqueness, signs only its own slot, and sends the partially-signed transaction; the gate co-signs as fee payer and broadcasts. No EIP-3009 equivalent, no proxy, no approval — gasless for the buyer regardless of token. See Gasless payments.
  • algorand (Algorand) — any ASA (USDCa, …). The client signs an ASA transfer at fee 0, atomically grouped with the sponsor’s fee-pooling pay; the sponsor signs that fee txn and submits the group. No token feature required — gasless for the buyer regardless of token.
  • aptos (Aptos) — any Fungible Asset (USDC, USD₮, …). The client signs a fee-payer (sponsored, AIP-39) primary_fungible_store::transfer (sender slot only); the sponsor adds the fee-payer signature and submits. No token feature required — gasless for the buyer regardless of token.
  • near (NEAR) — any NEP-141 token (USDC, USDT). The client signs a NEP-366 SignedDelegateAction with its full-access key authorizing exactly one ft_transfer to payTo (the exact amount, deposit: 1 yoctoNEAR, fixed 30 TGas); the merchant’s relayer wraps it in its own outer transaction, prepays the gas and the yocto, and submits. The buyer holds zero NEAR — gasless regardless of token. Self-settle only today (no third-party NEAR x402 facilitator settles yet — see Gasless payments).
Works on exactStays on onchain-proof
EVM EIP-3009 (USDC / EURC; FDUSD, USD1 & U on BNB)The other non-EVM families (TON, Tron, Sui, Stellar, XRPL)
EVM Permit2 — any ERC-20 (e.g. Binance-Peg USDC on BNB)The chain’s native coin (incl. SOL, ALGO, APT, NEAR)
Solana SVM — any SPL token (USDC / USDT)A contract / EIP-1271 / EIP-7702 signer (EVM)
Algorand ASA (USDCa) · Aptos FA (USDC / USD₮) · NEAR NEP-141 (USDC / USDT, via NEP-366 meta-tx)

An exact rail is selected only when the 402 names a network your bound chain supports — the client matches each offered rail against its own chain via the driver (matching the network whether it’s a CAIP-2 id or a chain slug — see Interoperability below) and settles on that chain. So an EIP-3009/Permit2 rail on your bound EVM chain, or an SVM rail on a Solana-bound client, is payable; an exact rail naming a different chain (or a family without an exact scheme) simply isn’t selected and falls back to onchain-proof.

The exact rail is the standard x402 scheme, so a PipRail client interoperates with the wider x402 ecosystem out of the box — any server or facilitator that speaks exact, however it labels the network. Before matching a rail to your bound chain the client normalizes the rail’s network, so a 402 that names the chain as a CAIP-2 id (eip155:8453) or a chain slug (base, bsc, polygon) is matched and paid identically. You don’t have to know, or configure, which form a given facilitator emits — both resolve to the same chain.

This matters because facilitators in the wild are inconsistent: the same endpoint may advertise a rail as eip155:56 in one place and bsc (or 56) in another. PipRail pays all of them. A label that resolves to a different chain than the one you’re bound to — or an unrecognized one — is simply not selected, never mis-paid; the trusted EIP-712 domain (which fixes the chain id at signing time) is the final guard regardless of the label.

When you enable both schemes, the client gathers onchain-proof rails first, so on a dual-rail 402 the default selection is unchanged. An exact rail is only ever picked when the bound driver can actually settle it (EVM EIP-3009/Permit2, Solana SVM, the Algorand ASA rail, the Aptos FA rail, or the NEAR NEP-366 meta-tx).

To make the client prefer the gasless exact rail when a gate offers both, enable autoRoute (new PipRailClient({ …, autoRoute: true }), or per call fetch(url, { autoRoute: true })): it pays the cheapest settleable rail, and since the buyer-gasless exact rail estimates at ~0 gas, it wins automatically. Without autoRoute the dual-rail default stays onchain-proof; a foreign exact-only server is paid over exact either way.

Once a scheme is enabled, paying is the same call as ever — fetch/get/post handle the 402 transparently and pick the right path per rail:

const res = await client.get('https://api.example.com/report')
const data = await res.json()
// → the gated JSON, paid for via exact (or onchain-proof) transparently

Your spend policy and onBeforePay hook gate an exact payment before the wallet signs anything — exactly as they gate an onchain-proof payment.

You can leave the constructor on the default and flip schemes for a single request, overriding the constructor’s schemes for that call:

const url = 'https://api.example.com/report'
await client.fetch(url, { schemes: ['exact'] })

planPayment() and quote() honour the enabled schemes. On an exact rail, only the token balance gates payability (the buyer spends no gas), so an INSUFFICIENT_GAS blocker never applies and gas-basis warnings are suppressed.

const url = 'https://api.example.com/report'
const plan = await client.planPayment(url) // analyses exact rails when enabled
if (!plan) {
await client.fetch(url) // not gated — fetch it for free
} else if (plan.payable) {
await client.fetch(url, { autoRoute: true })
} else {
console.log(plan.fundingHint) // one-line, human-readable: what to top up
}

planPayment() returns null when the URL isn’t payment-gated, so null-guard it before reading payable.

If a 402 offers only an exact rail and the bound family can’t pay it — a family without an exact scheme (TON, Tron, Sui, Stellar, XRPL), the chain’s native coin (incl. SOL, ALGO, APT, NEAR), or a contract / EIP-1271 / EIP-7702 signer — the client throws UnsupportedSchemeError (.code === 'UNSUPPORTED_SCHEME') rather than signing something that can’t settle. (A non-EIP-3009 ERC-20 is not in this list — it pays via Permit2; nor is an SPL token on Solana — it pays via SVM.)

import { PipRailClient, UnsupportedSchemeError } from '@piprail/sdk'
const url = 'https://api.example.com/report'
try {
await client.fetch(url, { schemes: ['exact'] })
} catch (err) {
if (err instanceof UnsupportedSchemeError) {
// this chain/asset/signer can't pay the exact rail — fall back to onchain-proof
console.error(err.message)
} else {
throw err
}
}

The exact pay path is deliberately more conservative than the onchain-proof retry loop: the buyer signs once and the same header is re-presented on every retry — it never re-signs a fresh nonce.

  • A transport error or timeout after the authorization is submitted throws PaymentTimeoutError carrying the nonce as .ref — the facilitator may have already settled, so verify on-chain and never re-pay.
  • A definitive facilitator rejection (success: false) throws MaxRetriesExceededError — fix the cause, then re-present the same signed authorization, never a fresh one.
  • A 5xx is returned as-is: a server-side settle failure leaves your authorization valid and its nonce unused, so nothing is recorded as spent.

On an exact rail, the .ref carried by PaymentTimeoutError / MaxRetriesExceededError is the authorization nonce — the EIP-3009 nonce (a 0x… 32-byte value) or, on the Permit2 method, the Permit2 nonce (a uint256). It is not a tx hash. Recover by checking the nonce’s on-chain state (EIP-3009 authorizationState(from, nonce), or the Permit2 nonce bitmap) and re-presenting the same authorization — never re-sign:

On the non-EVM exact rails the .ref is likewise the family’s single-use marker, recovered differently: Solana — the buyer’s transaction signature (a duplicate signature is the chain’s replay guard; plus the SPL-Memo nonce when present); Algorand — the atomic group / transaction id; Aptos — the sender’s account sequence number; NEAR — the access-key nonce (carried as accountId:nonce). The discipline is identical: verify the marker on-chain, re-present the same signed payload, never re-sign and never re-pay.

import { PaymentTimeoutError, MaxRetriesExceededError } from '@piprail/sdk'
const url = 'https://api.example.com/report'
try {
await client.fetch(url, { schemes: ['exact'] })
} catch (err) {
if (err instanceof PaymentTimeoutError || err instanceof MaxRetriesExceededError) {
// .ref exists ONLY on these two classes — the EIP-3009 nonce on the exact rail
console.log('recover with this authorization nonce, do NOT re-pay:', err.ref)
} else {
throw err
}
}