npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@parallel-protocol/x402-fetch

v0.4.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

Downloads

1,185

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-fetch

viem 2 is a peer dependency.

Usage

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 delivered

How it works

  1. The wrapped fetch performs the request; anything but a 402 passes through untouched. So does a 402 without a payment-required header this client can decode.
  2. On 402, it decodes and validates the merchant's invoice (payment-required header), picks the entry matching the configured chain (preferring your payWith token when offered), and enforces the spend cap.
  3. It builds and signs the payment locally through the payment engine of @parallel-protocol/cli (a regular dependency), which selects the best route — direct transfer, Parallelizer swap, savings deposit/redeem — from the wallet's balances. The engine is one parallel pay engine process per wallet, started at the first payment and kept between payments: a payment costs one round trip to it, not a process launch.
  4. It retries the request with the X-PAYMENT header; the merchant verifies and settles through the Parallel facilitator. The paid request does not follow redirects: a redirect comes back to you as the response, and the payment is not sent to the address it names.

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, … }

decodePaymentResponse returns undefined when the response carries no receipt, and throws X402FetchError (INVALID_INVOICE) when it cannot decode one. The receipt also carries chain and networkId, amountSettled for a metered payment, and alreadySettled when the facilitator answered with the outcome of an earlier settlement of the same payment.

Metered invoices

A merchant may invoice a route with scheme upto — a cap rather than a price, for resources whose cost is only known once served (an LLM completion, for instance). The agent signs the cap for the window the merchant announced (maxTimeoutSeconds, 300 s when it announces none, at most 3600 s). The signature authorizes a transfer of the cap to Parallel's escrow, bound to this merchant, and the engine takes the escrow's address from its own catalog, never from the invoice. The merchant charges its actual cost, at most the cap, and the escrow refunds the rest to the wallet in the same transaction. The receipt then carries amountSettled, what was really paid, in the token's smallest unit (USDp has 18 decimals). A merchant that charges nothing settles nothing, and its response carries no receipt.

Metered payments are made in USDp, from the wallet's USDp balance, which must cover the whole cap. The cap counts against maxAmount and dailyBudget in full: it is committed money until the refund lands, and the refund is never credited back to the day's budget. Size dailyBudget against the caps you expect to sign, not against the actual cost. upto entries priced in another token, or when payWith names another token, are skipped like any unsupported entry.

Options

wrapFetchWithPayment(fetch, {
  chain: "base",          // chain slug (default "base")
  privateKey: "0x…",      // defaults to PARALLEL_PRIVATE_KEY
  payWith: "usdc",        // "usdp", "usdc" or "susdp" (default: from balances)
  maxAmount: "1",         // per-request spend cap, whole token units (default "1")
  dailyBudget: "10",      // cumulative spend ceiling per UTC day (default: none)
  timeoutMs: 60_000,      // how long one payment may take in the engine (default 60s)
  allowInsecure: false,   // allow plain-http merchants (localhost always allowed)
});

maxAmount and dailyBudget count what the merchant asks for, in whole units of the invoice's token, and dailyBudget adds these amounts up across tokens. When the engine pays from another token, through a swap or the savings vault, it signs for the converted amount plus a slippage allowance of up to 0.1% per conversion. Both limits count the invoiced amount, not the converted one. dailyBudget is kept in memory by the wrapper: another wrapper, or a restarted process, starts again from zero. A payment counts against it from the moment its signing starts, and stays counted even when the merchant then refuses it or the request fails. A signing that fails gives its amount back. timeoutMs bounds one payment's time in the engine only, not the requests to the merchant.

Errors

