- 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
103 lines
4.4 KiB
JavaScript
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);
|
|
});
|