Skip to content
DexterDocs
x402 SDK / Node.js Payments
Sections

Node.js Payments

payAndFetch handles an endpoint's HTTP 402 payment challenge and returns the payment result alongside the API response. Set a spending cap before making the request.

  1. The client requests the resource.
  2. The API returns HTTP 402 and its payment requirements.
  3. The client authorizes payment and retries with its proof.
  4. The API asks the facilitator to verify and settle the payment.
  5. The facilitator returns the settlement result.
  6. After successful settlement, the API returns the resource and receipt.

Before You Start

Use Node 22 or newer and an endpoint that accepts Solana USDC through x402. You need its URL, HTTP method, and expected price. The example below makes a GET request and allows at most 0.01 USDC.

The wallet needs USDC on Solana mainnet. Dexter's facilitator pays the transaction fee when it is the fee payer advertised by the endpoint; that path does not require SOL in the buyer's wallet. Other facilitators can have different funding requirements.

For a browser-connected wallet, use the React guide. To create a paid endpoint, start with Accept a Payment.

Install

mkdir paid-api-example
cd paid-api-example
npm init -y
npm install @dexterai/x402@6.0.0 @dexterai/vault@0.43.4 @solana/web3.js@1 viem@2
printf '\n.dexter-wallet.json\n' >> .gitignore

The client entry point uses viem even when your request uses Solana. Save the following examples with the .mjs filenames shown.

Create and Fund a Wallet

This script creates a keypair once, writes the secret to a local file with owner-only permissions, and prints the receiving address. It refuses to overwrite an existing file.

generate-wallet.mjs
import { writeFileSync } from 'node:fs';
import { Keypair } from '@solana/web3.js';

const wallet = Keypair.generate();
writeFileSync(
  '.dexter-wallet.json',
  JSON.stringify(Array.from(wallet.secretKey)),
  { mode: 0o600, flag: 'wx' },
);
console.log('Fund this address with Solana USDC:', wallet.publicKey.toBase58());
node generate-wallet.mjs

Send USDC on Solana to the printed address. Keep .dexter-wallet.json private and out of source control. In a deployed agent, supply the key through your secret manager instead of committing or copying this file into a container image.

Check the balance before paying:

check-balance.mjs
import { readFileSync } from 'node:fs';
import { Connection, Keypair, PublicKey } from '@solana/web3.js';
import { USDC_MINT } from '@dexterai/x402/client';

const secret = Uint8Array.from(JSON.parse(readFileSync('.dexter-wallet.json', 'utf8')));
const wallet = Keypair.fromSecretKey(secret);
const connection = new Connection(
  process.env.SOLANA_RPC_URL ?? 'https://api.mainnet-beta.solana.com',
  'confirmed',
);
const accounts = await connection.getParsedTokenAccountsByOwner(
  wallet.publicKey,
  { mint: new PublicKey(USDC_MINT) },
);
const atomic = accounts.value.reduce(
  (sum, { account }) => sum + BigInt(account.data.parsed.info.tokenAmount.amount),
  0n,
);
console.log('Address:', wallet.publicKey.toBase58());
console.log('USDC atomic balance:', atomic.toString());
node check-balance.mjs

USDC has six decimal places: an atomic amount of 10000 is 0.01 USDC. An RPC error stops the script. A successful read with no USDC accounts prints zero. Use a dedicated SOLANA_RPC_URL for unattended operation.

Make One Capped Request

Replace the endpoint below with the GET endpoint you intend to buy from. Running this command authorizes a payment up to the cap when that endpoint requests one.

pay.mjs
import { readFileSync } from 'node:fs';
import {
  createKeypairWallet,
  getPaymentReceipt,
  payAndFetch,
} from '@dexterai/x402/client';

const url = process.env.PAID_API_URL;
if (!url) throw new Error('Set PAID_API_URL to your paid GET endpoint.');

const secret = JSON.parse(readFileSync('.dexter-wallet.json', 'utf8'));
const solana = await createKeypairWallet(secret);
const result = await payAndFetch(
  url,
  { method: 'GET' },
  { solana },
  { maxAmountAtomic: '10000', solanaRpcUrl: process.env.SOLANA_RPC_URL },
);

if (!result.ok) {
  console.error('Payment result:', result.reason, result.detail ?? '');
  process.exitCode = 1;
} else {
  console.log('Paid:', result.paid);
  if (result.paid) {
    console.log('USDC amount (atomic):', result.amountPaid);
    console.log('Network:', result.network.caip2);
    console.log('Transaction:', result.txSignature ?? 'not supplied');
  }

  if (!result.response) {
    console.error('Payment confirmed, but no API response arrived. Do not pay again.');
    process.exitCode = 1;
  } else {
    console.log('HTTP status:', result.response.status);
    console.log('Receipt:', getPaymentReceipt(result.response));
    console.log('Response:', await result.response.text());
    if (!result.response.ok) process.exitCode = 1;
  }
}
export PAID_API_URL='https://your-api.example.com/paid/route'
node pay.mjs

For a successful 0.002 USDC purchase, the output includes Paid: true and USDC amount (atomic): 2000. The response content depends on the endpoint. Transaction and receipt fields can be absent; preserve any identifiers the server supplies.

An endpoint can return an HTTP error without asking for payment. In that case paid is false; inspect response.status to determine whether the API request succeeded.

POST Requests

Pass a serialized body so the client can resend it after the 402 challenge:

const request = {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({ prompt: 'Your request' }),
};

Use request as the second argument to payAndFetch. Its paid retry requires a string body; FormData, buffers, and request streams are unsupported by this entry point.

Payment Failures

ResultNext action
insufficient_fundsCheck the wallet's USDC balance and the endpoint's network.
budget_exceededCompare the quoted amount with maxAmountAtomic.
unsupported_network or no_payment_optionsCheck the payment options against the wallet you supplied.
timeoutThe SDK reports that payment was not dispatched. Retry after addressing the cause.
payment_unconfirmed, settlement_failed, or errorReconcile the attempt before authorizing another payment.
Paid result without a responseRecover the response using the available receipt or transaction; avoid paying again.

The error reference explains the result types and retry rules. For repeated requests, add a budget account.

EVM Wallets

Use createEvmKeypairWallet with a hex private key and pass the resulting wallet in the evm slot. Fund the asset on the network the endpoint charges on. For example, a quote for USDC on Base uses that network's USDC balance. The maxAmountAtomic cap uses the asset's decimal scale; do not assume six decimals for every token or network.

Last Updated:

On this page