@parallel-protocol/x402-fetch
v0.1.0
Published
Payer-side x402 client for Parallel Protocol — wraps fetch so a 402 challenge is paid automatically; the agent only needs a funded wallet
Maintainers
Readme
@parallel-protocol/x402-fetch
Payer-side x402 client for Parallel Protocol — the counterpart of the
@parallel-protocol/x402
merchant middleware. It wraps fetch so that an x402 402 Payment Required
challenge is paid automatically and the request is retried with the payment
attached.
The agent only needs a funded wallet. No payment plumbing, no protocol knowledge, no other setup.
Install
npm install @parallel-protocol/x402-fetch
# or
bun add @parallel-protocol/x402-fetchUsage
import { wrapFetchWithPayment } from "@parallel-protocol/x402-fetch";
const payFetch = wrapFetchWithPayment(fetch); // key from PARALLEL_PRIVATE_KEY
const res = await payFetch("https://api.example.com/premium-data");
const data = await res.json(); // paid for and deliveredHow it works
- The wrapped fetch performs the request; anything but a
402passes through untouched. - On
402, it decodes and validates the merchant's invoice (payment-requiredheader), picks the entry matching the configured chain (preferring yourpayWithtoken when offered), and enforces the spend cap. - It builds and signs the payment locally through the
@parallel-protocol/cliengine (a regular dependency), which selects the best route — direct transfer, Parallelizer swap, savings deposit/redeem — from the wallet's balances. - It retries the request with the
X-PAYMENTheader; the merchant verifies and settles through the Parallel facilitator.
Gas is sponsored by the facilitator — the wallet needs no ETH, only the stablecoin it pays with (USDp, USDC or sUSDp). On a paid response, the tx hash and route are available via the receipt helper:
import { decodePaymentResponse } from "@parallel-protocol/x402-fetch";
const receipt = decodePaymentResponse(res); // { txHash, route, gasSponsored, … }Options
wrapFetchWithPayment(fetch, {
chain: "base", // chain slug (default "base")
privateKey: "0x…", // defaults to PARALLEL_PRIVATE_KEY
payWith: "usdc", // force the spend token; default: balance-based
maxAmount: "1", // per-request spend cap, whole token units (default "1")
timeoutMs: 60_000, // payment-engine timeout (default 60s)
allowInsecure: false, // allow plain-http merchants (localhost always allowed)
});An unknown chain slug or a missing/malformed key throws X402FetchError at
wrap time, not on the first request. Payment failures reject with an
X402FetchError carrying a code (and an engineCode such as
INSUFFICIENT_BALANCE when the engine rejected).
Security model
- Spend is capped. Invoices above
maxAmount(default 1 token) are rejected before anything is signed — a compromised merchant cannot drain the wallet in one request. Raise the cap deliberately, per wrapper. - The invoice is not trusted. Addresses, amounts and decimals are validated; the decimals of known Parallel tokens cannot be re-declared by the merchant, and invoices for a different chain fail loudly instead of paying into the void.
- The private key never leaves your machine. Payments are EIP-3009 typed messages signed locally; only signatures travel. The key does cross the process boundary to the local signing engine (a child process of your own user).
- Plain http is refused (except localhost) unless you opt in — an on-path attacker on http could inject invoices.
- Use a dedicated hot wallet funded with only what the agent needs. Any code running in your process can read your environment — never point an account holding significant funds at an autonomous agent.
- A payment can settle even when the paid retry fails (network errors, merchant bugs). Treat non-idempotent paid requests accordingly.
Scope & runtime
Targets Parallel merchants (header-carried invoices, as emitted by
@parallel-protocol/x402). Node and Bun; the payment engine runs as a
subprocess of your own runtime — no global installs, no downloads at payment
time. Payments are serialized per wrapper. Browser support and viem Account
signers (hardware and remote) are planned.
