Client SDK Quickstart
Get started with the Preimage Research Client SDK in under 5 minutes. This guide will help you build a client that automatically handles x402 payments to protected APIs.
Prerequisites
Before you begin, you'll need:
- Node.js 18+ installed
- A wallet with testnet funds:
- Testnet ETH for gas fees (Base Sepolia Faucet (opens in a new tab))
- Testnet USDC for payments
- A protected API to call (or use our example API)
This quickstart uses Base Sepolia testnet. For production, switch to mainnet networks.
Step 1: Install the Client SDK
npm install @preimage/clientStep 2: Get Your Wallet Private Key
Export your wallet's private key. You'll need this to sign USDC payments.
Never commit private keys to version control! Use environment variables or secure key management systems.
Create a .env file:
WALLET_PRIVATE_KEY=0x...Step 3: Create Your First Client
Create a file called client.ts:
import { createClient } from '@preimage/client';
import * as dotenv from 'dotenv';
dotenv.config();
const client = createClient({
gatewayUrl: 'https://api.preimage-research.ai',
walletPrivateKey: process.env.WALLET_PRIVATE_KEY!,
network: 'base-sepolia',
debug: true, // Enable logging to see the payment flow
});
async function main() {
try {
// Make a request to a protected endpoint
const response = await client.request('/v1/weather?city=SF');
console.log('✅ Success!');
console.log('Data:', response.data);
console.log('Receipt:', response.receipt);
console.log('Payment TX:', response.paymentTxHash);
} catch (error) {
console.error('❌ Error:', error.message);
}
}
main();Step 4: Run It
npx ts-node client.tsYou should see output like:
[DEBUG] Making request to /v1/weather?city=SF
[DEBUG] Received 402 Payment Required
[DEBUG] Signing USDC payment #1 (provider): 0.00098 USDC
[DEBUG] Signing USDC payment #2 (platform): 0.00002 USDC
[DEBUG] Submitting payments to facilitator
[DEBUG] Payment verified: 0x789...
[DEBUG] Retrying request with authorization
✅ Success!
Data: { city: 'SF', temperature: 62, conditions: 'Partly cloudy' }
Receipt: eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9...
Payment TX: 0x789abc...What Just Happened?
The client automatically:
- Detected 402 - Recognized the payment requirement
- Parsed challenge - Extracted payment details from the response
- Signed payments - Used your wallet to sign two USDC transfers:
- 98% to the API provider
- 2% to Preimage Research platform
- Submitted to facilitator - Sent signed payments for on-chain verification
- Retried request - Included authorization proof in retry
- Received data + receipt - Got the API response with cryptographic proof
All of this happened in one function call!
Configuration Options
Basic Configuration
const client = createClient({
gatewayUrl: string, // Gateway URL (required)
walletPrivateKey: string, // Your wallet private key (required)
network: string, // Network: 'base-sepolia' | 'base' | 'ethereum' | 'polygon'
debug?: boolean, // Enable verbose logging (default: false)
});Advanced Configuration
const client = createClient({
gatewayUrl: 'https://api.preimage-research.ai',
walletPrivateKey: process.env.WALLET_PRIVATE_KEY!,
network: 'base-sepolia',
// Optional: Custom facilitator URL
facilitatorUrl: 'https://x402.org/facilitator',
// Optional: Request timeout in milliseconds
timeout: 30000, // 30 seconds
// Optional: Enable debug logging
debug: true,
});Making Different Types of Requests
GET Request
const response = await client.request('/api/data');GET with Query Parameters
const response = await client.request('/api/search?q=bitcoin&limit=10');POST Request
const response = await client.request('/api/summarize', {
method: 'POST',
body: {
text: 'Long text to summarize...'
}
});With Custom Headers
const response = await client.request('/api/data', {
headers: {
'X-Custom-Header': 'value'
}
});PUT/PATCH/DELETE
// PUT
await client.request('/api/resource/123', {
method: 'PUT',
body: { name: 'Updated' }
});
// PATCH
await client.request('/api/resource/123', {
method: 'PATCH',
body: { status: 'active' }
});
// DELETE
await client.request('/api/resource/123', {
method: 'DELETE'
});Error Handling
try {
const response = await client.request('/api/data');
console.log(response.data);
} catch (error) {
if (error.message.includes('402')) {
console.error('Payment failed - check your wallet balance');
} else if (error.message.includes('timeout')) {
console.error('Request timed out');
} else if (error.message.includes('network')) {
console.error('Network error - check your internet connection');
} else {
console.error('Unexpected error:', error.message);
}
}Working with Receipts
Receipts are cryptographically signed proofs of payment:
const response = await client.request('/api/data');
if (response.receipt) {
// Receipt is a JWT (JSON Web Signature)
const [header, payload, signature] = response.receipt.split('.');
// Decode payload (Base64)
const receiptData = JSON.parse(
Buffer.from(payload, 'base64').toString()
);
console.log('Payment ID:', receiptData.paymentId);
console.log('Total USDC:', receiptData.totalUsdc);
console.log('Provider USDC:', receiptData.providerUsdc);
console.log('Platform USDC:', receiptData.platformUsdc);
console.log('Timestamp:', receiptData.timestamp);
console.log('Transaction:', receiptData.transactionHash);
}Store receipts! They're perfect for accounting, auditing, and reconciliation.
Supported Networks
| Network | Chain ID | Use Case |
|---|---|---|
| base-sepolia | 84532 | Testing & development |
| base | 8453 | Production (recommended) |
| ethereum-sepolia | 11155111 | Testing on Ethereum |
| ethereum | 1 | Production on Ethereum |
| polygon-amoy | 80002 | Testing on Polygon |
| polygon | 137 | Production on Polygon |
Switching Networks
const client = createClient({
gatewayUrl: 'https://api.preimage-research.ai',
walletPrivateKey: process.env.WALLET_PRIVATE_KEY!,
network: 'base', // or 'base-sepolia' for testnet
});Best Practices
1. Use Environment Variables
// ❌ Bad: Hardcoded private key
const client = createClient({
walletPrivateKey: '0x1234...',
// ...
});
// ✅ Good: From environment
const client = createClient({
walletPrivateKey: process.env.WALLET_PRIVATE_KEY!,
// ...
});2. Handle Errors Gracefully
// ❌ Bad: Unhandled errors
const response = await client.request('/api/data');
// ✅ Good: Try-catch with specific handling
try {
const response = await client.request('/api/data');
// ... use response
} catch (error) {
// Log error, show user message, retry, etc.
console.error('Request failed:', error.message);
}3. Store Receipts
// ✅ Good: Save receipts for accounting
const response = await client.request('/api/data');
if (response.receipt) {
await saveReceiptToDatabase({
receipt: response.receipt,
txHash: response.paymentTxHash,
timestamp: new Date(),
endpoint: '/api/data',
});
}4. Use Production Networks in Production
// ❌ Bad: Testnet in production
const client = createClient({
network: 'base-sepolia', // Don't use testnet in production!
// ...
});
// ✅ Good: Mainnet in production
const client = createClient({
network: process.env.NODE_ENV === 'production' ? 'base' : 'base-sepolia',
// ...
});Next Steps
Complete Client SDK Docs
Explore all client SDK features and API reference
Build an API Provider
Learn how to protect your own API with x402 payments
Troubleshooting
"Insufficient USDC balance"
Your wallet doesn't have enough USDC. Get testnet USDC from a faucet or buy mainnet USDC from an exchange.
"Insufficient ETH for gas"
You need ETH/MATIC/etc. for gas fees. Visit a faucet for testnet or buy from an exchange for mainnet.
"Payment verification failed"
- Check that you're using the correct network ('base-sepolia' vs 'base')
- Ensure your wallet has both USDC and ETH
- Verify the Gateway URL is correct
"Request timeout"
The request took too long. Try increasing the timeout:
const response = await client.request('/api/slow-endpoint', {
timeout: 60000 // 60 seconds
});Example Projects
Check out complete example projects in the GitHub repository:
- CLI Tool: examples/cli-client (opens in a new tab)
- Web App: examples/web-client (opens in a new tab)
- Backend Service: examples/server-client (opens in a new tab)