End-to-end demonstrations of the x402 payment protocol using the @quicknode/x402 package. Supports three payment models: per-request ($0.001/request, no auth), credit drawdown (SIWX auth + bulk credits), and nanopayment ($0.0001/request via Circle Gateway). Covers all supported protocols: JSON-RPC, REST, gRPC-Web, and WebSocket across EVM (Base Sepolia, Polygon Amoy, Polygon Mainnet, XLayer Testnet, XLayer Mainnet, Arc Testnet) and Solana (Devnet) wallets. Automatically creates a wallet, authenticates via SIWX (credit drawdown), funds with testnet stablecoins (Base Sepolia only via /drip), and makes paid requests.
Five example scripts showcase the complete SIWX + x402 v2 flow across different protocols. The bootstrap process auto-detects your chain type from .env and runs the appropriate auth and funding path. An interactive CLI (npm start) lets you select chain type, payment network, payment model (including nanopayment), and example to run.
flowchart TB
subgraph Detection["Chain Detection (.env)"]
D{SOLANA_PRIVATE_KEY?}
end
subgraph EVM["EVM Path"]
EA[Generate EVM Wallet] --> EX{X402_EVM_CHAIN?}
EX -->|base-sepolia| EB[Authenticate with SIWE]
EX -->|polygon-amoy| EB
EX -->|polygon-mainnet| EB
EX -->|xlayer-testnet| EB
EX -->|xlayer-mainnet| EB
EX -->|arc-testnet| EB
EB --> EC[Receive JWT Token]
EC --> ED{Check USDC Balance}
ED -->|Insufficient + hasFaucet| EE[Request from /drip Faucet]
EE --> ED
ED -->|Insufficient + no faucet| EF[Pre-fund Required]
end
subgraph Solana["Solana Path"]
SA[Generate Solana Keypair] --> SB[Authenticate with SIWX]
SB --> SC[Receive JWT Token]
SC --> SD[Pre-funded USDC Required]
end
D -->|Yes| SA
D -->|No| EA
subgraph Scripts["Example Scripts"]
ED -->|Sufficient| G["jsonrpc.ts — eth_blockNumber loop"]
ED -->|Sufficient| H["rest.ts — Aptos REST GET endpoints"]
ED -->|Sufficient| I["grpc.ts — Flow gRPC-Web (unary + streaming)"]
ED -->|Sufficient| J["websocket.ts — Ethereum newHeads subscription"]
SD --> G
SD --> H
SD --> I
SD --> J
end
| Script | Protocol | Network | Description |
|---|---|---|---|
bootstrap.ts |
All | — | Runs setup once, launches all 4 scripts in parallel via stmux |
jsonrpc.ts |
JSON-RPC | Base Sepolia | eth_blockNumber credit consumption loop |
rest.ts |
REST | Aptos Mainnet | HTTP GET endpoints (ledger info, blocks, accounts, transactions) |
grpc.ts |
gRPC-Web | Flow Mainnet | Unary calls (Ping, GetLatestBlock) + streaming (SubscribeBlocksFromLatest) |
websocket.ts |
WebSocket | Base Mainnet | Real-time newHeads subscription with non-blocking credit polling |
nanopayment.ts |
JSON-RPC | Base Sepolia | Circle Gateway nanopayment loop ($0.0001/request) |
-
Chain Detection -- Reads
.envforSOLANA_PRIVATE_KEY. If present, uses the Solana path; otherwise defaults to EVM. -
Wallet Management -- On first run, generates a new private key and saves it to
.env. Subsequent runs reuse the existing wallet.- EVM: Ethereum private key (
PRIVATE_KEY) - Solana: Ed25519 keypair encoded as Base58 (
SOLANA_PRIVATE_KEY)
- EVM: Ethereum private key (
-
SIWX Authentication -- Uses
@quicknode/x402withpreAuth: trueto handle SIWX authentication automatically (no manual SIWE/SIWX message construction). The JWT is valid for 1 hour.- EVM: SIWE (EIP-4361) via
@quicknode/x402 - Solana: SIWX with Ed25519 signature via
@quicknode/x402
- EVM: SIWE (EIP-4361) via
-
Token Funding -- Checks the wallet's payment token balance. If insufficient, requests testnet tokens from the
/dripendpoint (backed by CDP faucet). Base Sepolia only -- Polygon, XLayer, and Solana wallets must be pre-funded. XLayer uses USDG (not USDC). -
Paid Requests -- Uses
@quicknode/x402client.fetch with automatic SIWX auth, x402 payment, and JWT session management. When credits are exhausted, the 402 response triggers automatic x402 payment signing.- EVM: EIP-712 typed-data signature via
@quicknode/x402 - Solana: Ed25519 signature via
@quicknode/x402
- EVM: EIP-712 typed-data signature via
-
Credit Tracking -- Each script tracks payments, credits consumed, and USDC spent, then reports a summary.
- Node.js 18+
- npm
- Running x402 worker (deployed at
https://x402.quicknode.comby default)
The Solana flow requires manual keypair and USDC setup because detectChainType() only activates the Solana path when .env contains a non-empty SOLANA_PRIVATE_KEY, and the /drip faucet is EVM-only:
-
Generate a keypair -- Use the one-liner below (or any Solana keygen tool). This creates a
.envwith a new keypair and prints your wallet address:npx tsx -e " import nacl from 'tweetnacl'; import bs58 from 'bs58'; import { writeFileSync } from 'fs'; const kp = nacl.sign.keyPair(); writeFileSync('.env', 'SOLANA_PRIVATE_KEY=' + bs58.encode(Buffer.from(kp.secretKey)) + '\n'); console.log('Wallet:', bs58.encode(Buffer.from(kp.publicKey))); "
-
Fund with Solana Devnet USDC -- Send Devnet USDC to the wallet address printed above. The USDC mint on Solana Devnet is
4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU. You do not need SOL -- the x402 facilitator pays transaction fees. -
Run -- With USDC in the wallet,
npm startbootstraps auth and launches all examples.
To switch from EVM to Solana (or vice versa), edit your .env file. The bootstrap detects the chain based on which key is present:
# EVM mode (default)
PRIVATE_KEY=0xabc123...
# Solana mode (requires a non-empty value)
SOLANA_PRIVATE_KEY=Ht8NP8...# Install dependencies
npm install
# Run all 4 examples in parallel (via stmux)
npm start
# Or run individual examples
npm run start:jsonrpc # JSON-RPC demo
npm run start:rest # REST demo (Aptos)
npm run start:grpc # gRPC-Web demo (Flow)
npm run start:ws # WebSocket demo (Ethereum)
npm run start:nanopayment # Nanopayment demo (Circle Gateway)# Requires pre-funded USDC (no /drip faucet for Polygon)
X402_EVM_CHAIN=polygon-amoy npm start# Requires pre-funded USDG (no /drip faucet for XLayer)
X402_EVM_CHAIN=xlayer-testnet npm start# Requires pre-funded USDC (no /drip faucet for Arc)
X402_EVM_CHAIN=arc-testnet npm start
# Or run nanopayment example specifically
X402_EVM_CHAIN=arc-testnet npm run start:nanopayment# Install dependencies
npm install
# Generate a Solana keypair (see Solana Prerequisites above for the one-liner)
# Then fund the printed wallet address with Devnet USDC
# Run all 4 examples
npm startEach script accepts a network override via environment variable:
| Script | Env Variable | Default |
|---|---|---|
| All (EVM) | X402_EVM_CHAIN |
base-sepolia (options: base-sepolia, base-mainnet, polygon-amoy, polygon-mainnet, xlayer-testnet, xlayer-mainnet, arc-testnet) |
| All (Solana) | X402_SOLANA_CHAIN |
solana-devnet (options: solana-devnet, solana-mainnet) |
jsonrpc.ts |
JSONRPC_NETWORK |
base-sepolia |
rest.ts |
REST_NETWORK |
aptos-mainnet |
grpc.ts |
X402_GRPC_BASE_URL |
{X402_BASE_URL}/flow-mainnet |
websocket.ts |
WS_NETWORK |
base-mainnet |
All scripts read the X402_BASE_URL environment variable to determine the x402 worker endpoint. This is useful for local development or pointing at a different deployment:
# Use a local worker
X402_BASE_URL=http://localhost:8787 npm start
# Use a custom deployment
X402_BASE_URL=https://my-worker.example.com npm start| Setting | Default | Description |
|---|---|---|
X402_BASE_URL |
https://x402.quicknode.com |
x402 worker URL (all endpoints derive from this) |
X402_GRPC_BASE_URL |
{X402_BASE_URL}/flow-mainnet |
Optional gRPC endpoint override |
Defined in lib/x402-helpers.ts:
| Setting | Value | Description |
|---|---|---|
MIN_TOKEN_BALANCE |
$0.005 |
Minimum EVM balance before requesting faucet |
EVM_CHAIN_ID |
Resolved from X402_EVM_CHAIN |
Numeric chain ID for SIWE (e.g., 84532, 80002, 137) |
EVM_CAIP2 |
Resolved from X402_EVM_CHAIN |
CAIP-2 identifier (e.g., eip155:84532, eip155:80002) |
EVM_TOKEN_ADDRESS |
Resolved from X402_EVM_CHAIN |
Payment token contract address for the selected chain (USDC or USDG) |
SOLANA_CAIP2 |
Resolved from X402_SOLANA_CHAIN |
CAIP-2 identifier for the selected Solana chain |
qn-x402-examples/
├── bootstrap.ts # Orchestrator: chain detection, auth, funding, launches stmux
├── jsonrpc.ts # JSON-RPC example (eth_blockNumber loop)
├── rest.ts # REST example (Aptos blockchain GET endpoints)
├── grpc.ts # gRPC-Web example (Flow: unary + streaming)
├── websocket.ts # WebSocket example (Ethereum newHeads subscription)
├── nanopayment.ts # Nanopayment example (Circle Gateway, $0.0001/request)
├── lib/
│ └── x402-helpers.ts # Shared: wallet (EVM + Solana), auth, credits, faucet, x402 fetch
├── proto/
│ └── flow/ # Flow Access API proto definitions
├── gen/ # Generated protobuf types (via buf generate)
├── package.json
├── tsconfig.json
├── buf.yaml # Buf configuration
├── buf.gen.yaml # Buf code generation config
└── .gitignore # Excludes .env
The scripts automatically manage the .env file:
| Variable | Description |
|---|---|
PRIVATE_KEY |
Auto-generated EVM wallet private key (hex) |
SOLANA_PRIVATE_KEY |
Auto-generated Solana keypair (Base58-encoded 64-byte secret key) |
X402_BASE_URL |
Override the x402 worker URL (default: https://x402.quicknode.com) |
X402_EVM_CHAIN |
EVM auth/payment chain: base-sepolia, base-mainnet, polygon-amoy, polygon-mainnet, xlayer-testnet, xlayer-mainnet, arc-testnet (default: base-sepolia) |
X402_PAYMENT_MODEL |
Payment model: pay-per-request, credit-drawdown, or nanopayment (default: credit-drawdown) |
X402_SOLANA_CHAIN |
Solana auth/payment chain: solana-devnet, solana-mainnet (default: solana-devnet) |
X402_GRPC_BASE_URL |
Override the gRPC-Web endpoint (default: {X402_BASE_URL}/flow-mainnet) |
JSONRPC_NETWORK |
Override JSON-RPC network (default: base-sepolia) |
REST_NETWORK |
Override REST network (default: aptos-mainnet) |
WS_NETWORK |
Override WebSocket network (default: base-mainnet) |
Security Note: The .env file contains your private key. It's excluded from git via .gitignore, but never share it or commit it to version control.
All scripts share a common library that provides:
| Export | Description |
|---|---|
setupExample(tracker) |
Unified setup using @quicknode/x402 (creates client, auth, funding) |
createClientForChain() |
Create a @quicknode/x402 client for the detected chain type |
createTrackingFetch(client, tracker) |
Wraps client.fetch with payment tracking and maxPayments cap |
getCredits(getToken) |
Check credit balance via /credits |
createCreditPoller(getToken) |
Non-blocking background credit updates |
PaymentTracker |
Tracks payment counts, fetch calls, and maxPayments cap |
Two-phase demo:
- Credential check -- Authenticate, fund wallet, verify credits
- Credit consumption loop --
eth_blockNumbercalls until credits exhausted, showing per-request credit delta
Two-phase demo using Aptos blockchain REST API:
- Individual REST calls --
GET /v1/(ledger info),GET /v1/blocks/by_height/{h},GET /v1/accounts/0x1,GET /v1/accounts/0x1/resources,GET /v1/transactions/by_version/1 - Credit consumption loop -- Lightweight
GET /v1/calls until credits exhausted
Two-phase demo using Flow blockchain gRPC-Web:
- Unary calls --
Ping,GetLatestBlockvia@connectrpc/connect-webwith custom x402 fetch transport - Streaming --
SubscribeBlocksFromLatestwith AbortController for graceful cancellation
Real-time subscription demo:
- Subscribes to
newHeadson Ethereum viaeth_subscribe - Uses
CreditPollerfor non-blocking credit tracking (fire-and-forget HTTP) - Handles credit exhaustion via close code
4402or JSON-RPC error - Auto re-authenticates on token expiry
Note: The
@quicknode/x402package handles authentication automatically viapreAuth: true. The manual flow below is shown for reference only.
const siweMessage = new SiweMessage({
domain: new URL(X402_BASE_URL).host,
address: walletClient.account.address,
statement: SIWX_STATEMENT,
uri: X402_BASE_URL,
version: '1',
chainId: EVM_CHAIN_ID,
nonce: generateNonce(),
issuedAt: new Date().toISOString(),
});
const message = siweMessage.prepareMessage();
const signature = await walletClient.signMessage({ message });
const response = await fetch(`${X402_BASE_URL}/auth`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message, signature }),
});
const { token, accountId, expiresAt } = await response.json();const message = formatSiwsMessage({
domain: new URL(X402_BASE_URL).host,
address: keypair.address, // Base58 public key
statement: SIWX_STATEMENT,
uri: X402_BASE_URL,
version: '1',
chainRef: SOLANA_DEVNET_CHAIN_REF,
nonce: generateSiwxNonce(),
issuedAt: new Date().toISOString(),
});
const messageBytes = new TextEncoder().encode(message);
const signatureBytes = nacl.sign.detached(messageBytes, keypair.secretKey);
const signature = bs58.encode(Buffer.from(signatureBytes));
const response = await fetch(`${X402_BASE_URL}/auth`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ message, signature, type: 'siwx' }),
});
const { token, accountId, expiresAt } = await response.json();const response = await fetch(`${X402_BASE_URL}/credits`, {
headers: { 'Authorization': `Bearer ${jwtToken}` },
});Two payment models are available:
- Request -- Send
PAYMENT-SIGNATUREheader with each request (no JWT needed) - Verify -- Worker verifies the payment signature via the facilitator
- Proxy -- Request is forwarded to the upstream RPC endpoint
- Error check -- If upstream returns an error, it's passed through without charge
- Settle -- On success only, the payment is settled on-chain
- Response -- Includes
PAYMENT-RESPONSEheader confirming payment
Use paymentModel: 'pay-per-request' in the client config. Ideal for AI agents and quick prototyping.
- Request -- Your request includes JWT Bearer token
- 402 Response -- Worker returns
402 Payment RequiredwithPAYMENT-REQUIREDheader - Payment --
@quicknode/x402automatically signs a payment authorization- EVM: EIP-712 stablecoin payment on the configured chain (USDC on Base/Polygon, USDG on XLayer)
- Solana: Ed25519 USDC payment on Solana Devnet
- Settlement -- The x402 facilitator settles the payment on-chain
- Credits -- Your account receives RPC credits (1,000 per payment on testnet)
- Retry -- The original request completes successfully
- Response -- Includes
PAYMENT-RESPONSEheader confirming payment
Use paymentModel: 'credit-drawdown' (default) or omit paymentModel. Ideal for high-volume usage.
- Deposit -- Before making requests, deposit USDC into the Circle Gateway Wallet contract (one-time on-chain tx)
- Request -- Send
PAYMENT-SIGNATUREheader with each request (no JWT needed) - Batch Settlement -- Payments are batched and settled via Circle Gateway at $0.0001/request
- Response -- Includes
PAYMENT-RESPONSEheader confirming payment
Use paymentModel: 'nanopayment' in the client config. EVM testnets only (Base Sepolia, Polygon Amoy, Arc Testnet).
All flows are handled automatically by the @quicknode/x402 client — you just make fetch calls!
Check that:
- The worker is running at
X402_BASE_URL SIWX_STATEMENTmatches the server config- System clock is synchronized (SIWX messages expire)
- For Solana: the
type: 'siwx'field is included in the auth request
JWT tokens expire after 1 hour. The examples automatically re-authenticate when this happens.
The /drip endpoint only supports EVM wallets. To fund a Solana wallet with Devnet USDC:
- Acquire Devnet USDC (mint:
4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU) - Send USDC to your wallet address
- You do not need SOL -- the x402 facilitator pays transaction fees
The CDP faucet has rate limits and is one-time per account. Options:
- Wait a few minutes and try again
- Manually visit faucet.circle.com and enter your wallet address
- Use a different wallet (delete
.envto generate a new one)
Check:
- Wallet has sufficient USDC balance
- x402 facilitator is accessible
- Network connectivity
- For Solana: ensure wallet has sufficient USDC (the facilitator pays SOL transaction fees)
| Package | Purpose |
|---|---|
@quicknode/x402 |
Quicknode x402 client (SIWX auth, payment, session management) |
viem |
Ethereum client library |
tweetnacl |
Ed25519 cryptography (Solana) |
bs58 |
Base58 encoding for Solana keys and signatures |
@connectrpc/connect |
Connect-RPC core |
@connectrpc/connect-web |
Connect-RPC browser transport (custom fetch for x402) |
@bufbuild/protobuf |
Protobuf runtime |
dotenv |
Environment variable management |
tsx |
TypeScript execution |
stmux |
Terminal multiplexer (bootstrap) |