import { tool } from '@langchain/core/tools'; import { isDeepStrictEqual } from 'node:util'; import { z } from 'zod'; import { getAddress, isAddress } from 'viem'; import { ExactEvmScheme } from '@x402/evm/exact/client'; import { x402Client, wrapFetchWithPayment, decodePaymentResponseHeader } from '@x402/fetch'; export const BRIEF_URL = 'https://www.blockchainiq.co/api/x402/v1/brief'; export const NETWORK = 'eip155:8453'; export const USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913'; export const AMOUNT = '10000'; const ZERO = '0x0000000000000000000000000000000000000000'; const MAX_BODY = 1024 * 1024; const 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' }, ], }; export class BriefError extends Error { constructor(code, paymentMayHaveCommitted = false) { super(code); this.name = 'BriefError'; this.code = code; this.paymentMayHaveCommitted = paymentMayHaveCommitted; } } function requireThat(condition, code) { if (!condition) throw new BriefError(code); } function object(value) { return value !== null && typeof value === 'object' && !Array.isArray(value); } function address(value, code = 'INVALID_ADDRESS') { requireThat(typeof value === 'string' && isAddress(value, { strict: false }), code); const result = getAddress(value); requireThat(result !== ZERO, code); return result; } function sameAddress(a, b) { return typeof a === 'string' && a.toLowerCase() === b.toLowerCase(); } /** Pure guard; called before constructing or invoking the wallet signer. */ export function validateChallenge(challenge, expectedPayTo) { requireThat(object(challenge) && challenge.x402Version === 2, 'UNSUPPORTED_X402_VERSION'); requireThat(object(challenge.resource) && challenge.resource.url === BRIEF_URL, 'WRONG_RESOURCE'); requireThat(challenge.resource.mimeType === undefined || challenge.resource.mimeType === 'application/json', 'WRONG_RESOURCE_TYPE'); requireThat(Array.isArray(challenge.accepts) && challenge.accepts.length === 1, 'AMBIGUOUS_PAYMENT_OPTIONS'); const req = challenge.accepts[0]; requireThat(object(req), 'MALFORMED_REQUIREMENTS'); const allowed = new Set(['scheme', 'network', 'asset', 'amount', 'payTo', 'maxTimeoutSeconds', 'extra']); requireThat(Object.keys(req).every(k => allowed.has(k)), 'UNKNOWN_PAYMENT_FIELD'); requireThat(req.scheme === 'exact', 'WRONG_SCHEME'); requireThat(req.network === NETWORK, 'WRONG_NETWORK'); requireThat(sameAddress(req.asset, USDC), 'WRONG_ASSET'); requireThat(req.amount === AMOUNT, 'WRONG_AMOUNT'); const payTo = address(req.payTo, 'INVALID_RECIPIENT'); if (expectedPayTo !== undefined) { requireThat(payTo === address(expectedPayTo), 'WRONG_RECIPIENT'); } requireThat(Number.isSafeInteger(req.maxTimeoutSeconds) && req.maxTimeoutSeconds >= 1 && req.maxTimeoutSeconds <= 300, 'UNSAFE_AUTHORIZATION_WINDOW'); requireThat(object(req.extra) && req.extra.name === 'USD Coin' && req.extra.version === '2', 'WRONG_USDC_DOMAIN'); requireThat(Object.keys(req.extra).every(k => ['name', 'version', 'assetTransferMethod'].includes(k)), 'UNKNOWN_PAYMENT_EXTRA'); requireThat(req.extra.assetTransferMethod === undefined || req.extra.assetTransferMethod === 'eip3009', 'UNSUPPORTED_TRANSFER_METHOD'); // Bazaar describes discovery only. No extension handlers are registered. requireThat(challenge.extensions === undefined || (object(challenge.extensions) && Object.keys(challenge.extensions).every(k => k === 'bazaar') && (challenge.extensions.bazaar === undefined || object(challenge.extensions.bazaar))), 'UNKNOWN_PAYMENT_EXTENSION'); return req; } /** Defense in depth over the exact EIP-3009 message sent to signTypedData. */ export function validateAuthorization(input, payer, payTo, timeout, now = Math.floor(Date.now() / 1000)) { requireThat(object(input) && input.primaryType === 'TransferWithAuthorization' && JSON.stringify(input.types) === JSON.stringify(TYPES), 'WRONG_AUTHORIZATION_TYPE'); const { domain: d, message: m } = input; requireThat(object(d) && d.name === 'USD Coin' && d.version === '2' && d.chainId === 8453 && sameAddress(d.verifyingContract, USDC) && Object.keys(d).length === 4, 'WRONG_AUTHORIZATION_DOMAIN'); requireThat(object(m) && Object.keys(m).length === 6 && sameAddress(m.from, payer) && sameAddress(m.to, payTo) && m.value === 10000n && m.validAfter === 0n && typeof m.validBefore === 'bigint' && m.validBefore > BigInt(now) && m.validBefore <= BigInt(now + timeout + 1) && typeof m.nonce === 'string' && /^0x[0-9a-fA-F]{64}$/.test(m.nonce), 'WRONG_AUTHORIZATION'); } async function readBounded(response, maximum) { requireThat(response.body !== null, 'EMPTY_RESPONSE'); const reader = response.body.getReader(); const chunks = []; let length = 0; try { while (true) { const { done, value } = await reader.read(); if (done) break; length += value.length; if (length > maximum) { await reader.cancel(); throw new BriefError('RESPONSE_TOO_LARGE'); } chunks.push(value); } } finally { reader.releaseLock(); } const bytes = new Uint8Array(length); let offset = 0; for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.length; } return bytes; } function parseJSON(bytes, code) { try { return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes)); } catch { throw new BriefError(code); } } function challengeHeader(response) { const header = response.headers.get('payment-required'); requireThat(typeof header === 'string' && header.length > 0 && header.length <= 32768 && /^[A-Za-z0-9+/]+={0,2}$/.test(header), 'MALFORMED_PAYMENT_REQUIRED'); return parseJSON(Buffer.from(header, 'base64'), 'MALFORMED_PAYMENT_REQUIRED'); } function createTransport(fetchImpl, state, signal, { quote = false, expectedPayTo } = {}) { return async input => { const req = new Request(input); requireThat(req.url === BRIEF_URL && req.method === 'GET' && !req.body, 'FORBIDDEN_REQUEST'); const paid = req.headers.has('payment-signature'); requireThat(!req.headers.has('x-payment'), 'FORBIDDEN_PAYMENT_HEADER'); requireThat(state.requests < (quote ? 1 : 2), 'REQUEST_LIMIT'); requireThat(state.requests === 0 ? !paid : paid && state.signatures === 1, 'UNEXPECTED_REQUEST_SEQUENCE'); if (paid) state.paymentWireStarted = true; state.requests++; const response = await fetchImpl(new Request(req, { redirect: 'error', credentials: 'omit', cache: 'no-store', signal, })); requireThat(!response.redirected && (!response.url || response.url === BRIEF_URL), 'REDIRECT_OR_WRONG_RESPONSE_URL'); requireThat(response.status === (paid ? 200 : 402), paid ? 'PAID_RESPONSE_NOT_200' : 'EXPECTED_PAYMENT_CHALLENGE'); requireThat(/^application\/json(?:;|$)/i.test(response.headers.get('content-type') || ''), 'RESPONSE_NOT_JSON'); if (!paid) { const challenge = challengeHeader(response); validateChallenge(challenge, expectedPayTo); const body = await readBounded(response, 65536); if (body.length) { const bodyJSON = parseJSON(body, 'MALFORMED_CHALLENGE_BODY'); // The v2 header is authoritative. If a v2 body is also supplied it must agree. if (object(bodyJSON) && bodyJSON.x402Version !== undefined) { validateChallenge(bodyJSON, expectedPayTo); requireThat(isDeepStrictEqual(bodyJSON.accepts, challenge.accepts), 'CONFLICTING_PAYMENT_CHALLENGE'); } } state.challenge = challenge; return new Response(body, { status: 402, headers: response.headers }); } const body = await readBounded(response, MAX_BODY); return new Response(body, { status: 200, headers: response.headers }); }; } /** One free, unsigned quote. The recipient must be reviewed and pinned before --pay. */ export async function quoteBrief({ fetchImpl = globalThis.fetch } = {}) { const state = { requests: 0, signatures: 0 }; try { const transport = createTransport(fetchImpl, state, AbortSignal.timeout(20000), { quote: true }); await transport(new Request(BRIEF_URL)); const req = validateChallenge(state.challenge); return { resource: BRIEF_URL, network: NETWORK, asset: USDC, amount: AMOUNT, priceUSDC: '0.01', payTo: address(req.payTo), maxTimeoutSeconds: req.maxTimeoutSeconds, note: 'Unsigned quote only. Review this recipient independently before configuring payment.' }; } catch (error) { throw error instanceof BriefError ? error : new BriefError('QUOTE_FAILED'); } } const envelope = z.object({ source: z.string(), disclaimer: z.string(), terms: z.string(), data: z.object({ asOf: z.string().refine(s => Number.isFinite(Date.parse(s))), coins: z.array(z.object({ coinId: z.string(), symbol: z.string(), price: z.number().finite() }).passthrough()).max(20), events: z.array(z.record(z.string(), z.unknown())).max(1000), flags: z.array(z.string()).max(1000), }).passthrough(), }).passthrough(); /** * Create one single-use LangChain tool. Wallet configuration is application-owned, * never model input. signerFactory is called only after an accepted challenge. */ export function createBlockchainIqBriefTool({ expectedPayTo, signerFactory, fetchImpl = globalThis.fetch } = {}) { const payTo = address(expectedPayTo, 'EXPECTED_RECIPIENT_REQUIRED'); requireThat(typeof signerFactory === 'function' && typeof fetchImpl === 'function', 'INVALID_CONFIGURATION'); let consumed = false; return tool(async () => { requireThat(!consumed, 'TOOL_ALREADY_USED'); consumed = true; const state = { requests: 0, signatures: 0, paymentWireStarted: false }; let signer; const getSigner = () => { requireThat(state.approved === true, 'CHALLENGE_NOT_APPROVED'); if (!signer) { signer = signerFactory(); requireThat(object(signer) && typeof signer.signTypedData === 'function', 'INVALID_SIGNER'); state.payer = address(signer.address, 'INVALID_PAYER'); } return signer; }; const guardedSigner = { get address() { getSigner(); return state.payer; }, async signTypedData(input) { const current = getSigner(); requireThat(state.signatures === 0, 'SIGNATURE_LIMIT'); validateAuthorization(input, state.payer, payTo, state.challenge.accepts[0].maxTimeoutSeconds); state.signatures++; return current.signTypedData(input); }, }; try { const client = x402Client.fromConfig({ schemes: [{ network: NETWORK, client: new ExactEvmScheme(guardedSigner) }], spendControls: { maxAmountPerPayment: '$0.01', allowedAssets: [ { network: NETWORK, asset: USDC, maxAmountPerPayment: AMOUNT }, ] }, }); client.onBeforePaymentCreation(async ({ paymentRequired, selectedRequirements }) => { const approved = validateChallenge(paymentRequired, payTo); requireThat(JSON.stringify(approved) === JSON.stringify(selectedRequirements), 'REQUIREMENTS_CHANGED'); state.challenge = paymentRequired; state.approved = true; }); const transport = createTransport(fetchImpl, state, AbortSignal.timeout(25000), { expectedPayTo: payTo }); const response = await wrapFetchWithPayment(transport, client)(BRIEF_URL); const header = response.headers.get('payment-response'); requireThat(typeof header === 'string' && header.length <= 16384, 'MISSING_SETTLEMENT_PROOF'); let settlement; try { settlement = decodePaymentResponseHeader(header); } catch { throw new BriefError('MALFORMED_SETTLEMENT_PROOF'); } requireThat(object(settlement) && settlement.success === true && settlement.network === NETWORK && typeof settlement.transaction === 'string' && /^0x[0-9a-fA-F]{64}$/.test(settlement.transaction) && sameAddress(settlement.payer, state.payer), 'INVALID_SETTLEMENT_PROOF'); const json = parseJSON(await readBounded(response, MAX_BODY), 'MALFORMED_BRIEF_JSON'); const parsed = envelope.safeParse(json); requireThat(parsed.success, 'MALFORMED_BRIEF_ENVELOPE'); return JSON.stringify({ response: parsed.data, settlement: { transaction: settlement.transaction, payer: address(settlement.payer), network: NETWORK, } }); } catch (error) { // Never surface SDK/signer/server prose, headers or a private key in diagnostics. throw new BriefError(error instanceof BriefError ? error.code : 'BRIEF_REQUEST_FAILED', state.paymentWireStarted); } }, { name: 'blockchainiq_market_brief', description: 'Buy one Blockchain IQ market brief for exactly 0.01 USDC on Base. Includes market data and settlement proof. Single-use; wallet and recipient are application configuration.', schema: z.object({}).strict(), }); }