rspace-online/scripts/test-x402.ts

101 lines
3.0 KiB
TypeScript

/**
* 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();