x402-paid-agent-starter / tree / typescript/src/evm-pay.ts ยท raw (this view has old GitHub links rewritten for the mirror; raw is byte-exact)
// Builds an x402 "exact" EVM payment header (EIP-3009 transferWithAuthorization) for v1 or v2 terms.
// The caller passes a viem account; the private key stays with the caller and is never logged.
import type { LocalAccount } from 'viem';

const CHAIN_IDS: Record<string, number> = { base: 8453, 'base-sepolia': 84532, 'eip155:8453': 8453, 'eip155:84532': 84532 };

const randomNonce = (): `0x${string}` => {
  const b = new Uint8Array(32);
  crypto.getRandomValues(b);
  return `0x${Array.from(b, (x) => x.toString(16).padStart(2, '0')).join('')}`;
};

const b64 = (v: unknown) => {
  const bytes = new TextEncoder().encode(JSON.stringify(v));
  let s = '';
  for (const x of bytes) s += String.fromCharCode(x);
  return btoa(s);
};

export async function exactPaymentHeader(
  terms: { x402Version: number; accepts: Array<Record<string, unknown>>; resource?: unknown },
  account: LocalAccount,
  opts: { maxAtomic?: bigint; now?: () => number } = {},
): Promise<string> {
  const req = terms.accepts.find((a) => a.scheme === 'exact' && String(a.network) in CHAIN_IDS);
  if (!req) throw new Error('no exact EVM payment option on Base in the 402 terms');
  const value = String(req.amount ?? req.maxAmountRequired);
  if (opts.maxAtomic !== undefined && BigInt(value) > opts.maxAtomic) throw new Error(`refusing to pay ${value} atomic USDC, above the cap ${opts.maxAtomic}`);
  const chainId = CHAIN_IDS[String(req.network)]!;
  const extra = (req.extra ?? {}) as { name?: string; version?: string };
  const now = Math.floor((opts.now ?? Date.now)() / 1000);
  const authorization = {
    from: account.address,
    to: String(req.payTo) as `0x${string}`,
    value,
    validAfter: String(now - 600),
    validBefore: String(now + Number(req.maxTimeoutSeconds ?? 60)),
    nonce: randomNonce(),
  };
  const signature = await account.signTypedData({
    domain: { name: extra.name ?? 'USD Coin', version: extra.version ?? '2', chainId, verifyingContract: String(req.asset) as `0x${string}` },
    types: {
      TransferWithAuthorization: [
        { name: 'from', type: 'address' }, { name: 'to', type: 'address' }, { name: 'value', type: 'uint256' },
        { name: 'validAfter', type: 'uint256' }, { name: 'validBefore', type: 'uint256' }, { name: 'nonce', type: 'bytes32' },
      ],
    },
    primaryType: 'TransferWithAuthorization',
    message: {
      from: authorization.from, to: authorization.to, value: BigInt(value),
      validAfter: BigInt(authorization.validAfter), validBefore: BigInt(authorization.validBefore), nonce: authorization.nonce,
    },
  });
  const payload = { signature, authorization };
  if (Number(terms.x402Version) >= 2) return b64({ x402Version: 2, resource: terms.resource, accepted: req, payload });
  return b64({ x402Version: 1, scheme: 'exact', network: req.network, payload });
}

agentgate-source: code mirror on Cloudflare Pages. All repos