SDK
x402 payments
x402 is an HTTP 402 flow: a server answers 402 with payment requirements, the client signs an authorization, base64-encodes it into an X-PAYMENT header, and retries. A facilitator settles the authorization on-chain. fetchWithX402 does this transparently from a session key — the agent just calls a URL.
Both supported rails verify the session key on-chain via ERC-1271:
- permit2-exact (the reliable rail — any token approved to Permit2). Includes Binance B402, which binds the recipient with a Permit2 witness (
permitWitnessTransferFrom). Checker = Permit2. - exact / EIP-3009 (the standard x402 wire). Only works for tokens whose EIP-3009 is ERC-1271-aware (Circle FiatTokenV2_2, e.g. Base/Ethereum USDC). Checker = the token.
Pay for a resource
import { createClient, BNB } from "@altananetwork/sdk";
const client = createClient({ chains: [BNB] });
// `session` from grantSession; the wallet must have approved Permit2 + the checker (below).
const res = await client.fetchWithX402({
session,
url: "https://api.example.com/paid-endpoint",
// chainId defaults to the client's default chain; override to target another.
});
console.log(res.status, await res.text()); // 200 + paid contentOne-time provisioning
Before the first payment, the wallet's admin sets up the rail:
import { PERMIT2_ADDRESS } from "@altananetwork/sdk";
// permit2-exact rail:
await client.approveTokenForPermit2({ wallet, signer: admin, token: USDC }); // ERC20 approve(Permit2)
await client.approveSignatureChecker({ wallet, signer: admin, session, checker: PERMIT2_ADDRESS });
// EIP-3009 rail (checker = the token itself):
await client.approveSignatureChecker({ wallet, signer: admin, session, checker: USDC });See approveTokenForPermit2 and approveSignatureChecker.
Parameters
client.fetchWithX402(opts: ClientFetchWithX402Options): Promise<Response>;
type ClientFetchWithX402Options = {
session: Session;
url: string;
init?: RequestInit;
/** Only pay options on this chain when any match. Defaults to the client's chain. */
chainId?: number;
/** Preferred rail when a chain offers several. Defaults to "permit2". */
preferRail?: "permit2" | "eip3009";
};Non-402 responses pass through untouched; a 402 is parsed, the best payable option is selected (preferring the requested chain, then the permit2 rail), signed, and retried.
Lower-level helpers
For servers/facilitators or custom flows, the standalone functions are also exported:
import {
fetchWithX402, // fetchWithX402(session, url, init?, { chainId, preferRail })
selectX402Requirement, // choose an option from a 402 `accepts[]`
signX402Payment, // sign one requirement → { header, payload }
buildPermit2TypedData, // plain PermitTransferFrom
buildPermit2WitnessTypedData, // permit2-exact PermitWitnessTransferFrom (B402)
buildEip3009TypedData, // TransferWithAuthorization
encodeXPaymentHeader,
networkToChainId, // CAIP-2 "eip155:56" → 56
normalizeResource, // 402 `resource` (object or URL string) → { url, ... }
PERMIT2_ADDRESS,
} from "@altananetwork/sdk";B402 wire compatibility
Real b402 merchants (CoinMarketCap and the BNB Agent Studio ecosystem) read a
few envelope fields under different names than the plain x402 sample does.
fetchWithX402 and signX402Payment emit both dialects, so the same payment is
readable by b402 merchants and by anything already integrated against Altana:
resource. The envelope carries a top-levelresourceobject saying what the payment buys, echoed from the 402 body. Merchants reject an envelope without it: CoinMarketCap answerspayment header resource is null.fetchWithX402carries it across automatically, accepting either the object form or a bare URL string, and falling back to the requested URL.permit2Authorization. Permit2 payloads carry the authorization twice: underpermitwith a siblingfrom(the Altana dialect), and underpermit2Authorizationwithfromnested inside (the b402 dialect). Same values, one signature.PAYMENT-SIGNATURE. The envelope is sent under bothX-PAYMENTandPAYMENT-SIGNATURE, since some b402 merchants read only the latter.
When passing a requirement to signX402Payment yourself, set resource on it
so the envelope carries one.
Notes
- Browser limitation. Third-party x402 endpoints often omit
X-PAYMENTfrom CORSAccess-Control-Allow-Headers, so a browser can't POST the payment. RunfetchWithX402server-side. - The signature is a 98-byte ERC-1271 envelope, not an EOA signature — see Off-chain signatures. A facilitator must verify via
isValidSignature.