/** * The on-the-wire payment protocol. Self-contained — it runs entirely * between the agent and your server, with nothing hosted in between. * * Lowercase headers, no `X-` prefix: * - payment-required (server → client, base64 JSON challenge) * - payment-signature (client → server, base64 JSON proof) * - payment-response (server → client, base64 JSON receipt on 200) * * Scheme is `onchain-proof`: the agent pays on-chain and hands back a proof * reference (an EVM tx hash, a Solana signature, …); the server verifies * that transaction itself, locally, against its own RPC. No third party. * * This file is CHAIN-AGNOSTIC. Identifiers are plain strings in CAIP-2 / * base-unit form so any family (EVM, Solana, …) round-trips through the same * envelopes. Each PaymentDriver interprets them for its own chain. * * Wire format targets x402 v2 — github.com/coinbase/x402, specs/x402-specification-v2.md * (v2.0, 2025-12-9) + specs/transports-v2/http.md. The ENVELOPE is v2-conformant * (PaymentPayload carries `accepted`; SettlementResponse has `success` + `transaction`). * The SETTLEMENT SCHEME is our own `onchain-proof` (client pays on-chain first, proves * with a tx ref, server verifies locally) — permitted by spec §6 (a scheme owns its * `payload`) + §7 (self-hosted verification), but it does NOT interoperate with the * built-in `exact` scheme (which is signature + facilitator-broadcast). Deliberate. */ /** A CAIP-2 network id, e.g. `eip155:8453` or `solana:5eykt4Us…`. */ type Caip2 = `${string}:${string}`; /** An asset id — chain-specific: an EVM `0x…` address, a Solana base58 mint, a * TON jetton master, a Stellar `CODE:ISSUER`, or `'native'`. */ type AssetId = string; /** An account id — chain-specific: an EVM `0x…` address, a Solana base58 pubkey, * a TON address, or a Stellar `G…` account. */ type AddressId = string; interface X402ResourceObject { url: string; description?: string; /** The resource's response content-type, e.g. 'application/json' (v2 ResourceInfo, optional). */ mimeType?: string; } interface X402AcceptEntry { scheme: 'onchain-proof'; network: Caip2; /** Amount in the token's base units (already scaled by decimals). */ amount: string; /** ERC-20 address / SPL mint, or 'native' for the chain's native coin. */ asset: AssetId; payTo: AddressId; /** Payment is only accepted if mined within this many seconds of now. */ maxTimeoutSeconds: number; extra: { /** Single-use id echoed back in the proof. */ nonce: string; /** Token decimals, so the client can render the amount. */ decimals: number; /** Confirmations the client should wait before retrying. */ minConfirmations: number; /** Human-readable amount, e.g. "0.05". */ amountFormatted: string; symbol?: string; }; } /** * A standard x402 `exact` rail (EVM / EIP-3009) — the interop rail a PipRail gate * advertises ALONGSIDE its `onchain-proof` rail (dual-advertise) so any standard * x402 client can pay it. Same v2 PaymentRequirements skeleton as * {@link X402AcceptEntry}; only `scheme` and `extra` differ. The `extra` carries * the EIP-712 domain a payer signs over — `name`/`version` are READ from the token * contract by the gate (never assumed), since e.g. USDC's domain name is "USD Coin", * not the "USDC" symbol. */ interface X402ExactAcceptEntry { scheme: 'exact'; network: Caip2; amount: string; asset: AssetId; payTo: AddressId; maxTimeoutSeconds: number; extra: { /** The exact transfer method. EVM: `'eip3009'` for tokens with native * `transferWithAuthorization`, or `'permit2'` for tokens WITHOUT it (e.g. * Binance-Peg USDC on BNB) — the payer signs a Permit2 witness transfer whose * `spender` is the canonical x402ExactPermit2Proxy and whose `witness.to` binds the * recipient. **Solana (SVM): `'svm'`** — the payer partial-signs an SPL * `TransferChecked` transaction whose fee payer is the merchant (`feePayer` below), * and the gate co-signs as fee payer + broadcasts. **Algorand: `'algorand'`** — the payer * signs an ASA `axfer` to `payTo` at fee 0, atomically grouped with a 0-ALGO `pay` from * the `feePayer` that pools the group fee (per `scheme_exact_algo.md`); the gate (or a * keyless facilitator) signs that fee txn + submits. **Aptos: `'aptos'`** — the payer signs a * fee-payer (sponsored) `primary_fungible_store::transfer` to `payTo` (per * `scheme_exact_aptos.md`); the gate (or a keyless facilitator) adds the fee-payer signature * + submits, paying gas. **NEAR: `'near'`** — the payer signs a NEP-366 `SignedDelegateAction` * authorizing exactly one NEP-141 `ft_transfer` to `payTo` (per `scheme_exact_near.md`); a * facilitator-selected relayer (`feePayer` below) prepays gas + the 1 yoctoNEAR and submits, so * the buyer holds zero NEAR. PipRail self-settles ALL. */ assetTransferMethod: 'eip3009' | 'permit2' /** A FOREIGN-dialect alias for `'permit2'` — Binance's x402 ("b402") facilitator labels its * EVM-Permit2 exact rail `'permit2-exact'` (the coinbase/x402 spec uses bare `'permit2'`). * PipRail EMITS only `'permit2'`; it TOLERATES `'permit2-exact'` inbound so the buyer pays a * Binance-issued 402 (the buyer treats the two as one rail). NB: settlement still requires the * rail to bind PipRail's canonical x402ExactPermit2Proxy — see the buyer in drivers/evm. */ | 'permit2-exact' | 'svm' | 'algorand' | 'aptos' | 'near'; /** EIP-712 domain name of the token. OPTIONAL per the exact-EVM scheme (only * `assetTransferMethod` is required) — a foreign rail may omit it. NEVER assumed * from the symbol (USDC's on-chain name() is "USD Coin", not "USDC"); a PipRail gate * READS it on-chain, and the PipRail buyer RE-DERIVES it on-chain and ignores this. */ name?: string; /** EIP-712 domain version of the token (USDC: "2"). OPTIONAL (see `name`); read/re-derived on-chain. */ version?: string; /** **SVM / Algorand / Aptos** — the fee-payer (gas sponsor) address. The buyer builds the * transaction with this account as the gas payer (so the buyer spends ZERO native coin), * leaving its signature for whoever sponsors; the gate (self mode) or a keyless facilitator * fills it and submits. On **SVM** it must differ from `payTo` (the fee payer must never * appear in an instruction — a MUST-rule); on **Algorand/Aptos** the fee txn/signature is * separate from the transfer, so `feePayer === payTo` is allowed. */ feePayer?: string; /** **SVM only, OPTIONAL** — a ≤256-byte reconciliation memo the buyer attaches to the * transaction (the SVM scheme's optional `extra.memo`). */ memo?: string; /** **SVM only** — which SPL token program the mint belongs to, so both the buyer and * the gate derive the SAME associated-token-account address (an ATA's address depends * on the token program). Defaults to `'spl-token'` (classic) when absent — the * built-in USDC/USDT are classic. */ tokenProgram?: 'spl-token' | 'token-2022'; /** Confirmations the gate waits for before granting access — mirrors the gate's * `minConfirmations`, so the exact rail honours the same reorg safety as onchain-proof. * A PipRail convenience (standard clients ignore unknown keys). */ minConfirmations?: number; /** Token decimals — a PipRail convenience (standard clients ignore unknown keys). */ decimals?: number; /** Human-readable amount, e.g. "0.05" — a PipRail convenience. */ amountFormatted?: string; symbol?: string; }; } /** * A standard x402 `upto` rail (EVM / Permit2) — variable-amount / metered billing. * The buyer signs a Permit2 `PermitWitnessTransferFrom` authorization for `amount` * as a **MAXIMUM**; the merchant serves the resource, meters the **actual** usage, then * self-settles `actual ≤ max` through the canonical `x402UptoPermit2Proxy` from its own * relayer (which is the bound `witness.facilitator`). EVM-Permit2 ONLY — the upto spec * bans EIP-3009 (it fixes the amount at sign time) and has no non-EVM variant. A SEPARATE * accept type from {@link X402ExactAcceptEntry}: the only wire delta vs the exact Permit2 * rail is `scheme: 'upto'` and the `witness.facilitator` field, but keeping them distinct * leaves the exact parser/union untouched and lets the gate/client route on `scheme`. */ interface X402UptoAcceptEntry { scheme: 'upto'; network: Caip2; /** The authorized MAXIMUM in base units (already scaled by decimals). The merchant * settles the ACTUAL (≤ this) after serving. */ amount: string; asset: AssetId; payTo: AddressId; maxTimeoutSeconds: number; extra: { /** PipRail's transfer-method tag for the upto rail. This literal `'permit2-upto'` IS what * rides on the wire in `extra.assetTransferMethod` — it's a NON-STANDARD extra key (the * upto spec defines no `assetTransferMethod`; the discriminant a conformant foreign client * keys off is `scheme: 'upto'` + the Permit2 witness shape, and it ignores this unknown key). * We carry it so OUR parser stays unambiguous from the exact Permit2 rail. */ assetTransferMethod: 'permit2-upto'; /** The address bound into `witness.facilitator`. In self-settle this is the merchant's * own relayer; the buyer MUST sign over it; only this address can settle (the proxy * reverts `UnauthorizedFacilitator()` otherwise). */ facilitatorAddress: string; /** The Permit2 domain is fixed (`"Permit2"`), but the token's `name`/`version` ride * along for the optional EIP-2612 gas-sponsoring extension. Read/re-derived on-chain, * never assumed. */ name?: string; version?: string; /** Confirmations the gate waits for before granting access — a PipRail convenience. */ minConfirmations?: number; /** Token decimals — a PipRail convenience (standard clients ignore unknown keys). */ decimals?: number; /** Human-readable MAX, e.g. "0.50". */ amountFormatted?: string; symbol?: string; }; } /** A challenge `accepts[]` entry — PipRail's `onchain-proof` rail, a standard `exact` * rail, or a standard `upto` (metered) rail. */ type X402AnyAccept = X402AcceptEntry | X402ExactAcceptEntry | X402UptoAcceptEntry; interface X402Challenge { x402Version: 2; /** * Optional human-readable reason (v2 `error?: string`). PipRail EMITS it only on a * rejected-proof re-challenge (omitted on a fresh challenge). Typed to also tolerate * `null` when PARSING a foreign challenge — some deployed servers send `error: null`. */ error?: string | null; resource: X402ResourceObject; accepts: X402AnyAccept[]; /** v2 optional extensions. PipRail stamps the machine-readable rejection reason here on a * rejected-proof re-challenge: `{ piprail: { code, detail } }`. Omitted otherwise. */ extensions?: Record; } interface X402PaymentSignature { x402Version: 2; /** * x402 v2 PaymentPayload: the full PaymentRequirements entry the client chose * (carries `scheme` + `network`), echoed back from the challenge's `accepts[]`. */ accepted: X402AcceptEntry; /** * Scheme-defined payload. For `onchain-proof`: the challenge nonce + the proof * ref (`txHash` — a chain-specific id: an EVM tx hash, a Solana signature, a * TON locator, or a Stellar tx hash). */ payload: { nonce: string; txHash: string; }; /** * Optional v2 extensions the client attaches to its payload — e.g. the * `payment-identifier` idempotency id at `extensions['payment-identifier'].info.id` * (read by the gate via {@link readPaymentIdentifier}). A standard reader ignores it, * and omitting it keeps the payload byte-identical. */ extensions?: Record; } /** * The EIP-3009 authorization a payer signs for a standard `exact` rail. All * numeric fields are DECIMAL strings on the wire (value, validAfter, validBefore); * `nonce` is a 0x-prefixed 32-byte hex. Identical shape across x402 v1 and v2. */ interface ExactAuthorizationWire { from: string; to: string; value: string; validAfter: string; validBefore: string; nonce: string; } /** The `payload` a client sends for an `exact` rail: an EIP-3009 signature + its authorization. */ interface ExactPaymentPayload { signature: string; authorization: ExactAuthorizationWire; } /** * The `permit2Authorization` a payer signs for the `permit2` variant of the x402 * `exact` EVM scheme (tokens without EIP-3009 — e.g. Binance-Peg USDC on BNB). It is * an EIP-712 `PermitWitnessTransferFrom` over the canonical Permit2 contract, whose * `spender` is the canonical **x402ExactPermit2Proxy** and whose **witness** binds the * recipient (`to`) + an activation time (`validAfter`). All numeric fields are DECIMAL * strings on the wire (`permitted.amount`, `nonce`, `deadline`, `witness.validAfter`). */ interface Permit2Authorization { /** What may be pulled: the ERC-20 token + the exact base-unit amount. */ permitted: { token: string; amount: string; }; /** The payer (token owner). */ from: string; /** The signature's allowed spender — the canonical x402ExactPermit2Proxy. */ spender: string; /** Permit2 unordered nonce (a uint256, decimal string). Single-use via its bitmap. */ nonce: string; /** Unix-seconds signature expiry. */ deadline: string; /** The proxy-enforced witness: funds go ONLY to `to`, and not before `validAfter`. */ witness: { to: string; validAfter: string; }; } /** The `payload` a client sends for the `permit2` exact variant: a signature + its Permit2 authorization. */ interface Permit2PaymentPayload { signature: string; permit2Authorization: Permit2Authorization; } /** * The `permit2Authorization` a payer signs for the x402 `upto` (metered) EVM scheme. * Identical to {@link Permit2Authorization} EXCEPT the witness carries a `facilitator` * field as its **MIDDLE** member (`{ to, facilitator, validAfter }`) — the only delta * from the exact Permit2 witness. The EIP-712 witness type is * `Witness(address to,address facilitator,uint256 validAfter)`. `permitted.amount` is * the signed **MAXIMUM**; the merchant settles the ACTUAL (≤ max). `spender` is the * canonical **x402UptoPermit2Proxy**. All numeric fields are DECIMAL strings on the wire. */ interface Permit2UptoAuthorization { /** What may be pulled: the ERC-20 token + the signed MAXIMUM base-unit amount. */ permitted: { token: string; amount: string; }; /** The payer (token owner). */ from: string; /** The signature's allowed spender — the canonical x402UptoPermit2Proxy. */ spender: string; /** Permit2 unordered nonce (a uint256, decimal string). Single-use via its bitmap. */ nonce: string; /** Unix-seconds signature expiry. */ deadline: string; /** The proxy-enforced witness: funds go ONLY to `to`, only the bound `facilitator` can * settle, and not before `validAfter`. `facilitator` is the MIDDLE field. */ witness: { to: string; facilitator: string; validAfter: string; }; } /** The `payload` a client sends for the `upto` rail: a signature + its Permit2 upto authorization. */ interface Permit2UptoPaymentPayload { signature: string; permit2Authorization: Permit2UptoAuthorization; } /** * The `payload` a client sends for the **SVM (Solana) `exact`** variant: a base64-encoded, * serialized, **partially-signed** versioned Solana transaction (the buyer's `TransferChecked` * with the merchant as fee payer; the fee-payer signature slot is left empty for the gate to * fill). Per `scheme_exact_svm.md`. The transaction itself IS the proof — there's no separate * authorization object (the SVM analogue of EIP-3009's `authorization`). */ interface ExactSvmPaymentPayload { transaction: string; } /** * The `payload` a client sends for the **Algorand `exact`** variant, per * `scheme_exact_algo.md`: an atomically-grouped set of base64-encoded msgpack transactions, * and the index within it of the transaction that pays the resource server. The buyer's ASA * `axfer` (to `payTo`, fee 0) is SIGNED; a 0-ALGO `pay` from the `feePayer` that pools the * group fee is left UNSIGNED for whoever sponsors (the gate's relayer in self mode, or a * keyless facilitator). The group itself IS the proof — there's no separate authorization * object (the Algorand analogue of EIP-3009's `authorization` / SVM's `transaction`). */ interface ExactAlgorandPaymentPayload { /** Index into `paymentGroup` of the txn that pays the resource server (the buyer's `axfer`). */ paymentIndex: number; /** The atomic group: each element is a base64-encoded, msgpack-encoded (signed or unsigned) * Algorand transaction. ≤ 16 elements (the protocol's atomic-group cap). */ paymentGroup: string[]; } /** * The `payload` a client sends for the **Aptos `exact`** variant, per `scheme_exact_aptos.md`: * a fee-payer (sponsored, AIP-39) `primary_fungible_store::transfer`. `transaction` is the base64 * BCS-serialized `SimpleTransaction` (raw tx + the bound `feePayerAddress`); `senderAuth` is the * base64 BCS-serialized buyer (sender) authenticator. The buyer leaves the fee-payer signature for * whoever sponsors (the gate's relayer in self mode, or a keyless facilitator), who adds it + * submits. The (tx + sender authenticator) IS the proof — there's no separate `authorization` * object (the Aptos analogue of EIP-3009's `authorization` / SVM's `transaction`). The two-field * shape (a `senderAuth` alongside `transaction`) also distinguishes it from the SVM payload. */ interface ExactAptosPaymentPayload { /** Base64 BCS-serialized `SimpleTransaction` (raw transaction + the bound `feePayerAddress`). */ transaction: string; /** Base64 BCS-serialized buyer (sender) `AccountAuthenticator`. */ senderAuth: string; } /** * The `payload` a client sends for the **NEAR `exact`** variant, per `scheme_exact_near.md`: * a base64-encoded, Borsh-serialized NEP-366 `SignedDelegateAction` whose single delegated action * is one NEP-141 `ft_transfer` (to `payTo`, the exact `amount`, `deposit: 1` yoctoNEAR). The buyer * signs the delegate action with a FULL-ACCESS key (a function-call key can't attach the 1 yocto and * is rejected); a facilitator-selected relayer wraps it, prepays gas + the yocto, and submits. The * signed delegate action IS the proof — there's no separate authorization object (the NEAR analogue * of EIP-3009's `authorization` / SVM's `transaction`). Its single self-contained string field also * distinguishes it from every other family's payload shape. */ interface ExactNearPaymentPayload { /** Base64 of the Borsh-encoded NEP-366 `SignedDelegateAction` (one `ft_transfer`). */ signedDelegateAction: string; } /** Any `exact`-rail payload shape — EIP-3009 (`authorization`), Permit2 (`permit2Authorization`), * SVM (`transaction`), Algorand (`paymentGroup`), Aptos (`transaction` + `senderAuth`), or NEAR * (`signedDelegateAction`). */ type ExactPaymentPayloadAny = ExactPaymentPayload | Permit2PaymentPayload | ExactSvmPaymentPayload | ExactAlgorandPaymentPayload | ExactAptosPaymentPayload | ExactNearPaymentPayload; interface ParsedExactBase { x402Version: number; /** The client's claimed network (slug or CAIP-2) — for matching, not trust. */ network: string; /** The client's claimed asset, if present (v2 `accepted.asset`). */ asset?: string; /** The full decoded PaymentPayload, for verbatim forwarding to a facilitator (Mode B). */ raw: Record; } /** * What {@link parseExactPaymentHeader} extracts from an inbound `exact` payment, * normalised across the v1 (`X-PAYMENT`, flat `{scheme,network,payload}`, network slug) * and v2 (`PAYMENT-SIGNATURE`, `{accepted,payload}`, CAIP-2 network) wire shapes. * `network`/`asset` are the CLIENT's claim — used only to MATCH an offered rail; the gate * re-derives every verified field from its own trusted rail. A discriminated union on * `method`, so narrowing on `method` narrows `payload`: `'eip3009'` → {@link ExactPaymentPayload} * (`authorization`), `'permit2'` → {@link Permit2PaymentPayload} (`permit2Authorization`), * `'svm'` → {@link ExactSvmPaymentPayload} (`transaction`), `'algorand'` → * {@link ExactAlgorandPaymentPayload} (`paymentGroup`); `'aptos'` → * {@link ExactAptosPaymentPayload} (`transaction` + `senderAuth`); `'near'` → * {@link ExactNearPaymentPayload} (`signedDelegateAction`). */ type ParsedExactPayment = (ParsedExactBase & { method: 'eip3009'; payload: ExactPaymentPayload; }) | (ParsedExactBase & { method: 'permit2'; payload: Permit2PaymentPayload; }) | (ParsedExactBase & { method: 'svm'; payload: ExactSvmPaymentPayload; }) | (ParsedExactBase & { method: 'algorand'; payload: ExactAlgorandPaymentPayload; }) | (ParsedExactBase & { method: 'aptos'; payload: ExactAptosPaymentPayload; }) | (ParsedExactBase & { method: 'near'; payload: ExactNearPaymentPayload; }); /** * What {@link parseUptoPaymentHeader} extracts from an inbound `upto` payment — the * metered-rail sibling of {@link ParsedExactPayment}. A single PipRail-internal * `method: 'permit2-upto'` discriminant (the on-wire `scheme` is `'upto'`). `network`/ * `asset` are the CLIENT's claim — used only to MATCH an offered rail; the gate re-derives * every verified field from its own trusted rail. Kept separate from the exact parse path * so an upto payload (scheme `'upto'`, witness carries `facilitator`) never matches the * exact parser and vice-versa. */ type ParsedUptoPayment = ParsedExactBase & { method: 'permit2-upto'; payload: Permit2UptoPaymentPayload; }; interface X402Receipt { scheme: 'onchain-proof' | 'exact' | 'upto'; /** * x402 v2 SettlementResponse: settlement succeeded. Always `true` here — a * failed verification returns a 402, never a receipt. */ success: true; network: Caip2; /** * x402 v2 SettlementResponse: the on-chain transaction id of the SETTLED * payment — a chain-specific id (an EVM/Tron/Stellar/XRPL/NEAR tx hash, a * Solana signature, or a Sui digest). This is the verified tx itself, NOT the * submit-time proof ref in `payload.txHash` (which can be a composite locator * on TON/NEAR). (Was `txHash` before v2 envelope conformance.) */ transaction: string; asset: AssetId; amount: string; payer: AddressId; payTo: AddressId; verifiedAt: string; /** * NEW (additive, optional). The challenge nonce this settlement was bound to — * the value `genNonce()` minted into the 402 and echoed back as the buyer's * `payload.nonce` (NOT an exact-rail authorization/delegate nonce). REQUIRED to * re-verify Template-A families (Stellar/XRPL/NEAR/Algorand/TON) off-chain via a * synthetic accept; informational on digest-bound (Template-B) families. Omitted * unless the gate's `receipts` option is on, so a default 200 stays byte-identical. */ nonce?: string; } /** * The settled-payment record handed to a gate's `onPaid` hook — the wire * {@link X402Receipt} plus the merchant-facing extras the gate already computed * for the challenge, so a receipt handler never needs a second lookup to display * or reconcile it: the token's `decimals`/`symbol`, the human `amountFormatted` * (derived from the SETTLED base-unit `amount`, not the requested price), and a * stable `idempotencyKey`. * * **Delivery contract — read this before persisting receipts.** `onPaid` is * **at-least-once**: with a single in-memory replay store it fires exactly once * per proof, but across instances sharing a custom `isUsed`/`markUsed` store two * nodes can settle the same proof in a race and each fire once. Always **dedupe on * `idempotencyKey`** (a unique index / upsert). It is also fire-and-forget by * default — the gate does not block the response on it and a process crash between * settlement and your side-effect drops that receipt. For durability either set * `awaitOnPaid` (record before the 200) or push to a durable queue inside the hook; * for a webhook, use {@link deliverReceipt} (signed, retried, idempotent). */ interface PaidReceipt extends X402Receipt { /** The token's on-chain decimals — pairs with `amount` so you can format without a lookup. */ decimals: number; /** The token symbol when the gate knows it (e.g. `USDC`, `FDUSD`). */ symbol?: string; /** Human-readable settled amount, e.g. `"0.05"` — `amount` / 10**`decimals`. */ amountFormatted: string; /** * A stable, unique key for this settlement (the settled `transaction` id). `onPaid` * is at-least-once across instances — dedupe persistence and webhook delivery on this. */ idempotencyKey: string; } /** * The OPTIONAL Tier-2 EIP-712 delivery attestation — the official offer-receipt * `SignedReceipt`. Populated only when the merchant enables `receipts: { attest }` * (a separate phase); absent for Tier-1 chain-grounded receipts. Kept open for * forward-compat with the spec's typed-data fields, filled in by the EVM * `signReceipt` SPI; verification is `PipRailClient.verifyAttestation`. */ interface SignedReceipt { /** The EIP-712 signature over the receipt typed-data. */ signature: string; /** The recovered signer the verifier expects to equal the merchant `payTo`. */ signer: AddressId; [extra: string]: unknown; } /** * The self-contained, portable receipt a buyer KEEPS and **anyone** re-verifies * against the chain with only an RPC — no key, no backend, no PipRail account. * It bundles the verified {@link X402Receipt} (which carries `nonce` for Template-A * re-verification) with the reconstruction metadata a third party needs to rebuild * the trusted accept and re-run the driver's `verify()`: what was paid for * (`resource`) and the asset `decimals` (Stellar/XRPL/TON need it to re-scale the * amount). Rides the wire in `extensions['offer-receipt'].info` on the * `PAYMENT-RESPONSE` header; {@link PipRailClient.verifyReceipt} re-verifies it. */ interface PipRailReceipt { /** Receipt-format version — distinct from `x402Version`. */ piprail: '1'; /** The verified settlement (carries the challenge `nonce` for Template-A re-verify). */ receipt: X402Receipt; /** What was paid for — the challenge's resource URL. */ resource: { url: string; }; /** * ADDITIVE. The asset's on-chain decimals — threaded into the synthetic accept's * `extra.decimals` so Stellar/XRPL/TON `verify()` can re-scale the wire amount. * Receipt-bundle metadata only (the gate already knows it via {@link PaidReceipt}); * NOT on the minimal wire {@link X402Receipt}. */ decimals?: number; /** OPTIONAL Tier-2 attestation — present only when the merchant signed it. */ attestation?: SignedReceipt; } /** * Why a verification failed — a closed, chain-agnostic vocabulary. Every code a * driver returns is in this union; a client/agent branches on it rather than * parsing prose. Surfaced to the agent in the 402 body's `error` field with a * human-readable `detail`. Some codes are family-specific (annotated below): * e.g. account-watch chains (TON, Stellar) can't distinguish "wrong recipient" * from "no payment", so both collapse to `transfer_not_found`. * * `transient` = the proof may simply not have propagated to the server's RPC * node yet; `definitive` = retrying won't change it. These labels are * informational for consumers — the built-in client retries EVERY code up to * `maxPaymentRetries` (a short backoff absorbs RPC lag); it does not branch on * the code. */ type VerifyErrorCode = 'tx_not_found' | 'insufficient_confirmations' | 'tx_reverted' | 'no_meta' | 'wrong_recipient' | 'amount_too_low' | 'transfer_not_found' | 'payment_expired' | 'tx_already_used' | 'signature_invalid' | 'upto_settle_exceeds_max'; /** The shape every driver's `verify()` returns. Shared by drivers + protocol. */ type VerifyResult = { ok: true; receipt: X402Receipt; } | { ok: false; error: VerifyErrorCode; detail: string; }; declare const HEADER_REQUIRED = "payment-required"; declare const HEADER_SIGNATURE = "payment-signature"; declare const HEADER_RESPONSE = "payment-response"; declare const HEADER_SIGNATURE_V1 = "x-payment"; declare const HEADER_RESPONSE_V1 = "x-payment-response"; /** * Decode a base64-JSON wire value into a plain object (or `null` on garbage) — the * inverse of {@link buildSignatureHeader}/{@link buildExactSignatureHeader}. Exported * so a transport that carries the SAME payload as RAW JSON (A2A) can round-trip a * base64 fixture into the object the `…Object` parser cores / `gate.verifyObject` consume. */ declare function decodeBase64Json(value: string): unknown; declare function buildChallengeHeader(challenge: X402Challenge): string; /** * Build the PAYMENT-RESPONSE header from a settled {@link X402Receipt}. When `extensions` * is supplied (the gate's `receipts` option is on), it rides as an `extensions` sibling on * the SettlementResponse — a standard x402 reader ignores it, {@link parseReceiptExtension} * reconstructs the {@link PipRailReceipt}. Omit it (the default) and the header is * byte-identical to before this feature. */ declare function buildReceiptHeader(receipt: X402Receipt, extensions?: Record): string; /** The x402 extension key for verifiable delivery receipts (the `offer-receipt` extension). */ declare const EXT_OFFER_RECEIPT = "offer-receipt"; /** * Assemble the `extensions['offer-receipt']` block for a settled response — PURE * JSON, viem-free. SPEC-FAITHFUL placement: * - `info.receipt` holds the official `offer-receipt` **SignedReceipt** (the canonical * `{ format, payload, signature }` a STOCK `@x402/extensions` reader * (`extractReceiptFromResponse` → `info.receipt`) consumes, PLUS an additive `signer` — * the recovered signer address, a convenience a stock reader ignores and a verifier * re-derives from the signature anyway, so it's never trusted on the wire) — present * ONLY when a Tier-2 attestation was signed. When there is no attestation (Tier-1, * chain-grounded) there is no signed artifact, so `info.receipt` is absent — a stock * reader correctly sees "no signed receipt" (because there isn't one). * - `info.settlement` holds PipRail's chain-grounded {@link X402Receipt} (the * settlement record {@link parseReceiptExtension} re-reads for `verifyReceipt`) — a * PipRail-namespaced sibling a stock reader ignores. * The optional `schema` JSON-Schema sibling is owner-gated and not emitted by default. * The gate merges the returned record into the SettlementResponse's `extensions` on the * `PAYMENT-RESPONSE` header. */ declare function buildReceiptExtension(bundle: { receipt: X402Receipt; resource: { url: string; }; decimals?: number; attestation?: SignedReceipt; }): Record; /** The x402 extension key for the optional idempotency identifier (the official * `payment-identifier` extension). A client MAY attach a stable `id` so the server dedupes * retries and rejects a reused id bound to a DIFFERENT payment. */ declare const EXT_PAYMENT_IDENTIFIER = "payment-identifier"; /** * Advertise the `payment-identifier` extension on a 402 challenge — PURE JSON, viem-free. The * v2 `{ info, schema }` shape: `info.required:false` (PipRail never MANDATES an id) plus a * JSON-Schema bound of {@link PAYMENT_ID_MIN}–{@link PAYMENT_ID_MAX} chars. A client reads this * from the challenge and MAY echo an `id` back on its payload; the gate dedupes it on its * existing used-proof set. The gate merges this into the challenge `extensions` (a sibling key, * never inside `extensions.piprail`). */ declare function buildPaymentIdentifierAdvertisement(): Record; /** * Read + validate an inbound `payment-identifier` id from a decoded payment-payload object * (`payload.extensions['payment-identifier'].info.id`). PURE, NEVER throws. Returns: * - the `id` string when present + valid (16–128 chars, `[A-Za-z0-9_-]`), * - `null` when ABSENT (the id is OPTIONAL — the gate proceeds exactly as without the feature), * - `{ invalid }` when PRESENT but malformed (the gate re-challenges so the buyer can fix it). */ declare function readPaymentIdentifier(payload: unknown): string | null | { invalid: string; }; declare function buildSignatureHeader(signature: X402PaymentSignature): string; /** * Build the v2 PAYMENT-SIGNATURE header value for a standard x402 `exact` payment: * base64 of `{ x402Version: 2, accepted, payload }`. `accepted` is the chosen rail * echoed back VERBATIM from the challenge's `accepts[]` (preserving any extra keys a * facilitator needs); `payload` is the EIP-3009 `{ signature, authorization }` the * buyer's EVM driver produced. Chain-agnostic (pure JSON/base64) — the driver owns * the signing, this only frames it for the wire. Round-trips through * {@link parseExactPaymentHeader}. (The `onchain-proof` counterpart is * {@link buildSignatureHeader}; the v1 flat-shape utility is `encodeXPaymentHeader`.) */ declare function buildExactSignatureHeader(input: { accepted: X402ExactAcceptEntry; payload: ExactPaymentPayloadAny; }): string; /** * Build the v2 PAYMENT-SIGNATURE header value for a standard x402 `upto` (metered) * payment: base64 of `{ x402Version: 2, accepted, payload }`. `accepted` is the chosen * upto rail echoed back VERBATIM from the challenge's `accepts[]`; `payload` is the * Permit2-upto `{ signature, permit2Authorization }` the buyer's EVM driver produced * (the witness carries `facilitator`). Chain-agnostic (pure JSON/base64). Round-trips * through {@link parseUptoPaymentHeader}. (The `exact` counterpart is * {@link buildExactSignatureHeader}.) */ declare function buildUptoSignatureHeader(input: { accepted: X402UptoAcceptEntry; payload: Permit2UptoPaymentPayload; }): string; /** * Parse the PAYMENT-REQUIRED challenge from a 402 response. Prefers the * `payment-required` header, falls back to the JSON body. */ declare function parseChallenge(response: Response): Promise; /** Parse the PAYMENT-RESPONSE receipt header on a 200 settlement. Reads the v2 * `payment-response` header, falling back to the v1 `x-payment-response` a foreign * server may set. Returns a fully-formed {@link X402Receipt} only (a bare foreign * exact SettleResponse without a `payer` is read by {@link parseSettleResponse}). */ declare function parseReceipt(response: Response): X402Receipt | null; /** * Read a {@link PipRailReceipt} back from a settled response's `PAYMENT-RESPONSE` * header (v2, or the v1 `X-PAYMENT-RESPONSE` fallback). Reads PipRail's chain-grounded * settlement record from `extensions['offer-receipt'].info.settlement`, and the optional * Tier-2 {@link SignedReceipt} from the spec slot `info.receipt`. Liberal/Postel: TOLERATES * + IGNORES a `schema` sibling (or any unknown sibling), and — for robustness — also accepts * the settlement record at `info.receipt` if it's there instead (an X402Receipt, distinguished * from a SignedReceipt by {@link isValidReceipt}). Returns `null` when no header, no extension * block, or no valid settlement record. Pure — no chain read. */ declare function parseReceiptExtension(response: Response): PipRailReceipt | null; /** * A standard x402 SettleResponse as the BUYER reads it off a settled (non-402) * response. The `success` flag is authoritative: `false` is an EXPLICIT facilitator/ * server REJECTION (the buyer must NOT record a spend), `true` is an affirmative * settlement. `transaction` is the on-chain settle tx the facilitator broadcast. */ interface SettleOutcome { success: boolean; transaction?: string; network?: string; payer?: string; errorReason?: string; /** * The ACTUAL settled amount in atomic units, when the SettleResponse carries it * (the x402 `upto` scheme makes this field REQUIRED — may be `"0"`). The upto buyer * reads it to record the metered ACTUAL spend in its ledger rather than the signed MAX. * Absent for `onchain-proof`/`exact` settle responses (they fix the amount up front). */ amount?: string; } /** * Read a standard x402 SettleResponse for the BUYER, from the v2 `payment-response` * header (or the v1 `x-payment-response` fallback). Returns `null` when neither * header is present, unparseable, or carries no boolean `success` — i.e. when the * server served the resource WITHOUT echoing a settle result, which the exact buyer * treats as an affirmative 2xx settlement (receipt-less). When a body IS present with * a boolean `success`, that flag is returned verbatim: ONLY an explicit * `success: false` is a rejection. Used by the exact pay path to tell a real * settlement from a phantom one (never record a spend on `success:false`). */ declare function parseSettleResponse(response: Response): SettleOutcome | null; /** * Parse an already-decoded `onchain-proof` PaymentPayload OBJECT (server side) — * the object-accepting CORE of {@link parseSignatureHeader}. Identical logic to the * base64 entry-point below, minus the `fromBase64Json` decode: this is what the A2A * transport feeds raw JSON metadata into (A2A carries the payload as raw JSON, not * base64), via `gate.verifyObject`. {@link parseSignatureHeader} is the thin base64 * wrapper — byte-identical to before this split on the HTTP path. */ declare function parseSignatureObject(parsed: unknown): X402PaymentSignature | null; /** Parse a PAYMENT-SIGNATURE header value (server side). A thin base64 wrapper over * {@link parseSignatureObject} — byte-identical to before the object-core split. */ declare function parseSignatureHeader(value: string): X402PaymentSignature | null; /** * Parse an inbound `exact` payment from a base64 header value (`PAYMENT-SIGNATURE` * v2 or `X-PAYMENT` v1). Tolerant of BOTH wire shapes — the inner * `{ signature, authorization }` payload is identical across versions, so we read * `scheme`/`network` from either the v2 `accepted` object or the v1 flat fields. * Returns null when the value isn't a recognisable `exact` payment (e.g. it's an * `onchain-proof` proof, or malformed). */ declare function parseExactPaymentHeader(value: string): ParsedExactPayment | null; /** * Parse an already-decoded `exact` PaymentPayload OBJECT — the object-accepting CORE * of {@link parseExactPaymentHeader}, identical logic minus the base64 decode. It * absorbs the v1/v2 wire skew (the `accepted` wrapper vs flat `scheme`/`network`) and * the six exact-method discriminants. The A2A transport feeds raw JSON metadata here * (via `gate.verifyObject`); {@link parseExactPaymentHeader} is the thin base64 wrapper, * byte-identical to before this split on the HTTP path. */ declare function parseExactObject(parsed: unknown): ParsedExactPayment | null; /** * Parse an inbound `upto` (metered) payment from a base64 header value * (`PAYMENT-SIGNATURE` v2 or `X-PAYMENT` v1). A SEPARATE parser from * {@link parseExactPaymentHeader}, gated strictly on `scheme === 'upto'` AND a * `witness.facilitator` STRING — so an upto payload never matches the exact parser and an * exact Permit2 payload (no `witness.facilitator`) never matches this one. Returns null * when the value isn't a recognisable `upto` payment. */ declare function parseUptoPaymentHeader(value: string): ParsedUptoPayment | null; /** * Parse an already-decoded `upto` (metered) PaymentPayload OBJECT — the object-accepting * CORE of {@link parseUptoPaymentHeader}, identical logic minus the base64 decode. Gated * strictly on `scheme === 'upto'` + a `witness.facilitator` string. The A2A transport feeds * raw JSON metadata here (via `gate.verifyObject`); {@link parseUptoPaymentHeader} is the thin * base64 wrapper, byte-identical to before this split on the HTTP path. */ declare function parseUptoObject(parsed: unknown): ParsedUptoPayment | null; /** * Pick the first accepts[] entry on the `onchain-proof` scheme whose network * satisfies `matches` (any chain family). Returns null if none match. */ declare function pickAccept(challenge: X402Challenge, matches: (network: string) => boolean): X402AcceptEntry | null; /** * Durable spend store — the pluggable seam that lets a client's budget SURVIVE a * restart, mirroring the gate's replay-protection `isUsed`/`markUsed` hook. * * The {@link SpendLedger} is in-memory by default (the session IS the process). Pass * a `SpendStore` to {@link PipRailClientOptions.spendStore} (or to * `MultiChainPayer.fromWallets`) and the ledger HYDRATES from `load()` at * construction and `append()`s every settled payment — so `maxTotal`, * `maxTotalPerDenom`, and the payment-count caps resume where they left off after a * crash or redeploy, with NO PipRail backend (you own the store, exactly like the * replay set). * * Contract: * - `load()` is read ONCE, synchronously, at ledger construction (the log is small * — one line per payment). Return `[]` for a fresh store. * - `append(record)` persists ONE settled payment. It is called on the hot path, so * it MUST NOT throw and SHOULD NOT block (ERRORS.md §: store I/O never throws — a * failed append is swallowed so a disk hiccup can't abort a confirmed payment). * - Round-trip the WHOLE `SpendRecord` (incl. `decimals` + `denom`) so totals and * the grand-total rebuild exactly on reload — the built-in stores below do. * * PURE + browser-safe: this module has zero Node/chain imports. The Node-only * {@link fileSpendStore} (a one-line local JSONL log) lives in `@piprail/sdk/node`. */ interface SpendStore { /** Hydrate the ledger at construction — every previously-settled payment, in order. * Read once, synchronously. Return `[]` for a fresh store. */ load(): SpendRecord[]; /** Persist one settled payment. MUST NOT throw (failures are swallowed by the ledger). */ append(record: SpendRecord): void; } /** * An in-memory {@link SpendStore} — useful for tests and for sharing a seed across * clients in one process. Not durable (it's the default behaviour made explicit); * for restart-survival use `fileSpendStore` from `@piprail/sdk/node` or your own. */ declare function memorySpendStore(seed?: SpendRecord[]): SpendStore; interface SpendRecord { url: string; host: string; network: Caip2; asset: string; /** Base units that count toward the budget. For onchain-proof/exact this is the paid * amount. For the metered `upto` rail it is the authorized **MAX** (not the merchant's * claimed actual) — the only buyer-provable bound, so a malicious merchant that settles * the MAX on-chain but under-reports the actual can never loosen a cumulative cap * (`maxTotal` / `maxTotalPerDenom` / `windowTotal`). See {@link settledBase} for the actual. */ amountBase: string; /** Human-readable {@link amountBase}, e.g. '0.05'. */ amountFormatted: string; /** METERED `upto` ONLY: the merchant-claimed settled **actual**, clamped to `≤` the * authorized MAX (`amountBase`). Informational — surfaced for transparency/reconciliation * (and equal to the receipt's amount); it does NOT feed any cap (those tally the MAX). * Absent for onchain-proof/exact (where the actual IS the budgeted amount). */ settledBase?: string; /** Human-readable {@link settledBase}. */ settledFormatted?: string; symbol?: string; /** TRUE token decimals. Carried on the record so a {@link SpendStore} can rebuild * exact totals + the grand total on reload (the client stamps it on every settle). */ decimals?: number; /** The DENOMINATION this payment counts toward in the cross-token grand total * (e.g. `'USD'`), or absent for a token with no denomination. Stamped by the * client from the policy at settle time so it round-trips through persistence. */ denom?: string; /** Proof ref (EVM tx hash, Solana signature, TON locator, Stellar tx hash). */ ref: string; /** ISO timestamp of settlement. */ at: string; } interface SpendAssetTotal { network: Caip2; asset: string; symbol?: string; decimals: number; totalBase: string; totalFormatted: string; count: number; } /** Cumulative spend in one DENOMINATION (a unit of account), summed across every * token of that denomination and every chain — the cross-token grand total. */ interface SpendDenomTotal { /** The denomination, e.g. `'USD'`. */ denom: string; /** Total spent, scaled to {@link DENOM_PRECISION} (the accumulator's base), as a string. */ totalScaled: string; /** Human-readable denomination total, e.g. '12.34'. */ totalFormatted: string; /** Number of payments (across all tokens + chains) that contributed to this denom. */ count: number; } interface SpendSummary { /** Total number of settled payments (across every chain + token). */ count: number; /** Cumulative spend per distinct (network, asset). */ byAsset: SpendAssetTotal[]; /** Cumulative spend per DENOMINATION — the cross-token grand total (empty when no * payment carried a denomination). NEVER a price-converted figure: a sum of tokens * grouped as one unit, each 1:1. */ byDenom: SpendDenomTotal[]; /** Every settled payment, in order. */ records: SpendRecord[]; } declare class SpendLedger { private readonly records; private readonly buckets; /** Per-denomination running total, scaled to {@link DENOM_PRECISION}. Keyed by the * UPPERCASE denomination so lookups are case-insensitive. */ private readonly denomTotals; /** Threshold keys already warned (`warnAtFraction` fires once per crossing per cap). * Lives on the LEDGER — not the client — so clients SHARING one (a cross-chain * MultiChainPayer) dedupe together: a denomination/count threshold fires once across the * whole shared budget, not once per chain. */ private readonly warned; private readonly store?; /** * Session clock origin (epoch-ms) — process/session start = ledger * construction. In-memory; a new process is a new session. The client reads it * to compute the `ttlSeconds` deadline and the rolling-window slice. */ readonly sessionStart: number; /** * @param store Optional durable {@link SpendStore}. When supplied, the ledger * HYDRATES from `store.load()` here (so prior spend resumes after a restart) and * `append()`s every settled payment. A throwing/absent store fails SAFE to an * empty in-memory ledger — it never blocks construction (ERRORS.md: never throw). */ constructor(store?: SpendStore); /** * A record is safe to tally iff its `amountBase` is a non-negative integer STRING and its * `decimals` is an integer in `[0, MAX_DECIMALS]`. The live `record()` path always passes * (the client validated the quote), but a hydrated record comes from an UNTRUSTED store * (a tampered/corrupt/future-version JSONL line), so we gate it here — a bad record is * dropped rather than allowed to throw `BigInt(...)`/`formatUnits(...)` later and brick a * read or the constructor. */ private isTallyable; /** Apply a record to the in-memory tallies (records + per-asset bucket + denom total). * Shared by {@link record} and constructor hydration; does NOT persist. Returns the * stored record, or `null` when the record is corrupt and was skipped. */ private ingest; /** Record a settled payment. `decimals` is the TRUE token decimals (for the * per-asset running total + the formatted summary). `denom` is the unit-of-account * the payment counts toward in the grand total (or omit for none). Persists to the * {@link SpendStore} when one is configured (a failed append never throws). */ record(r: SpendRecord, decimals: number, denom?: string): void; /** Running total (base units) already spent on this (network, asset). */ totalFor(network: string, asset: string): bigint; /** * Running grand total for a DENOMINATION, scaled to {@link DENOM_PRECISION} (so * tokens of different decimals add up exactly). Summed across every token of that * denomination and every chain this ledger has seen. Powers `maxTotalPerDenom`. * `0n` for a denomination never spent on. Case-insensitive. */ totalForDenom(denom: string): bigint; /** Total number of settled payments (across every chain + token). Powers `maxPayments`. */ count(): number; /** Mark a `warnAtFraction` threshold key as fired; returns `true` the FIRST time (so the * caller emits the `budget-threshold` event once) and `false` thereafter. Shared across * every client on this ledger, so a cross-chain threshold fires once for the whole budget. */ markWarned(key: string): boolean; /** * Number of settled payments whose `at` (ISO) is at or after `sinceMs` (epoch-ms), * across every chain + token. Backs the rolling payment-count cap * (`maxPaymentsPerWindow`, `sinceMs = now - windowSeconds*1000`). Linear scan — * negligible at agent-session cardinality and only when a window count cap is set. */ countSince(sinceMs: number): number; /** * Sum of base-unit amounts for (network, asset) whose record `at` (ISO * timestamp) is at or after `sinceMs` (epoch-ms). Backs the rolling window * (`sinceMs = now - windowSeconds*1000`). A linear scan of `records` — * agent-session cardinality is small (tens), and it only runs when a window * policy is set, so it's negligible against the network round-trip. */ totalSince(network: string, asset: string, sinceMs: number): bigint; /** * The per-(network, asset) buckets, as read-only tuples — `network`, `asset`, * `symbol`, the TRUE `decimals` (frozen from the first record), and the running * `totalBase`. Lets the client compose a budget view WITHOUT coupling the ledger * to the policy (the cap math lives in the client). Decimals only exist for a * pair once it's been spent on — a never-spent pair simply isn't a bucket. */ assetBuckets(): { network: Caip2; asset: string; symbol?: string; decimals: number; totalBase: bigint; }[]; /** * The per-denomination grand totals, as read-only tuples — `denom` and the running * `totalScaled` (at {@link DENOM_PRECISION}). Lets the client compose the grand-total * budget view; the cap math lives in the client. A never-spent denomination is absent. */ denomBuckets(): { denom: string; totalScaled: bigint; }[]; /** An immutable snapshot of all spend so far. */ summary(): SpendSummary; } export { parseSettleResponse as $, type AddressId as A, type X402ExactAcceptEntry as B, type Caip2 as C, type X402PaymentSignature as D, EXT_OFFER_RECEIPT as E, type X402Receipt as F, type X402ResourceObject as G, HEADER_REQUIRED as H, type X402UptoAcceptEntry as I, buildChallengeHeader as J, buildExactSignatureHeader as K, buildPaymentIdentifierAdvertisement as L, buildReceiptExtension as M, buildReceiptHeader as N, buildSignatureHeader as O, type PaidReceipt as P, buildUptoSignatureHeader as Q, decodeBase64Json as R, type SettleOutcome as S, memorySpendStore as T, parseChallenge as U, type VerifyErrorCode as V, parseExactObject as W, type X402AcceptEntry as X, parseExactPaymentHeader as Y, parseReceipt as Z, parseReceiptExtension as _, type AssetId as a, parseSignatureHeader as a0, parseSignatureObject as a1, parseUptoObject as a2, parseUptoPaymentHeader as a3, pickAccept as a4, readPaymentIdentifier as a5, EXT_PAYMENT_IDENTIFIER as b, type ExactAuthorizationWire as c, type ExactPaymentPayload as d, type ExactPaymentPayloadAny as e, HEADER_RESPONSE as f, HEADER_RESPONSE_V1 as g, HEADER_SIGNATURE as h, HEADER_SIGNATURE_V1 as i, type ParsedExactPayment as j, type ParsedUptoPayment as k, type Permit2Authorization as l, type Permit2PaymentPayload as m, type Permit2UptoAuthorization as n, type Permit2UptoPaymentPayload as o, type PipRailReceipt as p, type SignedReceipt as q, type SpendAssetTotal as r, type SpendDenomTotal as s, SpendLedger as t, type SpendRecord as u, type SpendStore as v, type SpendSummary as w, type VerifyResult as x, type X402AnyAccept as y, type X402Challenge as z };