Skip to content

Payments (x402)

Two methods cover every x402 flow:

MethodUse it when
client.x402.pay(input)You want a single call that signs and settles on-chain. Most apps.
client.x402.sign(input)You want the signed payload so you can forward it elsewhere (another facilitator, a queue, a different settle path).

Both produce a canonical x402 v2 payload (EIP-3009 TransferWithAuthorization against the token’s own domain) so any x402-compatible facilitator can settle it.

SymbolWhat it isPegChainsCap accounting
USDCCircle USD Coin (default)$1Avalanche, Fuji1 USDC = $1 against your USD caps
XSGDStraitsX Singapore-dollar stablecoin1 SGD (~US$0.77)Avalanche, FujiConverted at the live Chainlink SGD/USD rate — 10 XSGD counts as ≈ $7.75, not $10

Pass the symbol via tokenSymbol. The agent’s policy must allow it (allowedTokens) — tier ceilings include both USDC and XSGD, but agents created before XSGD support keep their stored ['USDC']-only policy; update it once with policy.setAgent to add 'XSGD'.

// Pay 10 XSGD (6 decimals, like USDC)
await client.x402.pay({
agentId: 'trading-bot-1',
to: '0xMerchant...',
value: '10000000',
tokenSymbol: 'XSGD',
});

The full current list, including each token’s settlement mode and signing domain, is served live at GET /tokens on the facilitator.

Sign and settle in one call. This is what 90% of apps want.

const result = await client.x402.pay({
agentId: 'trading-bot-1', // required
to: '0xMerchant...', // required — recipient EOA
value: '500000', // required — atomic units (USDC/XSGD have 6 decimals)
tokenSymbol: 'USDC', // optional — 'USDC' (default) or 'XSGD'
chain: 'avalanche', // optional — default = agent's stored chain
validBefore: 1781000000, // optional — default = now + 5 minutes (max 1 hour)
nonce: '0x...', // optional — auto-generated 32-byte random
settle: true, // optional — default true. Set false to skip the on-chain submit.
});
{
"agentId": "trading-bot-1",
"signed": {
"payerAddress": "0xb0e6ec46cd2d97de006f9318ac5c7994b7aadd2b",
"paymentPayload": {
"token": "0x5425890298aed601595a70AB815c96711a31Bc65",
"payload": {
"authorization": {
"from": "0xb0e6ec46cd2d97de006f9318ac5c7994b7aadd2b",
"to": "0xMerchant...",
"value": "500000",
"validAfter": 1781000040,
"validBefore": 1781000340,
"nonce": "0x..."
},
"signature": "0x..."
}
},
"paymentRequirements": { "network": "avalanche-testnet", "chainId": 43113 },
"policy": {
"perTxCapUSD": 5,
"perDayCapUSD": 50,
"spentTodayUSD": "0.500000",
"remainingTodayUSD": "49.500000"
}
},
"settle": {
"success": true,
"transaction": "0x5972437a3695b2641855416da6b7f936ceda300c7536189a5dc237e1cc21a8e7",
"network": "avalanche-fuji",
"payer": "0xb0e6ec46cd2d97de006f9318ac5c7994b7aadd2b",
"blockNumber": 56206516
}
}

Inspect result.settle?.transaction for the on-chain hash. Inspect result.signed.policy to see your remaining daily quota.

Produces a signed payload without submitting it on-chain. Useful when:

  • You want to forward the payload to a different facilitator.
  • You’re queueing payments for batch settlement.
  • The merchant wants to settle the payload themselves.
const signed = await client.x402.sign({
agentId: 'trading-bot-1',
to: '0xMerchant...',
value: '500000',
});
// Forward signed.paymentPayload + signed.paymentRequirements wherever you want.

Same return shape as the signed block in pay(). The signature is valid and on-chain settle-able for the lifetime of validBefore.

Every authorization carries a validAfter and validBefore (unix seconds). Defaults:

FieldDefaultNotes
validAfternow − 60sSmall clock-skew tolerance for the recipient.
validBeforenow + 300s (5 min)Tight by design — limits exposure if a signature is leaked or an agent is revoked.

The platform caps validBefore at now + 1 hour. Anything beyond that is a 400 validBefore window cannot exceed 3600s.

If you need long-lived payment instructions, sign multiple short-lived authorizations rather than one long one.

Before the platform signs, it checks all of:

CheckFailure
Agent exists and is active403 Agent ... is revoked — cannot sign
chain is in policy.allowedChains403 Chain ... not in agent policy
tokenSymbol is in policy.allowedTokens403 Token ... not in agent policy
valuepolicy.perTxCapUSD403 per_tx_cap_exceeded
Today’s spend + valuepolicy.perDayCapUSD403 per_day_cap_exceeded
Validity window valid and within max400

Every check happens server-side. Failed checks consume no gas (no signature is produced).

If you have a signed payload from somewhere else (another platform, a self-hosted agent), you can settle it via:

const result = await client.facilitator.settle(
signed.paymentPayload,
signed.paymentRequirements,
);

Or verify without spending gas:

const v = await client.facilitator.verify(
signed.paymentPayload,
signed.paymentRequirements,
);
console.log(v.isValid); // true/false

See x402 Protocol → Facilitator API for the underlying REST contract.

The platform tracks per-day spend automatically. You don’t need a counter in your code:

try {
await client.x402.pay({ agentId, to, value: '100000' }); // 0.1 USDC
} catch (err) {
if (err.code === 'per_day_cap_exceeded') {
// Agent is done for today — notify ops, or fall back to a different agent.
}
}
// In your producer
const { paymentPayload, paymentRequirements } = await client.x402.sign({
agentId, to, value: '500000',
validBefore: Math.floor(Date.now() / 1000) + 3000, // 50 min ahead — within the 1-hour cap
});
queue.push({ paymentPayload, paymentRequirements });
// In your worker, sometime later (but before validBefore)
const queued = queue.pop();
await client.facilitator.settle(queued.paymentPayload, queued.paymentRequirements);
const usAgent = await client.agents.create({ agentId: 'us-bot', chain: 'avalanche' });
const baseBot = await client.agents.create({ agentId: 'base-bot', chain: 'base' });
await client.x402.pay({ agentId: 'us-bot', to: '0x...', value: '1000000' }); // settles on Avalanche
await client.x402.pay({ agentId: 'base-bot', to: '0x...', value: '1000000' }); // settles on Base