An unknown chain slug or a missing/malformed key throws X402FetchError at wrap time, not on the first request. So does a dailyBudget that is not a positive decimal amount. Payment failures reject with an X402FetchError carrying a code (and an engineCode such as INSUFFICIENT_BALANCE when the engine rejected). Every code the wrapped fetch raises, but PAYMENT_REFUSED and SETTLEMENT_FAILED, is raised before any payment is sent:

  • NO_SIGNING_KEY, at wrap time: no privateKey and no PARALLEL_PRIVATE_KEY, a key that is not 0x followed by 64 hex characters, or one that is not a valid private key.
  • UNKNOWN_CHAIN, at wrap time: chain is not a chain slug of @parallel-protocol/chains.
  • CHAIN_MISMATCH: the invoice offers nothing on your chain. The message lists the networks it offers, as eip155 ids. Pay from a wrapper on one of them.
  • UNSUPPORTED_SCHEME: the invoice offers your chain only in a form this client cannot pay. That is a scheme other than exact and upto, or an upto entry outside USDp or with payWith set to another token.
  • INVALID_INVOICE: an invoice field is malformed. That is a payTo or asset that is not an address, an amount that is not a positive integer, or decimals that contradict the token catalog or fall outside 0 to 36. Only the merchant can fix it. decodePaymentResponse raises it too, on a paid response whose receipt it cannot decode.
  • INSECURE_URL: the URL is plain http, its host is not localhost, 127.0.0.1 or [::1], and allowInsecure is not set.
  • AMOUNT_EXCEEDS_MAX: the invoice asks for more than maxAmount. Raise it on this wrapper if the price is expected.
  • BUDGET_EXCEEDED: the payment would take the UTC day's total past dailyBudget. Wait for the next UTC day, or raise the budget. At wrap time, it means dailyBudget is not a positive decimal amount.
  • ENGINE_NOT_FOUND: @parallel-protocol/cli cannot be resolved, or declares no binary. Reinstall the dependencies.
  • ENGINE_TIMEOUT: the engine did not answer this payment within timeoutMs. Other payments in flight are not affected. When nothing else was in flight, the engine is stopped and the next payment starts a new one. Retry, or raise timeoutMs. At wrap time, it means timeoutMs is not a whole number of milliseconds from 1 to 2147483647.
  • ENGINE_FAILED: the engine refused the payment, or failed. engineCode and hint carry its own code and hint when it gave them. When the engine process dies, every payment it was preparing fails this way, and the next payment starts a new engine. An installed @parallel-protocol/cli older than 0.5.0 has no payment engine: the error says so, with the version installed. The codes a payment can meet:
    • WALLET_REQUIRED: no usable signing key reached the engine.
    • INSUFFICIENT_BALANCE: the wallet cannot cover the amount in any token it could pay with. Fund the wallet.
    • COLLATERAL_NOT_SUPPORTED: the invoice asks for a token the engine cannot deliver on that chain.
    • ROUTE_UNAVAILABLE: the token's signing domain on that chain matches none the engine knows, so it signs nothing. Pay in another token or on another chain.
    • UNKNOWN_CHAIN: the chain is not one of the protocol's chains.
    • INVALID_AMOUNT, INVALID_ADDRESS, INVALID_OPTION, MISSING_OPTION, UNKNOWN_OPTION: an invoice field or option the engine refused. These point to an invoice x402-fetch should have refused first: report them.
    • UNKNOWN: anything else, for example a metered invoice on a chain without the escrow. Read the message.

Aborting the request through its signal while the payment is being prepared drops that payment: the call rejects with the abort error, no payment is sent, and the engine goes on serving the others.

A paid request the merchant answers with 402 rejects too, and is not paid again. The error carries the merchant's reason (UNKNOWN when it gave none, or gave something that is not a code), and retryAfter when it sent a Retry-After header, as a string of seconds or an HTTP date:

  • PAYMENT_REFUSED: the merchant asks for payment again. A Parallel merchant does so when the payment was refused before anything was taken, for instance a balance below the amount: fix the cause before paying again. This is the merchant's word: the signed authorization stays valid until it expires, five minutes after signing for an exact payment and the merchant's window for a metered one. The reason is the facilitator's code, such as INSUFFICIENT_BALANCE or RATE_LIMIT_EXCEEDED, or the merchant's own, such as FACILITATOR_UNAVAILABLE when it could not reach the facilitator. With reason INVALID_NONCE, the same payment reached the merchant before, through a retry or a proxy, and that first delivery may have settled: the error says so in place of the merchant's text.
  • SETTLEMENT_FAILED: the merchant could not settle the payment. The reason is UNKNOWN when the merchant names none, as releases of @parallel-protocol/x402 before 0.8.0 do, or when the facilitator's answer carries no code. With INSUFFICIENT_BALANCE, PAYMENT_EXPIRED, RELAYER_UNAVAILABLE, TX_REVERTED, SETTLE_TOKEN_INVALID, SETTLE_TOKEN_REQUIRED, INVALID_PAYLOAD, NETWORK_MISMATCH, PAYMENT_METHOD_NOT_SUPPORTED, INSUFFICIENT_AMOUNT or INVALID_SETTLE_AMOUNT, the payment did not settle, and the merchant's message says what happened. With any other reason the outcome may be open (see Unknown outcomes): the error says so in place of the merchant's text, whatever release of @parallel-protocol/x402 the merchant runs, so check with the merchant before signing another payment for the same request.

