x402-paid-agent-starter / tree / typescript/test/starter.test.ts ยท raw (this view has old GitHub links rewritten for the mirror; raw is byte-exact)
// TypeScript starter tests, one per acceptance item (A1 to A6). Offline with a fake facilitator and fetch.
// A3 has a live part that runs only with LAX_LIVE=1. A5 checks the recorded Base Sepolia proof links (see PROOF.md).
import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { readFileSync, existsSync } from 'node:fs';
import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts';
import { verifyTypedData } from 'viem';
import { createApp } from '../src/app.js';
import { listOffer } from '../src/list.js';
import { exactPaymentHeader } from '../src/evm-pay.js';
import { encodeHeader, decodeHeader, toAtomicUsdc, USDC } from '../src/x402.js';
import type { Facilitator, PaymentPayload, PaymentRequirements } from '../src/x402.js';
import { offerProblems } from './offer-check.js';

const PAY_TO = '0x3c1A1E01e790Cd326495631B3b38Bd7068F08d6F';
const PAYMENT: PaymentPayload = { x402Version: 1, scheme: 'exact', network: 'base-sepolia', payload: { signature: '0xsig', authorization: { from: '0x1111111111111111111111111111111111111111' } } };

function fakeFacilitator(over: Partial<{ valid: boolean; settle: boolean; verifyThrows: boolean; settleThrows: boolean }> = {}) {
  const calls: string[] = [];
  const seen: Array<{ p: PaymentPayload; r: PaymentRequirements }> = [];
  const f: Facilitator = {
    async verify(p, r) {
      calls.push('verify'); seen.push({ p, r });
      if (over.verifyThrows) throw new Error('down');
      return over.valid === false ? { isValid: false, invalidReason: 'invalid_exact_evm_payload_signature' } : { isValid: true, payer: '0x1111111111111111111111111111111111111111' };
    },
    async settle(p, r) {
      calls.push('settle'); seen.push({ p, r });
      if (over.settleThrows) throw new Error('down');
      return over.settle === false ? { success: false, transaction: '', network: r.network, errorReason: 'insufficient_funds' } : { success: true, transaction: '0xabc123', network: r.network };
    },
  };
  return { f, calls, seen };
}

function app(f: Facilitator, work?: () => unknown, network: 'base' | 'base-sepolia' = 'base-sepolia') {
  return createApp({ price: '0.01', payTo: PAY_TO, network, facilitator: f, description: 'test call', work });
}

test('A1 an unpaid call answers 402 with valid x402 requirements for the price, payee and network', async () => {
  for (const network of ['base', 'base-sepolia'] as const) {
    const a = createApp({ price: '0.25', payTo: PAY_TO, network, facilitator: fakeFacilitator().f, description: 'd' });
    const res = await a.fetch(new Request('https://agent.example/paid?x=1'));
    assert.equal(res.status, 402);
    const body = await res.json();
    assert.equal(body.x402Version, 1);
    assert.equal(typeof body.error, 'string');
    assert.equal(body.accepts.length, 1);
    const r = body.accepts[0];
    assert.equal(r.scheme, 'exact');
    assert.equal(r.network, network);
    assert.equal(r.maxAmountRequired, '250000');
    assert.equal(r.resource, 'https://agent.example/paid');
    assert.equal(r.description, 'd');
    assert.equal(r.mimeType, 'application/json');
    assert.equal(r.payTo, PAY_TO);
    assert.ok(Number.isInteger(r.maxTimeoutSeconds) && r.maxTimeoutSeconds > 0);
    assert.equal(r.asset, network === 'base' ? '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913' : '0x036CbD53842c5426634e7929541eC2318f3dCF7e');
    assert.deepEqual(r.extra, { name: USDC[network].name, version: '2' });
  }
  const h = await app(fakeFacilitator().f).fetch(new Request('https://agent.example/health'));
  assert.equal(h.status, 200, '/health is free');
});

test('A1 prices convert to atomic USDC and bad configuration is refused at start', () => {
  assert.equal(toAtomicUsdc('0.01'), '10000');
  assert.equal(toAtomicUsdc('1'), '1000000');
  assert.equal(toAtomicUsdc('0.000001'), '1');
  for (const bad of ['0', '-1', '0.0000001', 'abc', '1e3']) assert.throws(() => toAtomicUsdc(bad));
  assert.throws(() => createApp({ price: '0.01', payTo: 'nope', network: 'base', facilitator: fakeFacilitator().f }));
  assert.throws(() => createApp({ price: '0.01', payTo: PAY_TO, network: 'mainnet' as never, facilitator: fakeFacilitator().f }));
});

