x402-tokenverdict / tree / src/networks.js · raw (this view has old GitHub links rewritten for the mirror; raw is byte-exact)
// Shared x402 network list for all YourAITools Workers (canonical copy: /workspace/crypto/multichain/networks.js;
// copied verbatim into each Worker's src/, like the stats-fix templates; keep the copies identical).
// One toggle per network (`enabled`). The ENABLED_NETWORKS env var (comma list of CAIP-2 ids) overrides the toggles.
// Base mainnet is ALWAYS first and its accepts entry is passed through untouched, so Base buyers see exactly the
// same requirements and settle exactly as before. Extra networks are only added when the Worker's primary network is
// Base mainnet and createApp() got `enabledNetworks` (worker.js passes it; tests/local default to Base only).
// All networks settle through the same facilitator client (CDP supports every network here; checked with a $0
// unfunded /verify on 2026-10-22; Solana also by a SELF settlement). x402 v1 (X-PAYMENT, PayAI) stays Base-only.
import { convertToTokenAmount, parseMoney } from '@x402/core/utils';

export const BASE_MAINNET = 'eip155:8453';
export const SOLANA_MAINNET = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp';
export const EVM_PAYTO = '0x3c1A1E01e790Cd326495631B3b38Bd7068F08d6F';

export const NETWORKS = [
  { id: BASE_MAINNET, name: 'Base', kind: 'evm', enabled: true, asset: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', extra: { name: 'USD Coin', version: '2' } },
  { id: 'eip155:137', name: 'Polygon', kind: 'evm', enabled: true, asset: '0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359', extra: { name: 'USD Coin', version: '2' } },
  { id: 'eip155:42161', name: 'Arbitrum One', kind: 'evm', enabled: true, asset: '0xaf88d065e77c8cC2239327C5EDb3A432268e5831', extra: { name: 'USD Coin', version: '2' } },
  // Solana mainnet USDC (ON since 2026-10-22: CDP verify passed + SELF settle 3jvXwsEm…). payTo = SOLANA_PAY_TO env
  // (payout wallet FH73…2Jte, USDC token account CM3JgZ…bxpAT); dropped automatically when SOLANA_PAY_TO is unset.
  // feePayer = CDP facilitator's Solana fee payer from its authenticated /supported (exact, v2), checked 2026-10-22;
  // a live /supported answer overrides it whenever the Worker fetches supported kinds.
  { id: SOLANA_MAINNET, name: 'Solana', kind: 'svm', enabled: true, asset: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v', feePayer: 'GVJJ7rdGiXr5xaYbRwRbjfaJL7fmwRygFi1H6aGqDveb' },
];
export const DEFAULT_ENABLED = NETWORKS.filter((n) => n.enabled).map((n) => n.id).join(',');
export const byId = (id) => NETWORKS.find((n) => n.id === id);

/** Resolve the active network list (in NETWORKS order, Base first). Networks that can't be used (Solana without a payTo) are dropped. */
export function activeNetworks(cfg = {}) {
  const primary = cfg.network || BASE_MAINNET;
  if (primary !== BASE_MAINNET || cfg.enabledNetworks === undefined || cfg.enabledNetworks === null) return [byId(primary) || { id: primary, kind: 'evm' }];
  const want = new Set(String(cfg.enabledNetworks).split(',').map((s) => s.trim()).filter(Boolean));
  want.add(BASE_MAINNET);
  return NETWORKS.filter((n) => want.has(n.id) && (n.kind !== 'svm' || cfg.solanaPayTo));
}

/** One accepts entry for an extra network, mirroring the primary entry (same price string, timeout and scheme). */
function extraAccept(primary, n, cfg) {
  const { network, payTo, price, ...rest } = primary;
  if (n.kind === 'svm') return { ...rest, network: n.id, payTo: cfg.solanaPayTo, price };
  return { ...rest, network: n.id, payTo, price };
}

/** Expand every route's accepts with the extra enabled networks. The primary (Base) entry is kept as the same object, first. */
export function multiNetworkRoutes(routes, cfg = {}) {
  const nets = activeNetworks(cfg).filter((n) => n.id !== (cfg.network || BASE_MAINNET));
  if (!nets.length) return routes;
  const out = {};
  for (const [k, r] of Object.entries(routes)) {
    const acc = Array.isArray(r.accepts) ? r.accepts : [r.accepts];
    const primary = acc[0];
    if (!primary || primary.network !== BASE_MAINNET) { out[k] = r; continue; }
    out[k] = { ...r, accepts: [...acc, ...nets.map((n) => extraAccept(primary, n, cfg))] };
  }
  return out;
}

/** Static supported kinds for the facilitator client (no I/O on Workers): one exact v2 kind per active network. */
export function supportedKinds(cfg = {}) {
  return activeNetworks(cfg).map((n) => ({ x402Version: 2, scheme: 'exact', network: n.id, ...(n.kind === 'svm' ? { extra: { feePayer: n.feePayer } } : {}) }));
}

/** Register server schemes for the extra networks on an x402ResourceServer (Base is registered by the caller as before). */
export function registerNetworks(resourceServer, cfg, EvmScheme) {
  for (const n of activeNetworks(cfg)) {
    if (n.id === (cfg.network || BASE_MAINNET)) continue;
    resourceServer.register(n.id, n.kind === 'svm' ? new ExactSvmServerScheme() : new EvmScheme());
  }
  return resourceServer;
}

/** Public network list for discovery docs and landing JSON. */
export function networkInfo(cfg = {}) {
  return activeNetworks(cfg).map((n) => ({ network: n.id, name: n.name, asset: n.asset, symbol: 'USDC', payTo: n.kind === 'svm' ? cfg.solanaPayTo : (cfg.payTo || EVM_PAYTO) }));
}

/** Hono middleware: add the network list to JSON discovery docs (/, /openapi.json, /.well-known/x402). Never breaks a response. */
export function discoveryNetworks(cfg = {}) {
  const paths = new Set(['/', '/openapi.json', '/.well-known/x402']);
  return async (c, next) => {
    await next();
    try {
      const nets = networkInfo(cfg);
      if (nets.length < 2 || !paths.has(c.req.path) || c.res.status !== 200 || !/application\/json/.test(c.res.headers.get('content-type') || '')) return;
      const doc = await c.res.clone().json();
      const label = nets.map((n) => `${n.name} (${n.network})`).join(', ');
      if (c.req.path === '/openapi.json') {
        const walk = (o) => { if (!o || typeof o !== 'object') return; for (const [k, v] of Object.entries(o)) { if (k === 'x-x402' && v && typeof v === 'object' && !Array.isArray(v)) v.networks = nets; else walk(v); } };
        walk(doc.paths);
        doc['x-x402-networks'] = nets;
      } else {
        doc.networks = nets;
        if (typeof doc.instructions === 'string') doc.instructions = doc.instructions.replace(/USDC on Base \(eip155:8453\)/g, `USDC on ${label}`);
      }
      const headers = new Headers(c.res.headers); headers.delete('content-length');
      c.res = undefined;
      c.res = new Response(JSON.stringify(doc), { status: 200, headers });
    } catch { /* keep the original response */ }
  };
}

/** Hono middleware: count settled payments per network (paid_net:<CAIP-2>) in the fail-open StatsCounter batch. */
export function networkStats(stats, { isSmoke = () => false, waitUntilOf = () => undefined } = {}) {
  return async (c, next) => {
    await next();
    try {
      if (isSmoke(c) || c.res.status !== 200) return;
      const h = c.res.headers.get('PAYMENT-RESPONSE'); if (!h) return;
      const net = JSON.parse(decodeURIComponent(escape(atob(h)))).network;
      if (net) stats.hit(`paid_net:${net}`, waitUntilOf(c));
    } catch { /* fail-open */ }
  };
}

// Server side of the SVM "exact" scheme for x402 v2, vendored from @x402/svm 2.28.0 ExactSvmScheme (server; no rpcUrl,
// so no blockhash prefetch) so Workers don't need the Solana SDK just to state the terms. Verification and settlement
// (signature, amount, recipient, fee payer) are done by the CDP facilitator.
export class ExactSvmServerScheme {
  constructor() {
    this.scheme = 'exact';
    this.defaultAssetTransferMethod = 'default';
    this.paymentFlows = { default: { supported: ['authorization', 'upfront'], default: 'authorization' } };
    this.dynamicExtraFields = ['recentBlockhash', 'lastValidBlockHeight'];
  }
  getAssetDecimals(asset, network) { return network === SOLANA_MAINNET && asset === byId(SOLANA_MAINNET).asset ? 6 : undefined; }
  async parsePrice(price, network) {
    if (typeof price === 'object' && price !== null && 'amount' in price) {
      if (!price.asset) throw new Error(`Asset address must be specified for AssetAmount on network ${network}`);
      return { amount: price.amount, asset: price.asset, extra: price.extra || {} };
    }
    const n = byId(network);
    if (!n || n.kind !== 'svm') throw new Error(`No default USDC for ${network}`);
    const { amount } = parseMoney(price);
    return { amount: convertToTokenAmount(amount, 6), asset: n.asset, extra: {} };
  }
  async enhancePaymentRequirements(paymentRequirements, supportedKind) {
    return { ...paymentRequirements, extra: { ...paymentRequirements.extra, feePayer: supportedKind.extra?.feePayer } };
  }
}

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