screenshot-api/x402-demo-client.mjs
OpSpawn 120f033cf5 v3.2: x402 discovery endpoint, demo client, hackathon prep
- Added /x402 endpoint: machine-readable service catalog for agent discovery
- Added x402-demo-client.mjs: demonstrates full x402 payment flow
- Updated README with three distribution layers and discovery docs
- Service catalog includes pricing, input/output schemas, MCP info, Bazaar reference
2026-02-06 10:57:40 +00:00

103 lines
4.4 KiB
JavaScript

#!/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);
});