test('A2 a paid call is verified, then settled, then answered 200 with X-PAYMENT-RESPONSE', async () => {
  const fake = fakeFacilitator();
  let worked = 0;
  const a = app(fake.f, () => { fake.calls.push('work'); worked++; return { ok: true, answer: 42 }; });
  const res = await a.fetch(new Request('https://agent.example/paid', { headers: { 'X-PAYMENT': encodeHeader(PAYMENT) } }));
  assert.equal(res.status, 200);
  assert.deepEqual(await res.json(), { ok: true, answer: 42 });
  assert.deepEqual(fake.calls, ['verify', 'settle', 'work']);
  assert.deepEqual(fake.seen[0]!.p, PAYMENT);
  assert.equal(fake.seen[0]!.r.maxAmountRequired, '10000');
  const receipt = decodeHeader<Record<string, unknown>>(res.headers.get('X-PAYMENT-RESPONSE')!);
  assert.equal(receipt.success, true);
  assert.equal(receipt.transaction, '0xabc123');
  assert.equal(receipt.network, 'base-sepolia');
  assert.equal(worked, 1);
});

test('A2 a payment that fails verification or settlement gets 402 and no work', async () => {
  const cases = [
    [{ valid: false }, ['verify']],
    [{ verifyThrows: true }, ['verify']],
    [{ settle: false }, ['verify', 'settle']],
    [{ settleThrows: true }, ['verify', 'settle']],
  ] as const;
  for (const [over, expected] of cases) {
    const fake = fakeFacilitator(over);
    let worked = 0;
    const res = await app(fake.f, () => { worked++; return {}; }).fetch(new Request('https://agent.example/paid', { headers: { 'X-PAYMENT': encodeHeader(PAYMENT) } }));
    assert.equal(res.status, 402, JSON.stringify(over));
    const body = await res.json();
    assert.equal(body.x402Version, 1);
    assert.equal(body.accepts.length, 1);
    assert.equal(res.headers.get('X-PAYMENT-RESPONSE'), null);
    assert.deepEqual(fake.calls, expected);
    assert.equal(worked, 0, 'no work');
  }
  for (const header of ['%%%', encodeHeader({ ...PAYMENT, network: 'base' }), encodeHeader({ ...PAYMENT, scheme: 'upto' }), encodeHeader({ x402Version: 1 })]) {
    const fake = fakeFacilitator();
    const res = await app(fake.f).fetch(new Request('https://agent.example/paid', { headers: { 'X-PAYMENT': header } }));
    assert.equal(res.status, 402);
    assert.deepEqual(fake.calls, [], 'a malformed or wrong network header never reaches the facilitator');
  }
});

for (const dir of ['typescript', 'python']) {
  test(`A3 the ${dir} offer passes the LAX offer rules with the Five Laws`, () => {
    const path = new URL(`../../${dir}/lax-offer.json`, import.meta.url);
    assert.ok(existsSync(path), `${dir}/lax-offer.json exists`);
    const offer = JSON.parse(readFileSync(path, 'utf8'));
    assert.deepEqual(offerProblems(offer), []);
    const cap = offer.capabilities[0];
    assert.equal(cap.pricing.model, 'per_call');
    assert.equal(cap.pricing.currency, 'USDC');
    assert.equal(cap.pricing.rail, 'x402');
  });
}

test('A3 the local rules catch a broken offer', () => {
  const offer = JSON.parse(readFileSync(new URL('../lax-offer.json', import.meta.url), 'utf8'));
  assert.ok(offerProblems({ ...offer, constitutional_constraints: ['Harm Shield'] }).length >= 4);
  assert.ok(offerProblems({ ...offer, endpoint: 'http://x.example' }).length === 1);
});

test('A3 live: the registry says each offer is valid (unpaid POST answers 402, nothing is listed)', { skip: process.env.LAX_LIVE !== '1' && 'set LAX_LIVE=1 to run' }, async () => {
  for (const dir of ['typescript', 'python']) {
    const offer = readFileSync(new URL(`../../${dir}/lax-offer.json`, import.meta.url), 'utf8');
    const res = await fetch('https://lax.kognai.ai/registry/offers', { method: 'POST', headers: { 'content-type': 'application/json' }, body: offer });
    const body = await res.json();
    assert.equal(res.status, 402, `${dir}: ${JSON.stringify(body).slice(0, 200)}`);
    assert.match(body.error, /offer is valid/i);
  }
});

function fakeRegistry(mode: 'v1' | 'v2') {
  const posts: Array<{ headers: Record<string, string>; body: string }> = [];
  const terms = { x402Version: mode === 'v2' ? 2 : 1, error: 'pay', accepts: [{ scheme: 'exact', network: mode === 'v2' ? 'eip155:8453' : 'base', amount: '10000', maxAmountRequired: '10000', payTo: '0xf62fb064f9d9d03b8013e5b6023e3501805ba973', asset: USDC.base.address, maxTimeoutSeconds: 60, extra: { name: 'USD Coin', version: '2' } }] };
  const fetch = async (_url: string, init: { headers: Record<string, string>; body: string }) => {
    posts.push(init);
    const paid = init.headers['PAYMENT-SIGNATURE'] ?? init.headers['X-PAYMENT'];
    if (!paid) {
      const headers: Record<string, string> = { 'content-type': 'application/json' };
      if (mode === 'v2') headers['PAYMENT-REQUIRED'] = encodeHeader(terms);
      return new Response(JSON.stringify(mode === 'v2' ? { x402Version: 2, error: 'pay' } : terms), { status: 402, headers });
    }
    return new Response(JSON.stringify({ ok: true, id: 'lax-ext-test123', transaction: '0xfee' }), { status: 201 });
  };
  return { fetch, posts, terms };
}

