In this quickstart, you will deposit USDC into a Gateway Wallet, pay for an
x402-protected resource without gas fees, and check your balance. By the end,
you’ll have a working client that can make gasless payments to any
x402-compatible API that supports Circle Gateway.
Prerequisites
Before you begin, ensure you have:
Nanopayments require an EOA wallet. Smart contract account (SCA) wallets are not
supported because Gateway verifies payment signatures offchain using
ecrecover, which is incompatible with EIP-1271 contract signatures.
Step 1. Set up your project
1.1. Create the project and install dependencies
# Set up your directory and initialize a Node.js project
mkdir nanopayments-buyer
cd nanopayments-buyer
npm init -y
# Set up module type and start command
npm pkg set type=module
npm pkg set scripts.pay="tsx --env-file=.env pay.ts"
# Install runtime dependencies
npm install @circle-fin/x402-batching viem tsx typescript
# Install dev dependencies
npm install --save-dev @types/node
This step is optional. It helps prevent missing types in your IDE or editor.
Create a tsconfig.json file:
Then, update the tsconfig.json file:
cat <<'EOF' > tsconfig.json
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"types": ["node"]
}
}
EOF
1.3. Set environment variables
Open .env in your editor and add:
PRIVATE_KEY=YOUR_PRIVATE_KEY
PRIVATE_KEY is the private key for the EOA you use to deposit USDC and sign
nanopayment authorizations.
Open .env in your editor rather than writing values with shell commands, and
add .env to your .gitignore. This prevents credentials from leaking into
your shell history or version control.
The npm run pay command loads variables from .env using Node.js native
env-file support.
This example uses one or more private keys for local testing. In production,
use a secure key management solution and never expose or share private keys.
Step 2: Initialize the client
Create a new file pay.ts and initialize the GatewayClient with your chain
and private key:
import { GatewayClient } from "@circle-fin/x402-batching/client";
const client = new GatewayClient({
chain: "arcTestnet",
privateKey: process.env.PRIVATE_KEY as `0x${string}`,
});
The chain parameter determines which blockchain the client connects to for
deposits and withdrawals. See the
SDK reference for all supported chain
names.
Step 3: Deposit USDC into Gateway
Before you can make gasless payments, deposit USDC from your wallet into the
Gateway Wallet contract. This is a one-time onchain transaction:
const balances = await client.getBalances();
console.log(`Gateway balance: ${balances.gateway.formattedAvailable} USDC`);
// 1 USDC = 1_000_000 base units (6 decimals)
if (balances.gateway.available < 1_000_000n) {
console.log("Depositing 1 USDC...");
const deposit = await client.deposit("1");
console.log(`Deposit tx: ${deposit.depositTxHash}`);
}
getBalances() calls the
Get Token Balances API
endpoint. The deposit itself is an onchain transaction and does not use the
Gateway API.
After the deposit confirms, your Gateway balance can be used for gasless
payments to any supported seller. See the discussion at
Fast deposits about
increasing deposit speeds.
Step 4: Pay for a resource
Add the payment logic to pay.ts. Call client.pay() with the URL of an
x402-protected resource. The client handles the full payment flow automatically:
- Sends the initial request to the URL.
- Receives the
402 Payment Required response with payment details.
- Signs an EIP-3009 authorization offchain (zero gas).
- Retries the request with the
PAYMENT-SIGNATURE header.
const url = "http://localhost:3000/premium-data";
const { data, status } = await client.pay(url);
console.log(`Status: ${status}`);
console.log("Response:", data);
Under the hood, pay() negotiates the 402 flow and submits the payment
through the Settle x402 Payment
API endpoint.
Don’t have a seller URL to test with? Set up a local test API in two minutes
using the seller quickstart.
Step 5: Check your balance
Add balance checking after the payment using the
Get Token Balances API
endpoint:
const updated = await client.getBalances();
console.log(`Wallet USDC: ${updated.wallet.formatted}`);
console.log(`Gateway available: ${updated.gateway.formattedAvailable}`);
Step 6: Run the script
Run the complete script:
You should see the deposit transaction (if needed), the response from the paid
resource, and your updated balance.
Step 7: Withdraw funds (optional)
You can withdraw USDC from Gateway back to your wallet at any time. Same-chain
withdrawals are instant:
const result = await client.withdraw("5");
console.log(`Withdrew ${result.formattedAmount} USDC`);
console.log(`Tx: ${result.mintTxHash}`);
To withdraw to a different blockchain:
const crossChain = await client.withdraw("5", { chain: "baseSepolia" });
console.log(`Withdrew to ${crossChain.destinationChain}`);
Crosschain withdrawals require native gas tokens on the destination blockchain
to cover the minting transaction.
Check support before paying
Before attempting a payment, you can verify that the target URL supports Gateway
batching. The supports() method requests the target URL, checks for a 402
response, and inspects the PAYMENT-REQUIRED header for a compatible Gateway
batching option:
const support = await client.supports(url);
if (!support.supported) {
console.error("This URL does not support Gateway payments");
} else {
const { data } = await client.pay(url);
}
This is useful when building clients that interact with APIs where Gateway
support is not guaranteed.