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.
Before you start
Section titled “Before you start”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.
1. Install
Section titled “1. Install”# Base / EVM — what the rest of this page usesnpm install tx402 @x402/evm viem# Solana insteadnpm install tx402 @solana-program/token @solana/kit @x402/svm viemNode 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.
pip install "tx402[evm]" # or tx402[svm], or tx402[all]Python 3.10 or newer. The bare tx402 install has no chain library; the extra adds one.
2. Start a merchant to pay
Section titled “2. Start a merchant to pay”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.
git clone --depth 1 https://github.com/neogeeks/tx402 && cd tx402node tools/test-merchant/cli.js \ --requirements baseSepolia \ --facilitator https://x402.org/facilitatorIt 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:
npx tx402 call <merchant-url> --max-spend "0.10 USDC" --dry-runYou 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.
4. Point tx402 at your key
Section titled “4. Point tx402 at your key”# Base Sepolia — 0x-prefixed 32-byte hexexport TX402_DEV_PRIVATE_KEY=0x...
# Solana Devnet — the JSON array a `solana-keygen` file contains, or its base58 formexport 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.
5. Make the paid call
Section titled “5. Make the paid call”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.
6. Do the same from code
Section titled “6. Do the same from code”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());import os
from tx402 import Policy, Tx402Clientfrom tx402.signers import keypair_to_solana_signer, private_key_to_evm_signer
# Configure the chain you funded. Both is fine — the merchant's offer decides.with Tx402Client( evm_signer=private_key_to_evm_signer(os.environ["TX402_DEV_PRIVATE_KEY"]), solana_signer=keypair_to_solana_signer(os.environ["TX402_DEV_SOLANA_KEYPAIR"]), policy=Policy( max_per_request="0.10 USDC", max_per_hour="1.00 USDC", allowed_networks=["eip155:84532", "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1"], ), # Only because the merchant above is on localhost. Never set this for a real merchant. allow_insecure_localhost=True,) as tx402: response = tx402.get("<merchant-url>") print(response.status_code, response.text)Runnable versions of both are in
examples/.
What just happened
Section titled “What just happened”- tx402 sent your request normally. The merchant answered
402with aPAYMENT-REQUIREDheader listing what it accepts. - tx402 decoded and validated that challenge strictly — size, depth, duplicate keys, the resource origin against the URL you asked for.
- Your policy ran, in a fixed order: domain, network, scheme and asset, per-request cap, rolling hourly cap. Nothing had touched a key yet.
- tx402 read your balance on each offered chain, concurrently, and ranked the routes.
- It reserved the amount from your local budget — atomically, before signing.
- Your signer produced exactly one authorization, with a fresh nonce.
- tx402 retried the request once, carrying
PAYMENT-SIGNATURE. - The merchant settled and answered. tx402 read
PAYMENT-RESPONSEand 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.
If it did not work
Section titled “If it did not work”| 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.