x402-payguard / tree / src/paid.js ยท raw (this view has old GitHub links rewritten for the mirror; raw is byte-exact)
// Optional paid tier for the stdio MCP server: full PayGuard results from the hosted API, paid per call over x402
// with the user's own wallet. Only active when PAYGUARD_X402_PRIVATE_KEY (or X402_PRIVATE_KEY) is set.
// Needs the optional peer dependencies @x402/fetch, @x402/evm and viem (loaded lazily; the free path never imports them).
// Spend guard: Base USDC only, at most $0.01 per payment, and only to the PayGuard pay-to address.
import { API_BASE } from './lite.js';

export const PAY_TO = '0x3c1A1E01e790Cd326495631B3b38Bd7068F08d6F';
export const NETWORK = 'eip155:8453';
export const MAX_PER_PAYMENT = '$0.01';
export const KEY_ENVS = ['PAYGUARD_X402_PRIVATE_KEY', 'X402_PRIVATE_KEY'];
export const INSTALL_HINT = 'Paid mode needs @x402/fetch, @x402/evm and viem next to this package, e.g. command "npx" with args ["-y", "-p", "@agentgate-builder/payguard", "-p", "@x402/fetch", "-p", "@x402/evm", "-p", "viem", "payguard"].';

export function keyFromEnv(env = process.env) {
  for (const k of KEY_ENVS) { const v = (env[k] || '').trim(); if (v) return { name: k, value: v }; }
  return null;
}

/** Returns a paying client or { error } (never throws, never echoes the key). */
export async function createPaidClient({ env = process.env, fetchImpl = globalThis.fetch, apiBase = (env.PAYGUARD_API_URL || API_BASE).replace(/\/+$/, ''), payTo = PAY_TO } = {}) {
  const key = keyFromEnv(env);
  if (!key) return null;
  const hex = key.value.startsWith('0x') ? key.value : `0x${key.value}`;
  if (!/^0x[0-9a-fA-F]{64}$/.test(hex)) return { error: `${key.name} is set but is not a 32-byte hex private key; using the free offline preview.` };
  let wrap, ExactEvmScheme, privateKeyToAccount;
  try {
    ({ wrapFetchWithPaymentFromConfig: wrap } = await import('@x402/fetch'));
    ({ ExactEvmScheme } = await import('@x402/evm'));
    ({ privateKeyToAccount } = await import('viem/accounts'));
  } catch {
    return { error: `${key.name} is set, but ${INSTALL_HINT} Using the free offline preview.` };
  }
  let account;
  try { account = privateKeyToAccount(hex); } catch { return { error: `${key.name} could not be loaded as an EVM private key; using the free offline preview.` }; }
  const onlyPayGuard = (_v, reqs) => reqs.filter((r) => r.network === NETWORK && String(r.payTo).toLowerCase() === payTo.toLowerCase());
  const pay = wrap(fetchImpl, { schemes: [{ network: NETWORK, client: new ExactEvmScheme(account) }], policies: [onlyPayGuard], spendControls: { maxAmountPerPayment: MAX_PER_PAYMENT } });
  const call = async (path, body) => {
    const res = await pay(`${apiBase}${path}`, { method: 'POST', headers: { 'content-type': 'application/json', accept: 'application/json' }, body: JSON.stringify(body) });
    const text = await res.text();
    let json = null; try { json = JSON.parse(text); } catch { /* not JSON */ }
    if (!res.ok) return { error: `paid API answered HTTP ${res.status}`, detail: json?.message || json?.error || text.slice(0, 300) };
    return { result: json, paymentResponse: res.headers.get('payment-response') || res.headers.get('x-payment-response') || null };
  };
  return {
    payer: account.address, apiBase,
    address: (address) => call('/v1/address', { address }),
    batch: (addresses) => call('/v1/batch', { addresses }),
  };
}

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