// x402 v1 "exact" scheme rules: pure functions, no I/O.
export type Network = 'base' | 'base-sepolia';
export const USDC: Record<Network, { address: string; name: string; version: string }> = {
base: { address: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', name: 'USD Coin', version: '2' },
'base-sepolia': { address: '0x036CbD53842c5426634e7929541eC2318f3dCF7e', name: 'USDC', version: '2' },
};
export interface PaymentRequirements {
scheme: 'exact';
network: Network;
maxAmountRequired: string;
resource: string;
description: string;
mimeType: string;
payTo: string;
maxTimeoutSeconds: number;
asset: string;
outputSchema?: Record<string, unknown>;
extra: { name: string; version: string };
}
export interface PaymentPayload {
x402Version: number;
scheme: string;
network: string;
payload: Record<string, unknown>;
}
export interface VerifyResult { isValid: boolean; invalidReason?: string; payer?: string }
export interface SettleResult { success: boolean; transaction: string; network: string; errorReason?: string; payer?: string }
export interface Facilitator {
verify(paymentPayload: PaymentPayload, paymentRequirements: PaymentRequirements): Promise<VerifyResult>;
settle(paymentPayload: PaymentPayload, paymentRequirements: PaymentRequirements): Promise<SettleResult>;
}
export const NETWORKS: readonly Network[] = ['base', 'base-sepolia'];
export function isNetwork(n: unknown): n is Network {
return typeof n === 'string' && (NETWORKS as readonly string[]).includes(n);
}
/** "0.01" USDC to atomic units (6 decimals) as a string: "10000". */
export function toAtomicUsdc(price: string): string {
if (typeof price !== 'string') throw new TypeError('price must be a decimal string such as "0.01"');
const p = price.trim().replace(/^\$/, '');
const m = /^(\d+)(?:\.(\d{1,6}))?$/.exec(p);
if (!m) throw new RangeError(`price must be a positive decimal with at most 6 decimals, got "${price}"`);
const atomic = BigInt(m[1]!) * 1_000_000n + BigInt((m[2] ?? '').padEnd(6, '0'));
if (atomic <= 0n) throw new RangeError('price must be greater than 0');
return atomic.toString();
}
export function isAddress(a: unknown): a is string {
return typeof a === 'string' && /^0x[0-9a-fA-F]{40}$/.test(a);
}
export function buildRequirements(o: {
price: string; payTo: string; network: Network; resource: string; description: string;
mimeType?: string; maxTimeoutSeconds?: number; outputSchema?: Record<string, unknown>;
}): PaymentRequirements {
const usdc = USDC[o.network];
return {
scheme: 'exact',
network: o.network,
maxAmountRequired: toAtomicUsdc(o.price),
resource: o.resource,
description: o.description,
mimeType: o.mimeType ?? 'application/json',
payTo: o.payTo,
maxTimeoutSeconds: o.maxTimeoutSeconds ?? 60,
asset: usdc.address,
...(o.outputSchema ? { outputSchema: o.outputSchema } : {}),
extra: { name: usdc.name, version: usdc.version },
};
}
// base64 JSON helpers that work on Node, Bun, Deno and workers
export function encodeHeader(value: unknown): string {
const bytes = new TextEncoder().encode(JSON.stringify(value));
let bin = '';
for (const b of bytes) bin += String.fromCharCode(b);
return btoa(bin);
}
export function decodeHeader<T = unknown>(header: string): T {
const bin = atob(header.trim());
const bytes = Uint8Array.from(bin, (c) => c.charCodeAt(0));
return JSON.parse(new TextDecoder().decode(bytes)) as T;
}
/** Decodes X-PAYMENT and checks it is for this scheme and network. Returns a reason string when it is not usable. */
export function readPayment(header: string, req: PaymentRequirements): { payment: PaymentPayload } | { reason: string } {
let p: PaymentPayload;
try {
p = decodeHeader<PaymentPayload>(header);
} catch {
return { reason: 'X-PAYMENT is not base64 encoded JSON' };
}
if (!p || typeof p !== 'object' || typeof p.payload !== 'object' || p.payload === null) return { reason: 'X-PAYMENT has no payload' };
if (p.x402Version !== 1) return { reason: `unsupported x402Version ${String(p.x402Version)}` };
if (p.scheme !== req.scheme) return { reason: `unsupported scheme ${String(p.scheme)}` };
if (p.network !== req.network) return { reason: `wrong network ${String(p.network)}, expected ${req.network}` };
return { payment: p };
}
agentgate-source: code mirror on Cloudflare Pages. All repos