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 NowScheme
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