/** * test-x402.ts — End-to-end x402 payment test against rSpace rSplat. * * Tests the full 402 flow: * 1. POST to the gated endpoint → expect 402 with payment requirements * 2. Sign USDC payment on Base Sepolia using EIP-3009 * 3. Retry with X-PAYMENT header → expect 200/201 * * Usage: * EVM_PRIVATE_KEY=0x... bun run scripts/test-x402.ts [url] * * Defaults to https://demo.rspace.online/rsplat/api/splats * Wallet needs testnet USDC from https://faucet.circle.com */ import { x402Client, wrapFetchWithPayment } from "@x402/fetch"; import { ExactEvmScheme } from "@x402/evm/exact/client"; import { privateKeyToAccount } from "viem/accounts"; const PRIVATE_KEY = process.env.EVM_PRIVATE_KEY as `0x${string}`; if (!PRIVATE_KEY) { console.error("Set EVM_PRIVATE_KEY env var (0x-prefixed hex private key)"); process.exit(1); } const TARGET_URL = process.argv[2] || "https://demo.rspace.online/api/x402-test"; async function main() { console.log("=== x402 Payment Test ===\n"); console.log("Target:", TARGET_URL); // Step 1: Verify the endpoint returns 402 console.log("\n[1] Testing 402 response (no payment)..."); const raw = await fetch(TARGET_URL, { method: "POST" }); console.log(" Status:", raw.status); if (raw.status !== 402) { console.log(" Expected 402, got", raw.status); console.log(" Body:", await raw.text()); process.exit(1); } const body = (await raw.json()) as { paymentRequirements?: { payTo: string; network: string; maxAmountRequired: string; }; }; console.log(" Payment requirements:"); console.log(" payTo:", body.paymentRequirements?.payTo); console.log(" network:", body.paymentRequirements?.network); console.log(" amount:", body.paymentRequirements?.maxAmountRequired); // Step 2: Set up x402 client with EVM signer console.log("\n[2] Setting up x402 client..."); const account = privateKeyToAccount(PRIVATE_KEY); console.log(" Wallet:", account.address); const client = new x402Client(); client.register("eip155:84532", new ExactEvmScheme(account)); const paidFetch = wrapFetchWithPayment(fetch, client); // Step 3: Make the paid request console.log("\n[3] Making paid request (x402 client handles signing automatically)..."); try { const res = await paidFetch(TARGET_URL, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ test: true }), }); console.log(" Status:", res.status); const resBody = await res.text(); try { console.log( " Response:", JSON.stringify(JSON.parse(resBody), null, 2), ); } catch { console.log(" Response:", resBody.slice(0, 500)); } if (res.ok) { console.log("\n x402 payment successful! USDC transferred on Base Sepolia."); } else if (res.status === 402) { console.log("\n Still 402 — payment signing or verification failed."); console.log( " Check wallet has USDC on Base Sepolia (faucet: https://faucet.circle.com)", ); } } catch (err) { console.error(" Error:", err); } } main();