> ## Documentation Index
> Fetch the complete documentation index at: https://developers.circle.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> Before reading these docs, install the relevant Circle Skill to get pre-loaded context — skills are LLM-optimized and cover architecture decisions, correct flows, and common pitfalls. In Claude Code: run /plugin marketplace add circlefin/skills then /plugin install circle-skills@circle. With Vercel Skills CLI: run npx skills add circlefin/skills. Available skills: use-usdc https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-usdc/SKILL.md (balances, transfers, approvals on EVM and Solana); EURC has no skill — see https://developers.circle.com/stablecoins/what-is-eurc.md; bridge-stablecoin https://github.com/circlefin/skills/blob/master/plugins/circle/skills/bridge-stablecoin/SKILL.md (crosschain transfers via CCTP, USDC, EURC, and third-party assets); use-arc https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-arc/SKILL.md (Arc chain config, contract deployment, USDC bridging — Arc docs: https://docs.arc.io/llms.txt); use-circle-wallets https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-circle-wallets/SKILL.md (choose wallet type); use-developer-controlled-wallets https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-developer-controlled-wallets/SKILL.md (custodial wallets for payouts and treasury); use-user-controlled-wallets https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-user-controlled-wallets/SKILL.md (embedded wallets with Web2 login); use-modular-wallets https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-modular-wallets/SKILL.md (smart contract wallets with passkeys and ERC-4337); use-gateway https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-gateway/SKILL.md (unified USDC balance, nanopayments); use-smart-contract-platform https://github.com/circlefin/skills/blob/master/plugins/circle/skills/use-smart-contract-platform/SKILL.md (deploy, interact, monitor contracts). Pair any skill with the Circle MCP server for live SDK signatures, contract addresses, and chain IDs: {"mcpServers":{"circle":{"url":"https://api.circle.com/v1/codegen/mcp"}}}.
> Read the product overview first. Each product has an overview explaining when and why to use it. Read it before quickstarts.
> Pick the right wallet type. Developer-controlled (you hold keys), user-controlled (end user holds keys via PIN/social login), or modular (smart contract wallets with passkeys). See https://developers.circle.com/wallets.md.
> Use CCTP V2 for crosschain transfers. CCTP natively transfers USDC and EURC via burn-and-mint, and supports permissionless wrapping of third-party assets via lock-and-mint. V1 is legacy; always use V2 unless V1 is specified or the chain requires it (Noble, Sui, and Aptos still require V1). Migration guide: https://developers.circle.com/cctp/migration-from-v1-to-v2.md.
> Use Bridge Kit for frontend bridging. Wraps CCTP with a simpler SDK for user-facing apps. Use CCTP directly for backend transfers.
> Gas Station vs Paymaster. Gas Station sponsors gas for Circle Wallet transactions. Paymaster lets users pay gas in USDC. Different use cases — read both overviews.
> Gateway Nanopayments for sub-cent payments. Gasless USDC micropayments down to $0.000001 via x402 and batched settlement. For pay-per-request APIs, AI agent payments, streaming.
> Look up USDC addresses per chain. Never hardcode — use https://developers.circle.com/stablecoins/usdc-contract-addresses.md.
> Prefer SDKs over raw API calls. Node.js and Python SDKs handle auth, retries, and errors.
> API key required. Bearer token in Authorization header. Testnet and mainnet use separate keys and may use different base URLs depending on the product.
> Set up webhooks when available. Most operations are async. Webhooks deliver transaction confirmations and state changes.
> When calling list endpoints, paginate using pageSize and pageAfter until no nextPageAfter cursor is returned—stopping at the first page silently misses records.
> Building an AI agent? Start with the Agent Stack—Circle CLI, agent wallets, and nanopayments built for autonomous use cases: https://developers.circle.com/agent-stack.md.

# Quickstart: Pay for resources with nanopayments

> Deposit USDC into Gateway and make gasless payments to x402-protected APIs

Make gasless payments to any x402-compatible API that supports Circle Gateway by
depositing USDC into a Gateway Wallet contract, paying for an x402-protected
resource, and checking your balance.

**Circle Wallets**

