import test from 'node:test'; import assert from 'node:assert/strict'; import { spawnSync } from 'node:child_process'; import { fileURLToPath } from 'node:url'; import { BRIEF_URL, NETWORK, USDC, createBlockchainIqBriefTool, quoteBrief, validateAuthorization } from '../src/index.js'; // Synthetic public fixture addresses and mock signatures; no private keys or RPC. const PAY_TO = '0x2222222222222222222222222222222222222222'; const PAYER = '0x1111111111111111111111111111111111111111'; const OTHER = '0x3333333333333333333333333333333333333333'; const TX = `0x${'ab'.repeat(32)}`; const encode = value => Buffer.from(JSON.stringify(value)).toString('base64'); const clone = value => structuredClone(value); const CHALLENGE = { x402Version: 2, resource: { url: BRIEF_URL, mimeType: 'application/json' }, accepts: [{ scheme: 'exact', network: NETWORK, asset: USDC, amount: '10000', payTo: PAY_TO, maxTimeoutSeconds: 60, extra: { name: 'USD Coin', version: '2' } }], }; const BRIEF = { source: 'Blockchain IQ', disclaimer: 'Market data and analysis, not investment advice.', terms: 'https://www.blockchainiq.co/terms', flagCount: 1, eventCount: 0, data: { asOf: '2026-10-26T18:00:00Z', coins: [ { coinId: 'bitcoin', symbol: 'btc', price: 100000, change24h: 1 }, ], events: [], flags: ['Synthetic fixture only'] }, }; const SETTLEMENT = { success: true, transaction: TX, network: NETWORK, payer: PAYER }; function harness({ challenge = CHALLENGE, result = BRIEF, settlement = SETTLEMENT, initialStatus = 402, paidStatus = 200, failPaid = false, paymentHeader, initialBody, responseMutator } = {}) { const state = { requests: [], signerFactories: 0, signatures: [], paidPayload: null }; const fetchImpl = async req => { state.requests.push(req); if (state.requests.length === 1) { const response = new Response(initialBody ?? JSON.stringify(challenge), { status: initialStatus, headers: { 'content-type': 'application/json', 'payment-required': paymentHeader ?? encode(challenge) }, }); if (responseMutator) responseMutator(response); return response; } state.paidPayload = JSON.parse(Buffer.from(req.headers.get('payment-signature'), 'base64')); if (failPaid) throw new Error('synthetic transport timeout; do not retry'); return new Response(JSON.stringify(result), { status: paidStatus, headers: { 'content-type': 'application/json', 'payment-response': encode(settlement) } }); }; const signerFactory = () => { state.signerFactories++; return { address: PAYER, signTypedData: async input => { state.signatures.push(input); return `0x${'44'.repeat(65)}`; } }; }; const tool = createBlockchainIqBriefTool({ expectedPayTo: PAY_TO, signerFactory, fetchImpl }); return { state, tool, fetchImpl }; } test('real LangChain tool.invoke uses the pinned SDK flow and returns safe settlement plus JSON', async () => { const { state, tool } = harness(); assert.equal(tool.name, 'blockchainiq_market_brief'); assert.equal(state.signerFactories, 0); const result = JSON.parse(await tool.invoke({})); assert.deepEqual(result.response, BRIEF); assert.deepEqual(result.settlement, SETTLEMENT_WITHOUT_SUCCESS()); assert.equal(state.requests.length, 2); for (const req of state.requests) { assert.equal(req.url, BRIEF_URL); assert.equal(req.method, 'GET'); assert.equal(req.redirect, 'error'); assert.equal(req.credentials, 'omit'); } assert.equal(state.requests[0].headers.has('payment-signature'), false); assert.equal(state.signerFactories, 1); assert.equal(state.signatures.length, 1); const signed = state.signatures[0]; assert.equal(signed.domain.chainId, 8453); assert.equal(signed.domain.verifyingContract.toLowerCase(), USDC.toLowerCase()); assert.equal(signed.message.to, PAY_TO); assert.equal(signed.message.value, 10000n); assert.equal(state.paidPayload.accepted.amount, '10000'); assert.equal(state.paidPayload.payload.authorization.from, PAYER); assert.equal(JSON.stringify(result).includes('signature'), false); }); function SETTLEMENT_WITHOUT_SUCCESS() { return { transaction: TX, payer: PAYER, network: NETWORK }; } const invalidChallenges = [ ['wrong network', c => { c.accepts[0].network = 'eip155:1'; }], ['wrong asset', c => { c.accepts[0].asset = OTHER; }], ['higher amount', c => { c.accepts[0].amount = '10001'; }], ['lower/different amount', c => { c.accepts[0].amount = '9999'; }], ['numeric amount', c => { c.accepts[0].amount = 10000; }], ['unreviewed recipient', c => { c.accepts[0].payTo = OTHER; }], ['zero recipient', c => { c.accepts[0].payTo = `0x${'0'.repeat(40)}`; }], ['other endpoint', c => { c.resource.url = 'https://www.blockchainiq.co/api/x402/v1/event-risk'; }], ['other origin', c => { c.resource.url = 'https://example.com/api/x402/v1/brief'; }], ['x402 v1', c => { c.x402Version = 1; }], ['two payment choices', c => { c.accepts.push(clone(c.accepts[0])); }], ['unknown fee', c => { c.accepts[0].fee = '1'; }], ['unknown fee extra', c => { c.accepts[0].extra.feeRecipient = OTHER; }], ['permit2', c => { c.accepts[0].extra.assetTransferMethod = 'permit2'; }], ['wrong USDC domain', c => { c.accepts[0].extra.name = 'Other Coin'; }], ['long authorization', c => { c.accepts[0].maxTimeoutSeconds = 10000; }], ['unknown signing extension', c => { c.extensions = { eip2612GasSponsoring: {} }; }], ]; for (const [name, mutate] of invalidChallenges) { test(`${name} stops before signer construction or a paid request`, async () => { const challenge = clone(CHALLENGE); mutate(challenge); const { state, tool } = harness({ challenge }); await assert.rejects(tool.invoke({}), error => error.paymentMayHaveCommitted === false); assert.equal(state.signerFactories, 0); assert.equal(state.signatures.length, 0); assert.equal(state.requests.length, 1); }); } test('application must configure recipient; it cannot be model input', async () => { assert.throws(() => createBlockchainIqBriefTool({ signerFactory() {} }), { code: 'EXPECTED_RECIPIENT_REQUIRED' }); const { tool, state } = harness(); await assert.rejects(tool.invoke({ payTo: OTHER, privateKey: 'not-a-key' })); assert.equal(state.requests.length, 0); assert.equal(state.signerFactories, 0); }); test('one tool instance cannot pay twice, even on concurrent invocations', async () => { const { tool, state } = harness(); const outcomes = await Promise.allSettled([tool.invoke({}), tool.invoke({})]); assert.equal(outcomes.filter(x => x.status === 'fulfilled').length, 1); assert.equal(state.requests.length, 2); assert.equal(state.signatures.length, 1); }); test('free quote does not construct a signer or send a paid request', async () => { const { fetchImpl, state } = harness(); const quote = await quoteBrief({ fetchImpl }); assert.equal(quote.payTo, PAY_TO); assert.equal(quote.amount, '10000'); assert.equal(state.requests.length, 1); assert.equal(state.signerFactories, 0); assert.equal(state.requests[0].headers.has('payment-signature'), false); }); test('provider refusal is terminal, unread as business prose, without signing', async () => { const { tool, state } = harness({ initialStatus: 403, initialBody: 'private upstream diagnostic' }); await assert.rejects(tool.invoke({}), { code: 'EXPECTED_PAYMENT_CHALLENGE' }); assert.equal(state.requests.length, 1); assert.equal(state.signerFactories, 0); await assert.rejects(tool.invoke({}), { code: 'TOOL_ALREADY_USED' }); }); test('malformed header and conflicting body stop before signer', async () => { for (const options of [{ paymentHeader: 'not base64!' }, { initialBody: JSON.stringify({ ...CHALLENGE, x402Version: 1 }) }]) { const { tool, state } = harness(options); await assert.rejects(tool.invoke({})); assert.equal(state.signerFactories, 0); assert.equal(state.requests.length, 1); } }); test('equivalent body field order and harmless error metadata do not invalidate a quote', async () => { const req = CHALLENGE.accepts[0]; const body = { error: 'Payment required', resource: CHALLENGE.resource, x402Version: 2, accepts: [{ extra: { version: '2', name: 'USD Coin' }, maxTimeoutSeconds: 60, payTo: PAY_TO, amount: '10000', asset: USDC, network: NETWORK, scheme: 'exact' }] }; assert.notEqual(JSON.stringify(body.accepts[0]), JSON.stringify(req)); const { tool, state } = harness({ initialBody: JSON.stringify(body) }); await tool.invoke({}); assert.equal(state.signatures.length, 1); }); test('oversized challenge and paid bodies stop within byte limits and never retry', async () => { const first = harness({ initialBody: ' '.repeat(65537) }); await assert.rejects(first.tool.invoke({}), { code: 'RESPONSE_TOO_LARGE' }); assert.equal(first.state.signerFactories, 0); const paid = harness({ result: { excess: 'x'.repeat(1024 * 1024) } }); await assert.rejects(paid.tool.invoke({}), e => e.code === 'RESPONSE_TOO_LARGE' && e.paymentMayHaveCommitted === true); assert.equal(paid.state.requests.length, 2); assert.equal(paid.state.signatures.length, 1); }); test('redirected response is rejected before signer; request redirect mode also forbids following', async () => { const { tool, state } = harness({ responseMutator: r => Object.defineProperty(r, 'redirected', { value: true }) }); await assert.rejects(tool.invoke({}), { code: 'REDIRECT_OR_WRONG_RESPONSE_URL' }); assert.equal(state.requests.length, 1); assert.equal(state.signerFactories, 0); }); test('paid transport timeout is ambiguous and never re-signs or retries', async () => { const { tool, state } = harness({ failPaid: true }); await assert.rejects(tool.invoke({}), e => e.paymentMayHaveCommitted === true); assert.equal(state.requests.length, 2); assert.equal(state.signatures.length, 1); await assert.rejects(tool.invoke({}), { code: 'TOOL_ALREADY_USED' }); assert.equal(state.requests.length, 2); }); test('malformed successful paid envelope is consumed without another payment', async () => { const { tool, state } = harness({ result: { data: 'unexpected' } }); await assert.rejects(tool.invoke({}), e => e.paymentMayHaveCommitted === true); assert.equal(state.requests.length, 2); assert.equal(state.signatures.length, 1); }); test('failed/repeated402 paid responses do not recover or send a third request', async () => { const { tool, state } = harness({ paidStatus: 402 }); await assert.rejects(tool.invoke({}), e => e.paymentMayHaveCommitted === true); assert.equal(state.requests.length, 2); assert.equal(state.signatures.length, 1); }); test('wrong payer/network/transaction or unsuccessful settlement never becomes proof', async () => { for (const patch of [{ payer: OTHER }, { network: 'eip155:1' }, { transaction: 'not-a-transaction' }, { success: false }]) { const { tool, state } = harness({ settlement: { ...SETTLEMENT, ...patch } }); await assert.rejects(tool.invoke({}), e => e.paymentMayHaveCommitted === true); assert.equal(state.requests.length, 2); assert.equal(state.signatures.length, 1); } }); test('authorization guard independently rejects altered typed data before delegating to signer', async () => { const { tool, state } = harness(); await tool.invoke({}); const valid = state.signatures[0]; validateAuthorization(valid, PAYER, PAY_TO, 60); for (const patch of [{ to: OTHER }, { value: 10001n }, { from: OTHER }, { validAfter: 1n }, { validBefore: BigInt(Math.floor(Date.now() / 1000) + 10000) }]) { assert.throws(() => validateAuthorization({ ...valid, message: { ...valid.message, ...patch } }, PAYER, PAY_TO, 60)); } assert.throws(() => validateAuthorization({ ...valid, domain: { ...valid.domain, chainId: 1 } }, PAYER, PAY_TO, 60)); }); test('default CLI help is offline and does not echo private environment configuration', () => { const cli = fileURLToPath(new URL('../src/cli.js', import.meta.url)); const canary = 'SYNTHETIC_SECRET_MUST_NOT_BE_ECHOED'; const result = spawnSync(process.execPath, [cli], { env: { AGENT_PRIVATE_KEY: canary }, encoding: 'utf8', timeout: 5000, }); assert.equal(result.status, 0); assert.match(result.stdout, /--quote/); assert.equal((result.stdout + result.stderr).includes(canary), false); });