/** * Minimal x402-enabled Express API using thirdweb settlePayment. * Supports Chain 138, Alltra (651940) with USDC + local verification. * See docs/04-configuration/X402_ALLTRA_ENDPOINT_SPEC.md and CHAIN138_X402_TOKEN_SUPPORT.md. */ import "dotenv/config"; import express from "express"; import { createThirdwebClient, defineChain } from "thirdweb"; import { facilitator, settlePayment } from "thirdweb/x402"; import { arbitrumSepolia } from "thirdweb/chains"; import { randomUUID } from "crypto"; const app = express(); app.use(express.json()); const PORT = process.env.PORT || 4020; const secretKey = process.env.THIRDWEB_SECRET_KEY; const serverWalletAddress = process.env.SERVER_WALLET_ADDRESS; const useChain138 = process.env.X402_USE_CHAIN_138 === "true"; const useAlltra = process.env.X402_USE_ALLTRA === "true"; const rpcUrl138 = process.env.RPC_URL_138 || "https://rpc-http-pub.d-bis.org"; const rpcUrl651940 = process.env.CHAIN_651940_RPC_URL || process.env.RPC_URL_651940 || "https://mainnet-rpc.alltra.global"; /** Custom Chain 138 for thirdweb (DeFi Oracle Meta Mainnet) */ const chain138 = defineChain({ id: 138, name: "DeFi Oracle Meta Mainnet", rpc: rpcUrl138, nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18, }, }); /** Chain 651940 — ALL Mainnet (Alltra); default for Alltra-native x402 + USDC */ const chain651940 = defineChain({ id: 651940, name: "ALL Mainnet", rpc: rpcUrl651940, nativeCurrency: { name: "Ether", symbol: "ETH", decimals: 18, }, blockExplorers: [ { name: "Alltra", url: "https://alltra.global" }, ], }); const client = secretKey ? createThirdwebClient({ secretKey }) : null; const thirdwebFacilitator = client && serverWalletAddress ? facilitator({ client, serverWalletAddress, }) : null; /** Resolve network: Alltra (651940) if enabled, else Chain 138 if enabled, else Arbitrum Sepolia for testing. */ function getNetwork() { if (useAlltra && thirdwebFacilitator) { return chain651940; } if (useChain138 && thirdwebFacilitator) { return chain138; } return arbitrumSepolia; } /** Alltra USDC (AUSDC) — docs/11-references/ADDRESS_MATRIX_AND_STATUS.md §2.3 */ const ALLTRA_USDC_ADDRESS = "0xa95EeD79f84E6A0151eaEb9d441F9Ffd50e8e881"; /** Replay store: (payer:resourceId:nonce) -> expiresAt (ms). In production use Redis/DB. */ const replayStore = new Map(); const REPLAY_TTL_MS = 15 * 60 * 1000; // 15 min function replayKey(payer, resourceId, nonce) { return `${payer.toLowerCase()}:${resourceId}:${nonce}`; } function isReplayConsumed(payer, resourceId, nonce) { const key = replayKey(payer, resourceId, nonce); const exp = replayStore.get(key); if (!exp) return false; if (Date.now() > exp) { replayStore.delete(key); return false; } return true; } function markReplayConsumed(payer, resourceId, nonce) { replayStore.set(replayKey(payer, resourceId, nonce), Date.now() + REPLAY_TTL_MS); } /** Price: Alltra USDC, Chain 138 cUSDC, or Arbitrum Sepolia default. */ function getPrice() { if (useAlltra) { return { amount: "10000", asset: { address: ALLTRA_USDC_ADDRESS, decimals: 6 }, }; } if (useChain138) { const cusdc138 = "0xf22258f57794CC8E06237084b353Ab30fFfa640b"; return { amount: "10000", asset: { address: cusdc138, decimals: 6 }, }; } return "$0.01"; } /** Build PaymentRequired for Alltra (651940) USDC — see X402_ALLTRA_ENDPOINT_SPEC.md */ function buildPaymentRequired(resourceId) { const expiresAt = new Date(Date.now() + 5 * 60 * 1000).toISOString(); return { network: "eip155:651940", asset: ALLTRA_USDC_ADDRESS, amount: "10000", recipient: serverWalletAddress, nonce: randomUUID(), expiresAt, resourceId, }; } /** Verify settlement on 651940 via eth_getTransactionReceipt */ async function verifySettlementOnChain(txHash) { const body = JSON.stringify({ jsonrpc: "2.0", id: 1, method: "eth_getTransactionReceipt", params: [txHash], }); const r = await fetch(rpcUrl651940, { method: "POST", headers: { "Content-Type": "application/json" }, body, }); const data = await r.json(); const receipt = data?.result; return receipt && receipt.status === "0x1"; } /** Alltra-native local verification: 402 + PAYMENT-REQUIRED when unpaid; verify PAYMENT-SIGNATURE and settlement. */ async function handlePaidRouteAlltra(req, res) { if (!serverWalletAddress) { return res.status(503).json({ error: "x402 not configured", hint: "Set SERVER_WALLET_ADDRESS in .env", }); } const resourceId = `${req.method} ${req.originalUrl || req.url}`; const paymentData = req.headers["payment-signature"] || req.headers["PAYMENT-SIGNATURE"] || req.headers["x-payment"] || req.headers["X-PAYMENT"]; if (!paymentData || paymentData.trim() === "") { const paymentRequired = buildPaymentRequired(resourceId); const headerValue = Buffer.from(JSON.stringify(paymentRequired), "utf8").toString("base64"); return res .status(402) .set("PAYMENT-REQUIRED", headerValue) .json({ error: "Payment required", paymentRequired: { ...paymentRequired, amount: paymentRequired.amount } }); } let payload; try { payload = JSON.parse(Buffer.from(paymentData, "base64").toString("utf8")); } catch { return res.status(400).json({ error: "Invalid PAYMENT-SIGNATURE: not base64 JSON" }); } const { payer, paymentRequired: pr, txHash } = payload; if (!payer || !pr || !txHash) { return res.status(400).json({ error: "PAYMENT-SIGNATURE must include payer, paymentRequired, txHash" }); } if (pr.recipient?.toLowerCase() !== serverWalletAddress?.toLowerCase() || pr.asset?.toLowerCase() !== ALLTRA_USDC_ADDRESS.toLowerCase()) { return res.status(400).json({ error: "Payment intent does not match (recipient or asset)" }); } if (new Date(pr.expiresAt) < new Date()) { return res.status(400).json({ error: "Payment expired" }); } if (isReplayConsumed(payer, resourceId, pr.nonce)) { return res.status(400).json({ error: "Replay: payment already consumed" }); } const ok = await verifySettlementOnChain(txHash); if (!ok) { return res.status(400).json({ error: "Settlement verification failed: invalid or failed tx on 651940" }); } markReplayConsumed(payer, resourceId, pr.nonce); return res.json({ data: "paid content", message: "Payment settled successfully (Alltra local verification)", }); } /** Shared handler for paid routes (PAYMENT-SIGNATURE or X-PAYMENT header) — thirdweb facilitator path. */ async function handlePaidRoute(req, res) { if (useAlltra) { return handlePaidRouteAlltra(req, res); } const paymentData = req.headers["payment-signature"] || req.headers["PAYMENT-SIGNATURE"] || req.headers["x-payment"] || req.headers["X-PAYMENT"]; if (!thirdwebFacilitator || !serverWalletAddress) { return res.status(503).json({ error: "x402 not configured", hint: "Set THIRDWEB_SECRET_KEY and SERVER_WALLET_ADDRESS in .env", }); } const resourceUrl = (req.protocol + "://" + req.get("host") + req.originalUrl) || ""; const method = req.method; const result = await settlePayment({ resourceUrl, method, paymentData: paymentData || undefined, payTo: serverWalletAddress, network: getNetwork(), price: getPrice(), facilitator: thirdwebFacilitator, routeConfig: { description: "Access to paid API content", mimeType: "application/json", maxTimeoutSeconds: 60 * 60, }, }); if (result.status === 200) { return res.json({ data: "paid content", message: "Payment settled successfully", }); } res .status(result.status) .set(result.responseHeaders || {}) .json(result.responseBody ?? { error: "Payment required" }); } /** Protected routes: require x402 payment (PAYMENT-SIGNATURE or X-PAYMENT header). */ app.get("/api/premium", handlePaidRoute); app.get("/api/paid", handlePaidRoute); /** Health: no payment required. */ app.get("/health", (req, res) => { const chainName = useAlltra ? "alltra-651940" : useChain138 ? "chain138" : "arbitrumSepolia"; res.json({ ok: true, x402: !!thirdwebFacilitator, chain: chainName, }); }); app.listen(PORT, () => { console.log(`x402-api listening on port ${PORT}`); if (!thirdwebFacilitator) { console.warn("THIRDWEB_SECRET_KEY or SERVER_WALLET_ADDRESS not set; /api/premium will return 503."); } else { const chainName = useAlltra ? "ALL Mainnet (651940) USDC" : useChain138 ? "Chain 138" : "Arbitrum Sepolia (default USDC)"; console.log(`Payment chain: ${chainName}`); } });