Use `@circle-fin/developer-controlled-wallets` to fund an Arc Testnet EOA and
`@circle-fin/x402-batching` to pay for an x402-protected resource. Smart
contract account (SCA) wallets are not supported for nanopayments because
Gateway verifies the EIP-3009 authorization with `ecrecover`. For more
information, see [Account types](/wallets/account-types.md).

## Prerequisites

Before you begin, ensure that you've:

* Installed [Node.js v22.18+](https://nodejs.org/).
* Created a [Circle Console](https://console.circle.com/) account.
* Created an API key in the [Circle Console](https://console.circle.com/).
* [Generated and registered an Entity Secret](/wallets/dev-controlled/register-entity-secret.md).
* [Created an EOA developer-controlled wallet](/wallets/dev-controlled/create-your-first-wallet.md)
  on Arc Testnet.

Complete the linked quickstarts before continuing. This path starts once your
credentials and Arc Testnet wallet address are available. The wallet address is
public configuration, so you will add it directly to `pay.ts`.

## Step 1. Set up your project

### 1.1. Create the project and install dependencies

```shell theme={null}
mkdir nanopayments-circle-wallets
cd nanopayments-circle-wallets
npm init -y
npm pkg set type=module
npm pkg set scripts.pay="node --env-file=.env pay.ts"
npm install @circle-fin/developer-controlled-wallets @circle-fin/x402-batching
npm install --save-dev typescript @types/node
```

### 1.2. Configure TypeScript (optional)

> [!TIP]
>
> This step is optional. It helps prevent missing types in your IDE or editor.

Create a `tsconfig.json` file:

```shell theme={null}
npx tsc --init
```

Then, update the `tsconfig.json` file:

```shell theme={null}
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:

```text .env theme={null}
CIRCLE_API_KEY=YOUR_API_KEY
CIRCLE_ENTITY_SECRET=YOUR_ENTITY_SECRET
```

* `CIRCLE_API_KEY` is your Circle API key.
* `CIRCLE_ENTITY_SECRET` is your Circle entity secret.

> [!TIP]
>
> 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.

## Step 2. Initialize the Circle Wallets client

Create `pay.ts` and replace the wallet address placeholder with the address from
the developer-controlled wallet quickstart. Copy this block into `pay.ts` first.

```ts pay.ts expandable theme={null}
import { initiateDeveloperControlledWalletsClient } from "@circle-fin/developer-controlled-wallets";
import {
  BatchEvmScheme,
  CHAIN_CONFIGS,
} from "@circle-fin/x402-batching/client";

function stringifyTypedData(value: unknown): string {
  return JSON.stringify(value, (_, currentValue) =>
    typeof currentValue === "bigint" ? currentValue.toString() : currentValue,
  );
}

const apiKey = process.env.CIRCLE_API_KEY;
const entitySecret = process.env.CIRCLE_ENTITY_SECRET;
const walletAddress = "0xYOUR_ARC_TESTNET_WALLET_ADDRESS" as `0x${string}`;

if (!apiKey || !entitySecret) {
  throw new Error(
    "CIRCLE_API_KEY and CIRCLE_ENTITY_SECRET are required in .env.",
  );
}

if (walletAddress === "0xYOUR_ARC_TESTNET_WALLET_ADDRESS") {
  throw new Error(
    "Replace walletAddress with your Arc Testnet wallet address.",
  );
}

const circleClient = initiateDeveloperControlledWalletsClient({
  apiKey,
  entitySecret,
});

const chain = CHAIN_CONFIGS.arcTestnet;
const depositAmount = "1";
// 1 USDC = 1_000_000 base units (6 decimals)
const depositAmountBaseUnits = "1000000";
const gatewayApi = "https://gateway-api-testnet.circle.com";
const protectedUrl = "http://localhost:3000/premium-data";

const batchScheme = new BatchEvmScheme({
  address: walletAddress,
  signTypedData: async (params) => {
    const typedData = {
      domain: {
        ...params.domain,
        chainId: params.domain.chainId.toString(),
      },
      primaryType: params.primaryType,
      types: {
        EIP712Domain: [
          { name: "name", type: "string" },
          { name: "version", type: "string" },
          { name: "chainId", type: "uint256" },
          { name: "verifyingContract", type: "address" },
        ],
        ...params.types,
      },
      message: params.message,
    };

    const response = await circleClient.signTypedData({
      walletAddress,
      blockchain: "ARC-TESTNET",
      data: stringifyTypedData(typedData),
    });

    const signature = response.data?.signature;
    if (!signature) {
      throw new Error("Circle Wallets returned no signature.");
    }
    return (
      signature.startsWith("0x") ? signature : `0x${signature}`
    ) as `0x${string}`;
  },
});
```

`BatchEvmScheme` builds the EIP-3009 payment authorization. The callback
normalizes its viem-style typed data for Circle Wallets by adding the explicit
`EIP712Domain` type, serializing `chainId`, and converting the response to a
`0x`-prefixed signature. The developer-controlled wallet signs the authorization
without exposing a private key to this script.

## Step 3. Fund the wallet

Use the [Circle Faucet](https://faucet.circle.com/) to send testnet USDC to the
Arc Testnet wallet address from the wallet quickstart. Arc Testnet uses
[USDC as its native gas token](https://docs.arc.io/arc/concepts/stablecoin-native-model#usdc-as-the-native-gas-token),
so the faucet funds both the USDC deposit and the transaction fees.

## Step 4. Deposit USDC into Gateway

> [!TIP]
>
> **Prefer a shorter deposit flow?** Use
> [Unified Balance Kit](https://docs.arc.io/app-kit/unified-balance) with the
> [Circle Wallets adapter](https://docs.arc.io/app-kit/tutorials/adapter-setups#circle-wallets).
> See
> [Deposit and spend a Unified Balance (Circle Wallets)](https://docs.arc.io/app-kit/quickstarts/unified-balance-deposit-and-spend)
> for Arc Testnet.

Before making a nanopayment, the wallet needs USDC in its Gateway balance. The
following function:

1. Checks the current Gateway balance.
2. Approves the Gateway Wallet contract to spend USDC.
3. Deposits USDC into Gateway.
4. Waits until the deposited balance is available.

Add these functions to `pay.ts`. They are standalone declarations, so copy them
as-is below the initialization code.

```ts pay.ts theme={null}
async function ensureGatewayBalance(): Promise<void> {
  let gatewayBalance = await getGatewayBalance();

  if (gatewayBalance >= Number(depositAmount)) {
    console.log(`Gateway balance: ${gatewayBalance} USDC`);
    return;
  }

  console.log(`Approving ${depositAmount} USDC for Gateway...`);
  const approval = await circleClient.createContractExecutionTransaction({
    walletAddress,
    blockchain: "ARC-TESTNET",
    contractAddress: chain.usdc,
    abiFunctionSignature: "approve(address,uint256)",
    abiParameters: [chain.gatewayWallet, depositAmountBaseUnits],
    fee: { type: "level", config: { feeLevel: "MEDIUM" } },
  });

  const approvalId = approval.data?.id;
  if (!approvalId) {
    throw new Error("USDC approval was not created.");
  }
  await waitForCircleTransaction(approvalId);

  console.log(`Depositing ${depositAmount} USDC into Gateway...`);
  const deposit = await circleClient.createContractExecutionTransaction({
    walletAddress,
    blockchain: "ARC-TESTNET",
    contractAddress: chain.gatewayWallet,
    abiFunctionSignature: "deposit(address,uint256)",
    abiParameters: [chain.usdc, depositAmountBaseUnits],
    fee: { type: "level", config: { feeLevel: "MEDIUM" } },
  });

  const depositId = deposit.data?.id;
  if (!depositId) {
    throw new Error("Gateway deposit was not created.");
  }
  await waitForCircleTransaction(depositId);

  console.log("Waiting for the Gateway balance to become available...");
  do {
    await new Promise((resolve) => setTimeout(resolve, 3000));
    gatewayBalance = await getGatewayBalance();
  } while (gatewayBalance < Number(depositAmount));

  console.log(`Gateway balance: ${gatewayBalance} USDC`);
}

async function getGatewayBalance(): Promise<number> {
  const response = await fetch(`${gatewayApi}/v1/balances`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({
      token: "USDC",
      sources: [{ domain: 26, depositor: walletAddress }],
    }),
  });

  if (!response.ok) {
    throw new Error(
      `Gateway balance request failed: ${response.status} ${await response.text()}`,
    );
  }

  const result = (await response.json()) as {
    balances: Array<{ domain: number; balance: string }>;
  };

  return Number(
    result.balances.find(({ domain }) => domain === 26)?.balance ?? "0",
  );
}

async function waitForCircleTransaction(transactionId: string): Promise<void> {
  const terminalStates = new Set([
    "COMPLETE",
    "CONFIRMED",
    "FAILED",
    "DENIED",
    "CANCELLED",
  ]);

  while (true) {
    const response = await circleClient.getTransaction({ id: transactionId });
    const state = response.data?.transaction?.state;

    if (state && terminalStates.has(state)) {
      if (state !== "COMPLETE" && state !== "CONFIRMED") {
        throw new Error(`Circle Wallet transaction ended in state: ${state}`);
      }
      return;
    }

    await new Promise((resolve) => setTimeout(resolve, 3000));
  }
}
```

## Step 5. Pay for a resource

The first request to an x402-protected endpoint returns `402 Payment Required`
and a `PAYMENT-REQUIRED` header. Sellers often advertise Gateway options for
many blockchains. Select the Arc Testnet `GatewayWalletBatched` option so the
EIP-712 `chainId` matches the Circle Wallets `ARC-TESTNET` signer, then create
the authorization and retry with a `PAYMENT-SIGNATURE` that includes the chosen
option and the `resource` metadata from the `402` response.

Add `payForResource` to `pay.ts`:

```ts pay.ts theme={null}
async function payForResource(url: string): Promise<unknown> {
  const unpaidResponse = await fetch(url);
  if (unpaidResponse.status !== 402) {
    throw new Error(
      `Expected 402 Payment Required, received ${unpaidResponse.status}.`,
    );
  }

  const paymentRequiredHeader = unpaidResponse.headers.get("PAYMENT-REQUIRED");
  if (!paymentRequiredHeader) {
    throw new Error("The endpoint did not return a PAYMENT-REQUIRED header.");
  }

  const paymentRequired = JSON.parse(
    Buffer.from(paymentRequiredHeader, "base64").toString("utf8"),
  ) as {
    x402Version: number;
    accepts: Array<{
      scheme: string;
      network: string;
      asset: string;
      amount: string;
      payTo: string;
      maxTimeoutSeconds: number;
      extra?: Record<string, unknown>;
    }>;
    resource?: unknown;
  };

  const arcNetwork = `eip155:${chain.chain.id}`;
  const gatewayOption = paymentRequired.accepts.find(
    (option) =>
      option.extra?.name === "GatewayWalletBatched" &&
      option.network === arcNetwork,
  );

  if (!gatewayOption) {
    throw new Error(
      `The endpoint does not support Gateway nanopayments on ${arcNetwork}.`,
    );
  }

  if (!paymentRequired.resource) {
    throw new Error("The endpoint did not return resource metadata.");
  }

  const paymentPayload = await batchScheme.createPaymentPayload(
    paymentRequired.x402Version,
    gatewayOption,
  );
  const paidResponse = await fetch(url, {
    headers: {
      "PAYMENT-SIGNATURE": Buffer.from(
        JSON.stringify({
          ...paymentPayload,
          accepted: gatewayOption,
          resource: paymentRequired.resource,
        }),
      ).toString("base64"),
    },
  });

  if (!paidResponse.ok) {
    throw new Error(
      `Payment failed: ${paidResponse.status} ${await paidResponse.text()}`,
    );
  }

  return paidResponse.json();
}
```

## Step 6. Run the script

At the end of `pay.ts`, add a small entry point that calls the functions in
order:

```ts pay.ts theme={null}
async function main(): Promise<void> {
  await ensureGatewayBalance();

  const data = await payForResource(protectedUrl);
  console.log("Paid response:");
  console.dir(data, { depth: null });
}

main().catch((error) => {
  console.error("Error:");
  console.dir(error, { depth: null });
  process.exit(1);
});
```

> [!TIP]
>
> Start the protected endpoint from the [seller
> quickstart](/gateway-nanopayments/quickstarts/seller.md) before running this
> script. It should be available at `http://localhost:3000/premium-data`.

Run the script:

```shell theme={null}
npm run pay
```

> [!NOTE]
>
> The seller returns the resource as soon as Gateway accepts the payment.
> Onchain settlement happens later when Gateway includes the [payment in a
> batch](/gateway-nanopayments/concepts/batched-settlement.md) (often several
> minutes). You do not need to wait for that to complete a successful pay. To
> inspect status later, query [Search x402
> Transfers](/api-reference/gateway-nanopayments/search-x402transfers.md) with the
> EIP-3009 authorization nonce.

**Self-managed**

## Prerequisites

Before you begin, ensure that you've:

* Installed [Node.js v22.18+](https://nodejs.org/).
* Obtained an
  [EOA (externally owned account)](/wallets/account-types.md#externally-owned-accounts-eoa)
  wallet private key for signing transactions and payment authorizations.
* Obtained testnet USDC from the [Circle Faucet](https://faucet.circle.com).
* Funded your wallet with testnet ETH (or native gas token) for the one-time
  deposit transaction.

> [!WARNING]
>
> Gateway Nanopayments requires an EOA wallet. Smart contract account (SCA)
> wallets are not supported because the batched settlement path verifies EIP-3009
> payment authorizations offchain using `ecrecover`, which is incompatible with
> ERC-1271 contract signatures. This limitation is specific to Gateway
> Nanopayments. Standard Gateway transfers support
> [ERC-1271 signatures](/gateway/references/erc-1271.md).

## Step 1. Set up your project

### 1.1. Create the project and install dependencies

```shell theme={null}
mkdir nanopayments-buyer
cd nanopayments-buyer
npm init -y
npm pkg set type=module
npm pkg set scripts.pay="node --env-file=.env pay.ts"
npm install @circle-fin/x402-batching viem typescript
npm install --save-dev @types/node
```

### 1.2. Configure TypeScript (optional)

> [!TIP]
>
> This step is optional. It helps prevent missing types in your IDE or editor.

Create a `tsconfig.json` file:

```shell theme={null}
npx tsc --init
```

Then, update the `tsconfig.json` file:

```shell theme={null}
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:

```text theme={null}
PRIVATE_KEY=YOUR_PRIVATE_KEY
```

* `PRIVATE_KEY` is the private key for the EOA you use to deposit USDC and sign
  nanopayment authorizations.

> [!TIP]
>
> 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.

> [!WARNING]
>
> 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:

```ts pay.ts theme={null}
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](/sdks/gateway-nanopayments-sdk.md) for all supported blockchains.

## 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:

```ts pay.ts theme={null}
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-reference/gateway/all/get-token-balances.md) 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](/gateway/references/supported-blockchains.md#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:

1. Sends the initial request to the URL.
2. Receives the `402 Payment Required` response with payment details.
3. Signs an EIP-3009 authorization offchain (zero gas).
4. Retries the request with the `PAYMENT-SIGNATURE` header.

```ts pay.ts theme={null}
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-reference/gateway-nanopayments/settle-x402payment.md)
API endpoint.

> [!TIP]
>
> Don't have a seller URL to test with? Set up a local test API in two minutes
> using the [seller quickstart](/gateway-nanopayments/quickstarts/seller.md).

## Step 5. Check your balance

Add balance checking after the payment using the
[Get Token Balances](/api-reference/gateway/all/get-token-balances.md) API
endpoint:

```ts pay.ts theme={null}
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:

```shell theme={null}
npm run pay
```

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:

```ts pay.ts theme={null}
const result = await client.withdraw("5");
console.log(`Withdrew ${result.formattedAmount} USDC`);
console.log(`Tx: ${result.mintTxHash}`);
```

To withdraw to a different blockchain:

```ts pay.ts theme={null}
const crossChain = await client.withdraw("5", { chain: "baseSepolia" });
console.log(`Withdrew to ${crossChain.destinationChain}`);
```

> [!NOTE]
>
> 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:

```ts theme={null}
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.

> [!TIP]
>
> If a payment fails, the
> [error reference](/sdks/gateway-nanopayments-sdk.md#error-reference) covers each
> error code, including `insufficient_funds`, `expired_authorization`,
> `invalid_payload`, and `invalid_signature`. Each entry explains the cause and
> how to recover.
