Three Schemes, One Result

EIP-3009: USDC on EVM Chains

Permit2: USDC, USDT, and Other ERC-20 Tokens

Solana: Native Fee Payer Abstraction

Building the Settle Payload

Quick Reference

Start accepting crypto today

CoinCircuit settles gasless stablecoin payments across EVM chains and Solana. The sender signs a payment authorization off-chain. CoinCircuit submits it on-chain and covers gas.

This guide shows how to build the signed payload for each scheme. You need ethers.js v6 for EVM chains or @solana/web3.js for Solana.

USDC implements transferWithAuthorization (EIP-3009). You sign an EIP-712 typed data message authorizing a token transfer from your wallet. Anyone can submit the signed message on-chain. The signer pays zero gas.

Two things to get right:

The domain must match what the USDC contract expects. Always query name() and version() from the contract itself. Base mainnet returns "USD Coin" / "2". Base Sepolia returns "USDC" / "2". Arbitrum returns "USD Coin" / "2". A mismatched domain produces an invalid signature that reverts on-chain.

The nonce is a random 32-byte hex value. The USDC contract tracks used nonces per sender address. Each value can only be used once.

USDC, USDT, and tokens without built-in gasless support use Uniswap's Permit2. Two steps: a one-time on-chain approval, then off-chain signing for each payment.

One-time setup: approve the Permit2 contract to spend your tokens.

Per-payment signing:

The Permit2 contract deploys at the same address on every EVM chain: 0x000000000022D473030F116dDEE9F6B43aC78BA3.

The spender in the signed message must match whoever calls permitTransferFrom on-chain. CoinCircuit resolves this at settlement time. Set it to ethers.ZeroAddress when building payloads.

Solana transactions support separate fee payers at the protocol level. You build a transaction, set CoinCircuit's wallet as the fee payer, sign your part, and send the partially signed transaction. CoinCircuit adds its signature and submits.

For SPL token transfers (USDC, USDT on Solana), replace SystemProgram.transfer with the SPL Token createTransferInstruction from @solana/spl-token.

Solana transactions expire after roughly 60 seconds based on the blockhash. Generate the transaction close to when you plan to submit it.

Each scheme produces a flat payload with from, to, value, and scheme-specific fields. Wrap it in the settle request body.

EIP-3009 (USDC on Base):

Permit2 (USDT on Arbitrum):

Solana (SOL):

POST this to https://api.coincircuit.io/api/v1/payments/agent/settle with your API key. CoinCircuit validates the payload, submits it on-chain, covers gas, and returns the transaction hash.

For the full resource server integration flow, see x402: How to Accept Payments from AI Agents on Your API.

Developer friendly API. Instant settlements. No hidden fees.

x402: How to Accept Payments from AI Agents on Your API Get Started Now

Scheme

Assets

Chains

Signing Standard

Key Fields

eip3009

USDC

Base, Arbitrum

EIP-712

validAfter, validBefore, nonce (32-byte hex), signature

permit2

USDC, USDT

Base, Arbitrum, BSC

deadline, nonce (numeric), signature

solana

SOL, USDC, USDT

Solana

Partial tx signing

transaction (base64)

transferWithAuthorization
import { ethers } from 'ethers';

const provider = new ethers.JsonRpcProvider('https://mainnet.base.org');
const usdcAddress = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';

// Query the contract for EIP-712 domain fields
const usdc = new ethers.Contract(usdcAddress, [
  'function name() view returns (string)',
  'function version() view returns (string)',
], provider);

const domain = {
  name: await usdc.name(),       // "USD Coin" on Base mainnet
  version: await usdc.version(), // "2"
  chai
const usdt = new ethers.Contract(usdtAddress, [
  'function approve(address spender, uint256 amount)',
], signer);

await usdt.approve(
  '0x000000000022D473030F116dDEE9F6B43aC78BA3', // Permit2
  ethers.MaxUint256,
);
import { ethers } from 'ethers';

const PERMIT2 = '0x000000000022D473030F116dDEE9F6B43aC78BA3';
const usdtAddress = '0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9'; // USDT on Arbitrum

const domain = {
  name: 'Permit2',
  chainId: 42161,
  verifyingContract: PERMIT2,
};

const types = {
  PermitTransferFrom: [
    { name: 'permitted', type: 'TokenPermissions' },
    { name: 'spender', type: 'address' },
    { name: 'nonce', type: 'uint256' },
    { name: 'deadline', type: 'uint256' },
  ],
  Toke
0x000000000022D473030F116dDEE9F6B43aC78BA3
import {
  Connection, Transaction, SystemProgram,
  PublicKey, Keypair,
} from '@solana/web3.js';

const connection = new Connection(
  'https://api.mainnet-beta.solana.com', 'confirmed',
);
const sender = Keypair.fromSecretKey(yourSecretKeyBytes);
const recipient = new PublicKey('RecipientAddress...');
const feePayer = new PublicKey('CoinCircuitFeePayerKey...');

const { blockhash } = await connection.getLatestBlockhash('confirmed');
const tx = new Transaction({ recentBlockhash: blockhash, fee
SystemProgram.transfer
createTransferInstruction
{
  "scheme": "eip3009",
  "chain": "base",
  "asset": "USDC",
  "payload": {
    "from": "0xYourWallet...",
    "to": "0xDepositAddress...",
    "value": "1000000",
    "validAfter": "0",
    "validBefore": "1774055094",
    "nonce": "0xrandom32bytes...",
    "signature": "0xYourSignature..."
  }
}
{
  "scheme": "permit2",
  "chain": "arbitrum",
  "asset": "USDT",
  "payload": {
    "from": "0xYourWallet...",
    "to": "0xDepositAddress...",
    "value": "1000000",
    "deadline": "1774055094",
    "nonce": "42",
    "signature": "0xYourSignature..."
  }
}
{
  "scheme": "solana",
  "chain": "solana",
  "asset": "SOL",
  "payload": {
    "from": "YourSolanaPublicKey...",
    "to": "RecipientPublicKey...",
    "value": "11771200",
    "transaction": "base64EncodedPartiallySignedTx..."
  }
}
https://api.coincircuit.io/api/v1/payments/agent/settle