x402-paid-agent-starter / tree / typescript/src/x402.ts ยท raw (this view has old GitHub links rewritten for the mirror; raw is byte-exact)
// 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