for (const mode of ['v2', 'v1'] as const) {
  test(`A4 listOffer answers the registry's 402 (${mode}), pays through the given function and returns the id`, async () => {
    const reg = fakeRegistry(mode);
    const offer = JSON.parse(readFileSync(new URL('../lax-offer.json', import.meta.url), 'utf8'));
    let seenTerms: unknown;
    const out = await listOffer({ offer, registryUrl: 'https://reg.example/registry/', fetch: reg.fetch as never, pay: async (t) => { seenTerms = t; return 'cGF5bWVudA=='; } });
    assert.deepEqual(out, { id: 'lax-ext-test123' });
    assert.equal(reg.posts.length, 2);
    assert.deepEqual(JSON.parse(reg.posts[1]!.body), offer);
    assert.deepEqual((seenTerms as { accepts: unknown[] }).accepts, reg.terms.accepts);
    const name = mode === 'v2' ? 'PAYMENT-SIGNATURE' : 'X-PAYMENT';
    assert.equal(reg.posts[1]!.headers[name], 'cGF5bWVudA==');
  });
}

test('A4 the key is never logged, and the signed payment is a valid EIP-3009 authorization', async () => {
  const key = generatePrivateKey();
  const account = privateKeyToAccount(key);
  const reg = fakeRegistry('v2');
  const logged: string[] = [];
  const methods = ['log', 'info', 'warn', 'error', 'debug'] as const;
  const saved = methods.map((m) => console[m]);
  const out = process.stdout.write.bind(process.stdout);
  for (const m of methods) console[m] = (...a: unknown[]) => { logged.push(a.map(String).join(' ')); };
  try {
    const offer = JSON.parse(readFileSync(new URL('../lax-offer.json', import.meta.url), 'utf8'));
    await listOffer({ offer, fetch: reg.fetch as never, pay: (t) => exactPaymentHeader(t, account, { maxAtomic: 10000n }) });
  } finally {
    methods.forEach((m, i) => { console[m] = saved[i]!; });
    process.stdout.write = out;
  }
  assert.equal(logged.length, 0);
  const header = reg.posts[1]!.headers['PAYMENT-SIGNATURE']!;
  assert.ok(!header.includes(key.slice(2)), 'the key is not in the header');
  const p = decodeHeader<{ x402Version: number; payload: { signature: `0x${string}`; authorization: Record<string, string> } }>(header);
  assert.equal(p.x402Version, 2);
  const a = p.payload.authorization;
  assert.equal(a.to, '0xf62fb064f9d9d03b8013e5b6023e3501805ba973');
  assert.equal(a.value, '10000');
  const ok = await verifyTypedData({
    address: account.address,
    domain: { name: 'USD Coin', version: '2', chainId: 8453, verifyingContract: USDC.base.address 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: a.from as `0x${string}`, to: a.to as `0x${string}`, value: BigInt(a.value!), validAfter: BigInt(a.validAfter!), validBefore: BigInt(a.validBefore!), nonce: a.nonce as `0x${string}` },
    signature: p.payload.signature,
  });
  assert.equal(ok, true);
  await assert.rejects(exactPaymentHeader(reg.terms, account, { maxAtomic: 9999n }), /above the cap/);
  const scripts = ['../scripts/list.ts', '../scripts/pay.ts', '../src/list.ts', '../src/evm-pay.ts'].map((f) => readFileSync(new URL(f, import.meta.url), 'utf8'));
  for (const s of scripts) assert.doesNotMatch(s, /console\.\w+\([^)]*(key|KEY)/, 'no key in log calls');
});

test('A5 each README holds the transaction hash of one paid Base Sepolia call', () => {
  for (const dir of ['typescript', 'python']) {
    const readme = readFileSync(new URL(`../../${dir}/README.md`, import.meta.url), 'utf8');
    assert.match(readme, /Base Sepolia proof[^\n]*\n[\s\S]*?https:\/\/sepolia\.basescan\.org\/tx\/0x[0-9a-fA-F]{64}/);
  }
});

test('A6 the Python starter mirrors A1 to A4 with pytest tests', () => {
  const root = new URL('../../python/', import.meta.url);
  for (const f of ['app.py', 'list_offer.py', 'requirements.txt', 'lax-offer.json', 'tests/test_starter.py']) assert.ok(existsSync(new URL(f, root)), f);
  const tests = readFileSync(new URL('tests/test_starter.py', root), 'utf8');
  for (const id of ['A1', 'A2', 'A3', 'A4']) assert.match(tests, new RegExp(`def test_${id.toLowerCase()}_`), id);
  for (const line of readFileSync(new URL('requirements.txt', root), 'utf8').split('\n').filter((l) => l.trim() && !l.startsWith('#'))) {
    assert.match(line, /==/, `pinned: ${line}`);
  }
});

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