Explicit Client
The explicit client (X402Client) provides manual control over the X402 payment flow. You explicitly check for 402 responses, create payments, and retry requests.
When to Use
Use the explicit client when you need:
Fine-grained control over the payment flow
Custom payment logic
Different handling for different payment scenarios
Integration with custom payment systems
Basic Usage
import { X402Client } from '@shade402/client';
import { Keypair } from '@solana/web3.js';
const wallet = Keypair.generate();
const client = new X402Client(wallet, process.env.SOLANA_RPC_URL);
try {
// Make initial request
let response = await client.get('https://api.example.com/data');
// Check if payment required
if (client.paymentRequired(response)) {
// Parse payment request
const paymentRequest = client.parsePaymentRequest(response);
// Create payment
const authorization = await client.createPayment(paymentRequest);
// Retry with payment
response = await client.get('https://api.example.com/data', {
payment: authorization,
});
}
// Process response
console.log(response.data);
} finally {
await client.close();
}HTTP Methods
All standard HTTP methods are supported:
// GET request
const getResponse = await client.get(url, options);
// POST request
const postResponse = await client.post(url, data, options);
// PUT request
const putResponse = await client.put(url, data, options);
// DELETE request
const deleteResponse = await client.delete(url, options);
// Generic request
const response = await client.request('GET', url, options);Payment Flow
Step 1: Initial Request
const response = await client.get('https://api.example.com/data');Step 2: Check Payment Required
if (client.paymentRequired(response)) {
// Payment is required (status 402)
}Step 3: Parse Payment Request
const paymentRequest = client.parsePaymentRequest(response);
console.log('Amount:', paymentRequest.maxAmountRequired);
console.log('Token:', paymentRequest.assetAddress);
console.log('Expires:', paymentRequest.expiresAt);Step 4: Validate Payment Request
// Check if expired
if (paymentRequest.isExpired()) {
throw new Error('Payment request expired');
}
// Check amount
const amount = parseFloat(paymentRequest.maxAmountRequired);
if (amount > maxAllowedAmount) {
throw new Error('Payment amount too high');
}Step 5: Create Payment
// Create payment with exact amount
const authorization = await client.createPayment(paymentRequest);
// Or specify custom amount (must be <= max_amount_required)
const authorization = await client.createPayment(paymentRequest, '0.005');Step 6: Encrypt Resource (Optional)
// Encrypt resource if server public key is available
let encryptedResource: string | undefined;
try {
encryptedResource = client.encryptResource(paymentRequest.resource);
} catch (error) {
// Encryption optional, continue without it
}Step 7: Retry Request
const response = await client.get('https://api.example.com/data', {
payment: authorization,
encryptedResource: encryptedResource,
});Resource Encryption
The client supports resource encryption for enhanced privacy:
// Get 402 response (includes server public key in headers)
const response = await client.get(url);
const paymentRequest = client.parsePaymentRequest(response);
// Server public key is now cached
const publicKey = client.getServerPublicKey();
// Encrypt resource
const encryptedResource = client.encryptResource(paymentRequest.resource);
// Retry with encrypted resource
const retryResponse = await client.get(url, {
payment: authorization,
encryptedResource: encryptedResource,
});Custom Payment Amount
You can pay less than the maximum amount if allowed:
const paymentRequest = client.parsePaymentRequest(response);
// Pay custom amount (must be <= max_amount_required)
const customAmount = '0.005'; // Half of max
const authorization = await client.createPayment(paymentRequest, customAmount);Error Handling
Handle errors at each step:
try {
let response = await client.get(url);
if (client.paymentRequired(response)) {
const paymentRequest = client.parsePaymentRequest(response);
if (paymentRequest.isExpired()) {
throw new Error('Payment request expired');
}
try {
const authorization = await client.createPayment(paymentRequest);
response = await client.get(url, { payment: authorization });
} catch (error) {
if (error instanceof InsufficientFundsError) {
console.log('Insufficient funds');
} else if (error instanceof PaymentExpiredError) {
console.log('Payment expired');
} else {
throw error;
}
}
}
return response.data;
} catch (error) {
console.error('Error:', error);
throw error;
} finally {
await client.close();
}URL Validation
The client validates URLs to prevent SSRF attacks:
Only allows
http://andhttps://schemesBlocks localhost and private IPs (unless
allowLocalis enabled)
For local development:
const client = new X402Client(
wallet,
rpcUrl,
undefined,
true // allowLocal - only for development!
);Complete Example
import { X402Client } from '@shade402/client';
import { Keypair } from '@solana/web3.js';
import {
PaymentExpiredError,
InsufficientFundsError,
} from '@shade402/core';
async function fetchWithExplicitPayment(url: string) {
const wallet = Keypair.generate();
const client = new X402Client(wallet, process.env.SOLANA_RPC_URL);
try {
// Step 1: Initial request
let response = await client.get(url);
// Step 2: Check if payment required
if (!client.paymentRequired(response)) {
return response.data; // No payment needed
}
// Step 3: Parse payment request
const paymentRequest = client.parsePaymentRequest(response);
// Step 4: Validate
if (paymentRequest.isExpired()) {
throw new PaymentExpiredError(paymentRequest);
}
// Step 5: Create payment
const authorization = await client.createPayment(paymentRequest);
// Step 6: Encrypt resource (optional)
let encryptedResource: string | undefined;
try {
encryptedResource = client.encryptResource(paymentRequest.resource);
} catch (error) {
// Continue without encryption
}
// Step 7: Retry with payment
response = await client.get(url, {
payment: authorization,
encryptedResource,
});
return response.data;
} catch (error) {
if (error instanceof InsufficientFundsError) {
console.error('Insufficient funds:', error.requiredAmount);
} else if (error instanceof PaymentExpiredError) {
console.error('Payment expired');
} else {
console.error('Unexpected error:', error);
}
throw error;
} finally {
await client.close();
}
}Best Practices
Always close the client when done
Check payment expiration before paying
Validate payment amounts
Handle errors appropriately
Use resource encryption when available
Never use
allowLocalin productionStore wallet keys securely
Monitor payment transactions
Next Steps
Learn about Automatic Client for simpler workflows
Check out Client Usage for overview
See Examples for more use cases
Last updated
