101 lines
3.0 KiB
TypeScript
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();
|