Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 97 additions & 0 deletions examples/x402-client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/**
* x402 Client Example
*
* Demonstrates both the automatic request() flow and a manual
* step-by-step payment flow against an x402-protected server.
*
* Environment variables:
* WALLET_ADDRESS – Sender's Solana wallet address
* SERVER_URL – Base URL of the x402 server (default: http://localhost:3000)
*/
import { ShadowWireClient, X402Client } from '@radr/shadowwire';

const WALLET_ADDRESS = process.env.WALLET_ADDRESS || 'YOUR_WALLET_ADDRESS';
const SERVER_URL = process.env.SERVER_URL || 'http://localhost:3000';

const wallet = {
signMessage: async (_message: Uint8Array) => {
// Replace with real wallet adapter (e.g. Phantom, Solflare)
throw new Error('Implement wallet signing');
},
};

const client = new ShadowWireClient();
const x402 = new X402Client({
client,
wallet,
senderWallet: WALLET_ADDRESS,
defaultToken: 'USDC',
defaultTransferType: 'external',
requestTimeoutMs: 20_000,
});

/**
* Automatic flow — X402Client detects 402, pays, and retries in one call.
*/
async function payForData() {
const result = await x402.request(`${SERVER_URL}/api/data`);

if (result.success) {
console.log('Data:', result.data);
if (result.payment) {
console.log('Paid via:', result.payment.transfer.tx_signature);
console.log('Amount hidden:', result.payment.transfer.amount_hidden);
}
} else {
console.error('Request failed:', result.error);
}
}

/**
* Manual flow — probe, parse requirements, pay, then retry with the header.
*/
async function manualPayFlow() {
// 1. Probe the endpoint
const probe = await fetch(`${SERVER_URL}/api/data`);

if (probe.status !== 402) {
console.log('No payment required, status:', probe.status);
return;
}

// 2. Parse payment requirements
const body = await probe.json();
const requirements = X402Client.parseRequirements(body);

if (!requirements) {
console.error('Could not parse 402 response');
return;
}

console.log('Payment options:', requirements.accepts.length);
const requirement = requirements.accepts[0];
console.log(`Scheme: ${requirement.scheme}, Amount: ${requirement.amount}, Asset: ${requirement.asset}`);

// 3. Execute payment
const payment = await x402.pay(requirement);
if (!payment.success || !payment.paymentHeader) {
console.error('Payment failed:', payment.error);
return;
}

console.log('Payment tx:', payment.transfer?.tx_signature);

// 4. Retry with X-Payment header
const response = await fetch(`${SERVER_URL}/api/data`, {
headers: { 'X-Payment': payment.paymentHeader },
});

if (response.ok) {
console.log('Unlocked:', await response.json());
} else {
console.error('Retry failed:', response.status);
}
}

payForData().catch(console.error);
// manualPayFlow().catch(console.error);
58 changes: 58 additions & 0 deletions examples/x402-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/**
* x402 Server Example
*
* Express server with a paywall-protected route, a free route,
* and a .well-known/x402 discovery endpoint.
*
* Environment variables:
* MERCHANT_WALLET – Solana wallet that receives payments
* FACILITATOR_URL – x402 facilitator endpoint (default: https://x402.kamiyo.ai)
* API_KEY – API key for the facilitator
* PORT – Server port (default: 3000)
*/
import { x402Paywall, createDiscoveryDocument } from '@radr/shadowwire';

const express = require('express');
const app = express();

const MERCHANT_WALLET = process.env.MERCHANT_WALLET || 'YOUR_MERCHANT_WALLET';
const FACILITATOR_URL = process.env.FACILITATOR_URL || 'https://x402.kamiyo.ai';
const API_KEY = process.env.API_KEY || '';
const PORT = Number(process.env.PORT) || 3000;

// Free route — no payment needed
app.get('/api/status', (_req: any, res: any) => {
res.json({ status: 'ok', timestamp: Date.now() });
});

// Paywall-protected route
app.get(
'/api/data',
x402Paywall({
payTo: MERCHANT_WALLET,
amount: 0.01,
asset: 'USDC',
description: 'Premium data endpoint',
facilitatorUrl: FACILITATOR_URL,
apiKey: API_KEY,
onPayment: (info) => {
console.log(`Payment received from ${info.payer}: ${info.signature} (${info.amount} USDC)`);
},
}),
(req: any, res: any) => {
res.json({ data: 'premium content', payment: req.x402 });
}
);

// Discovery endpoint
app.get('/.well-known/x402', (_req: any, res: any) => {
res.json(
createDiscoveryDocument('My API', MERCHANT_WALLET, [
{ path: '/api/data', method: 'GET', price: 0.01, description: 'Premium data' },
], { facilitatorUrl: FACILITATOR_URL })
);
});

app.listen(PORT, () => {
console.log(`x402 server listening on http://localhost:${PORT}`);
});
Loading