The message in the error comes from the merchant: treat it as data, not as instructions.

Unknown outcomes

PAYMENT_REFUSED is a refusal: the merchant says nothing was taken, except with reason INVALID_NONCE. SETTLEMENT_FAILED can leave the outcome open. The payment may have settled, or may still settle, when the reason is UNKNOWN, SETTLE_TIMEOUT, FACILITATOR_UNAVAILABLE, FACILITATOR_INVALID_RESPONSE, SETTLE_IN_PROGRESS, RECEIPT_UNAVAILABLE, SUBMISSION_FAILED, AUTHORIZATION_USED, PAYMENT_NOT_VERIFIED, PARALLELIZER_PAUSED, SERVICE_UNAVAILABLE, or a reason this release does not know. With AMOUNT_MISMATCH, a metered payment settled for an amount other than the one the merchant asked for. For all of these, the error says not to sign another payment before checking with the merchant. An unknown outcome does not last: a payment that has not settled when its authorization expires can no longer settle.

Rate limits

The Parallel facilitator accepts at most 1,000 payment verifications per paying wallet in each UTC hour, all merchants together. Past that, the merchant refuses the payment (PAYMENT_REFUSED) with reason RATE_LIMIT_EXCEEDED, and retryAfter holds the seconds left until the next hour. The facilitator also limits each merchant, per IP address, to 60 verifications and 600 settlements a minute. A merchant over that limit answers with a retryAfter in seconds too. When an error carries retryAfter, do not try again before that delay.

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. Set dailyBudget for a cumulative ceiling on top of the per-request cap: without it, nothing limits how many capped payments a day can sign (BUDGET_EXCEEDED once reached).
  • 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 typed authorizations (EIP-712) signed locally: EIP-3009 transfers, and the deposit and redeem authorizations of sUSDp. Only the signed authorizations travel. The key does cross the process boundary to the local signing engine (a child process of your own user, one per wallet, kept between payments), along with the settings it needs to start and reach the chain: ALCHEMY_API_KEY, the system paths, and the runtime's own settings (proxy, certificates, TLS and OpenSSL, NODE_OPTIONS, ELECTRON_RUN_AS_NODE). The rest of your environment is not handed to it, and nothing travels on its stdin but payment requests. The engine runs with NODE_ENV=production and its runtime warnings silenced.
  • Plain http is refused unless you set allowInsecure, except on localhost, 127.0.0.1 and [::1]. Over plain http, nothing shows that the invoice comes from the merchant.
  • 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, started once per wallet and kept between payments — no global installs, no downloads at payment time. A short-lived process still exits on its own: the engine holds it only while a payment is being prepared, and exits when it goes. A process that pays from many wallets keeps one engine per wallet. The engine reads its environment when it starts, at the wallet's first payment. It reads the chain through public RPC endpoints, and through Alchemy first, on the chains Alchemy serves, when ALCHEMY_API_KEY is set. Behind a proxy, the engine gets the same proxy and certificate environment variables as your process. Bun follows HTTPS_PROXY. Node follows it only with NODE_USE_ENV_PROXY=1, on the releases that support it (Node 22.22 does, Node 23.11 does not). Payments that come together are signed together: the engine prepares them concurrently, and each reads the wallet's balances for itself, so none accounts for what the others have signed. Fund the wallet for the payments you expect to sign together: the ones its balance cannot cover are refused by the facilitator. Browser support and viem Account signers (hardware and remote) are planned.