Quickstart
Client SDK

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:

  1. Node.js 18+ installed
  2. A wallet with testnet funds:
  3. 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/client

Step 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:

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.ts

You 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:

  1. Detected 402 - Recognized the payment requirement
  2. Parsed challenge - Extracted payment details from the response
  3. Signed payments - Used your wallet to sign two USDC transfers:
    • 98% to the API provider
    • 2% to Preimage Research platform
  4. Submitted to facilitator - Sent signed payments for on-chain verification
  5. Retried request - Included authorization proof in retry
  6. 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

NetworkChain IDUse Case
base-sepolia84532Testing & development
base8453Production (recommended)
ethereum-sepolia11155111Testing on Ethereum
ethereum1Production on Ethereum
polygon-amoy80002Testing on Polygon
polygon137Production 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

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: