Skip to content

Quickstart

This page is the one tx402 is measured against: a fresh user gets to a real settled payment in under five minutes without reading source code (SPEC §16). If it takes longer than that, that is a bug in this page.

Everything here happens on testnets with valueless tokens. Nothing on this page can spend real money.

You need one funded testnet wallet. Pick either chain — you do not need both.

  • Base Sepolia — a little Sepolia ETH for gas, and some testnet USDC. Faucets: Coinbase for ETH, Circle for USDC.
  • Solana Devnet — some Devnet SOL and Devnet USDC. solana airdrop 1 <address> --url devnet, then Circle’s faucet for USDC.
Terminal window
# Base / EVM — what the rest of this page uses
npm install tx402 @x402/evm viem
Terminal window
# Solana instead
npm install tx402 @solana-program/token @solana/kit @x402/svm viem

Node 20 or newer. The adapters ship in the same package behind subpath exports, but the chain runtimes they call are optional peer dependencies, so npm does not install them for you — that is what keeps import "tx402" free of any chain library. npm install tx402 on its own gives you the core and the CLI, which is enough for --dry-run.

x402 is young enough that public demo merchants come and go, so this page does not send you to one it cannot promise is up. Run one instead — it is a single command, it speaks real x402 v2, and with --facilitator it performs a real settlement on the testnet, so the payment below moves real testnet USDC and shows up on a block explorer.

Terminal window
git clone --depth 1 https://github.com/neogeeks/tx402 && cd tx402
node tools/test-merchant/cli.js \
--requirements baseSepolia \
--facilitator https://x402.org/facilitator

It prints one JSON line and keeps running:

{ "url": "http://127.0.0.1:54321", "port": 54321, "scenario": "pay-once", "settles": true }

Use that url wherever this page says <merchant-url> — the path is /resource, so the full URL is http://127.0.0.1:54321/resource. Swap --requirements baseSepolia for solanaDevnet to be offered a Solana route instead, or pass both, comma-separated, to be offered a choice and watch the router pick.

3. See what the merchant is asking, without paying

Section titled “3. See what the merchant is asking, without paying”

No key, no signature, no money — just the challenge and what tx402 would do with it:

Terminal window
npx tx402 call <merchant-url> --max-spend "0.10 USDC" --dry-run

You will see the requirement count, the route tx402 would take, the atomic amount, and the candidate ranking. --dry-run never invokes a signer and never reserves budget, so you can run it as many times as you like.

Add --json to get one machine-readable object on stdout instead.

Terminal window
# Base Sepolia — 0x-prefixed 32-byte hex
export TX402_DEV_PRIVATE_KEY=0x...
# Solana Devnet — the JSON array a `solana-keygen` file contains, or its base58 form
export TX402_DEV_SOLANA_KEYPAIR="$(cat ~/.config/solana/id.json)"

Set whichever matches the chain you funded. Setting both is fine: tx402 offers the router two signers and the merchant’s own offer decides which one is used.

tx402 prints a warning to stderr every time it reads this variable. That is deliberate, not noise: anything that can read this process’s environment can read the key.

The CLI accepts no flag that carries a key, and never will — a flag lands in shell history, in ps output, and in CI logs.

Terminal window
npx tx402 call <merchant-url> --max-spend "0.10 USDC"

The response body goes to stdout and nothing else does, so > out.json gives you a clean file. Diagnostics go to stderr.

Exit 0 means the resource was delivered. Any other code is classified — see the error reference.

quickstart.ts
import { createTx402Client } from "tx402";
import { keypairToSolanaSigner, privateKeyToEvmSigner } from "tx402/signers";
const tx402 = createTx402Client({
// Configure the chain you funded. Both is fine — the merchant's offer decides.
signers: {
evm: privateKeyToEvmSigner(process.env.TX402_DEV_PRIVATE_KEY as `0x${string}`),
solana: await keypairToSolanaSigner(process.env.TX402_DEV_SOLANA_KEYPAIR!),
},
policy: {
maxPerRequest: "0.10 USDC",
maxPerHour: "1.00 USDC",
allowedNetworks: ["eip155:84532", "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1"],
},
// Only because the merchant above is on localhost. Never set this for a real merchant.
allowInsecureLocalhost: true,
});
const response = await tx402.fetch("<merchant-url>");
console.log(response.status, await response.text());

Runnable versions of both are in examples/.

  1. tx402 sent your request normally. The merchant answered 402 with a PAYMENT-REQUIRED header listing what it accepts.
  2. tx402 decoded and validated that challenge strictly — size, depth, duplicate keys, the resource origin against the URL you asked for.
  3. Your policy ran, in a fixed order: domain, network, scheme and asset, per-request cap, rolling hourly cap. Nothing had touched a key yet.
  4. tx402 read your balance on each offered chain, concurrently, and ranked the routes.
  5. It reserved the amount from your local budget — atomically, before signing.
  6. Your signer produced exactly one authorization, with a fresh nonce.
  7. tx402 retried the request once, carrying PAYMENT-SIGNATURE.
  8. The merchant settled and answered. tx402 read PAYMENT-RESPONSE and committed the reservation.

Steps 3 and 5 happening before step 6 is the property the whole design is built around. See the request lifecycle for the full picture.

Exit What it means Try
3 Your own cap refused it Raise --max-spend, or accept it — this is the guardrail working
4 Wallet cannot cover it Fund the wallet, and check you funded the network the merchant offered
5 No usable route or bad challenge Check the right extra is installed and a signer is configured
7 Network failure Transient; retry is safe here
8 Ambiguous Money may have moved. Do not retry — reconcile with the merchant first

Full detail for every code is in the error reference.