#!/usr/bin/env node // x402-demo-client.mjs - Demonstrates the x402 payment flow with SnapAPI // Usage: node x402-demo-client.mjs [endpoint] [--dry-run] // // This script shows how an AI agent can discover and pay for API services // using the x402 protocol. No signup, no API keys - just HTTP + USDC. import { createPublicClient, createWalletClient, http, parseAbi } from 'viem'; import { base } from 'viem/chains'; const SNAPAPI_URL = process.env.SNAPAPI_URL || 'https://api.opspawn.com/screenshot-api'; const dryRun = process.argv.includes('--dry-run'); async function main() { console.log('=== x402 Payment Flow Demo ===\n'); // Step 1: Discover available services console.log('1. Discovering services at', SNAPAPI_URL + '/x402'); const discovery = await fetch(SNAPAPI_URL + '/x402'); const catalog = await discovery.json(); console.log(` Service: ${catalog.service.name} by ${catalog.service.provider}`); console.log(` Payment: ${catalog.payment.token} on ${catalog.payment.network}`); console.log(` Endpoints:`); for (const ep of catalog.endpoints) { console.log(` ${ep.method} ${ep.path} - ${ep.description} (${ep.price})`); } console.log(); // Step 2: Make an unauthenticated request to get payment requirements console.log('2. Requesting screenshot without payment...'); const captureUrl = `${SNAPAPI_URL}/api/capture?url=https://example.com`; const noAuthResponse = await fetch(captureUrl); console.log(` Status: ${noAuthResponse.status} (${noAuthResponse.statusText})`); if (noAuthResponse.status === 402) { // Extract x402 payment requirements from response const paymentRequired = noAuthResponse.headers.get('payment-required'); if (paymentRequired) { try { const requirements = JSON.parse(Buffer.from(paymentRequired, 'base64').toString()); console.log(' Payment Requirements:'); console.log(` Scheme: ${requirements.scheme || 'exact'}`); console.log(` Network: ${requirements.network}`); console.log(` Pay to: ${requirements.payTo}`); console.log(` Price: ${requirements.maxAmountRequired} (raw USDC units)`); if (requirements.extensions) { console.log(' Bazaar Discovery Extensions:'); console.log(` ${JSON.stringify(requirements.extensions, null, 2).split('\n').join('\n ')}`); } } catch (e) { console.log(' Payment-Required header (base64):', paymentRequired.substring(0, 80) + '...'); } } console.log(); if (dryRun) { console.log('3. [DRY RUN] Skipping actual payment. In production, an agent would:'); console.log(' a. Read the payment requirements from the 402 response'); console.log(' b. Sign a USDC authorization using their wallet private key'); console.log(' c. Retry the request with Payment-Signature header'); console.log(' d. Receive the screenshot as the response body'); console.log(' e. The facilitator settles the USDC transfer on-chain'); console.log('\n Total cost: $0.01 per screenshot, settled on Base L2'); console.log(' No signup. No API key. Just HTTP + USDC.\n'); } else { console.log('3. To complete payment, an x402-enabled agent would sign the authorization'); console.log(' with their wallet and retry the request. See @x402/client for details.\n'); } } else { const body = await noAuthResponse.text(); console.log(` Response: ${body.substring(0, 200)}`); } // Step 3: Show the free endpoint (md2html doesn't need payment) console.log('4. Testing free endpoint (md2html - no payment needed)...'); const md2htmlResponse = await fetch(`${SNAPAPI_URL}/api/md2html`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ markdown: '# Hello from x402 Demo\n\nThis was generated by an AI agent using the x402 protocol.', theme: 'dark' }), }); console.log(` Status: ${md2htmlResponse.status}`); if (md2htmlResponse.ok) { const html = await md2htmlResponse.text(); console.log(` Response: ${html.length} bytes of styled HTML`); console.log(` Preview: ${html.substring(0, 100)}...`); } console.log('\n=== Demo Complete ==='); console.log('Learn more: https://x402.org | https://github.com/opspawn/screenshot-api'); } main().catch(err => { console.error('Error:', err.message); process.exit(1); });