import { readFile } from "node:fs/promises"; import { randomUUID } from "node:crypto"; import { setTimeout } from "node:timers/promises"; import { x402Client } from "@x402/core/client"; import { decodePaymentRequiredHeader, encodePaymentSignatureHeader, } from "@x402/core/http"; import { ExactEvmScheme } from "@x402/evm/exact/client"; import { privateKeyToAccount } from "viem/accounts"; // This example performs a REAL Base Sepolia payment. Never use a mainnet wallet here. const base = process.env.API_URL ?? "http://localhost:3000"; const filename = process.argv[2]; const privateKey = process.env.TEST_PRIVATE_KEY; const maximum = process.env.MAX_PAYMENT_ATOMIC; if ( !filename || !privateKey?.match(/^0x[\da-fA-F]{64}$/) || !maximum?.match(/^[1-9]\d*$/) ) throw new Error( "Set TEST_PRIVATE_KEY and MAX_PAYMENT_ATOMIC, then pass a PDF path", ); const form = new FormData(); form.set( "file", new Blob([await readFile(filename)], { type: "application/pdf" }), "input.pdf", ); const uploaded = await fetch(`${base}/v1/documents`, { method: "POST", body: form, }); if (!uploaded.ok) throw new Error(await uploaded.text()); const doc = (await uploaded.json()) as { id: string; token: string }; const headers = { authorization: `Bearer ${doc.token}` }; const quoteResponse = await fetch(`${base}/v1/documents/${doc.id}/quotes`, { method: "POST", headers: { ...headers, "content-type": "application/json" }, body: JSON.stringify({ ocr: "auto" }), }); if (!quoteResponse.ok) throw new Error(await quoteResponse.text()); const quote = (await quoteResponse.json()) as { id: string; amount: string; network: string; executeUrl: string; }; if (quote.network !== "eip155:84532" || BigInt(quote.amount) > BigInt(maximum)) throw new Error("Quote exceeds spending cap or is not testnet"); const executeUrl = `${base}/v1/quotes/${quote.id}/execute`; const paymentHeaders = { ...headers, "Idempotency-Key": randomUUID() }; const challengeResponse = await fetch(executeUrl, { method: "POST", headers: paymentHeaders, }); if (challengeResponse.status !== 402) throw new Error(await challengeResponse.text()); const encoded = challengeResponse.headers.get("PAYMENT-REQUIRED"); if (!encoded) throw new Error("Missing x402 requirements"); const required = decodePaymentRequiredHeader(encoded); if ( required.accepts.length !== 1 || required.accepts[0]!.network !== quote.network || required.accepts[0]!.amount !== quote.amount || required.resource?.url !== quote.executeUrl ) throw new Error("Payment requirements do not match quote"); const client = new x402Client().register( "eip155:84532", new ExactEvmScheme(privateKeyToAccount(privateKey as `0x${string}`)), ); const payment = await client.createPaymentPayload(required); const accepted = await fetch(executeUrl, { method: "POST", headers: { ...paymentHeaders, "PAYMENT-SIGNATURE": encodePaymentSignatureHeader(payment), }, }); if (accepted.status !== 202) throw new Error( `Payment attempt: ${await accepted.text()}. Do not create another payment; retain quote ${quote.id}.`, ); for (let attempt = 0; attempt < 600; attempt++) { const statusResponse = await fetch(`${base}/v1/jobs/${quote.id}`, { headers, }); if (!statusResponse.ok) throw new Error(await statusResponse.text()); const status = (await statusResponse.json()) as { status: string; error?: string; }; if (status.status === "succeeded") { const result = await fetch( `${base}/v1/jobs/${quote.id}/result?format=markdown`, { headers }, ); if (!result.ok) throw new Error(await result.text()); console.log(await result.text()); break; } if (status.status === "failed") throw new Error(status.error ?? "Extraction failed"); if (attempt === 599) throw new Error( `Polling timed out; retain quote ${quote.id} and document token securely`, ); await setTimeout(1000); }