Code Examples
Complete, working examples for using Blocky402 API in different languages and scenarios. All examples use the x402 v2 wire format.
Complete Payment Flow
All Node.js examples below use @x402/hedera against hedera:testnet. The exact same HTTP envelope works for EVM (@x402/evm) and SVM (@x402/svm) — only the signer and the payload shape inside paymentPayload differ.
Node.js (Hedera)
import { ExactHederaScheme } from '@x402/hedera/exact/client';
import { createClientHederaSigner, PrivateKey } from '@x402/hedera';
const API_BASE = 'http://localhost:3002'; // or https://api.testnet.blocky402.com
async function completePaymentFlow() {
// 1. Discover supported networks + the Hedera fee-payer the facilitator advertises
const supported = await fetch(`${API_BASE}/supported`).then((r) => r.json());
const hederaKind = supported.kinds.find((k) => k.network === 'hedera:testnet');
if (!hederaKind) throw new Error('Facilitator does not advertise hedera:testnet');
const feePayer = hederaKind.extra?.feePayer ?? supported.signers?.['hedera:*']?.[0];
// 2. Build requirements + sign a v2 PaymentPayload
const paymentRequirements = {
scheme: 'exact',
network: 'hedera:testnet',
amount: '100000', // 100,000 tinybars = 0.001 HBAR
payTo: '0.0.8011510',
maxTimeoutSeconds: 300,
asset: '0.0.0', // HBAR (use a token id for HTS payments)
extra: { feePayer },
};
const signer = createClientHederaSigner(
'0.0.7326075', // payer account id
PrivateKey.fromStringECDSA('0xYourHederaPrivateKey'),
{ network: 'hedera:testnet' },
);
const scheme = new ExactHederaScheme(signer);
const signed = await scheme.createPaymentPayload(2, paymentRequirements);
const paymentPayload = {
x402Version: 2,
scheme: 'exact',
network: 'hedera:testnet',
accepted: paymentRequirements,
payload: signed.payload, // { transaction: 'base64-encoded-TransferTransaction' }
};
const body = { x402Version: 2, paymentPayload, paymentRequirements };
// 3. Verify
const verification = await fetch(`${API_BASE}/verify`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
}).then((r) => r.json());
if (!verification.isValid) {
throw new Error(`Payment invalid: ${verification.invalidMessage ?? verification.invalidReason}`);
}
// 4. Settle
const settlement = await fetch(`${API_BASE}/settle`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body),
}).then((r) => r.json());
if (!settlement.success) {
throw new Error(`Settlement failed: ${settlement.errorMessage ?? settlement.errorReason}`);
}
console.log('Transaction:', settlement.transaction);
// 5. Use the paymentPayload as the X-PAYMENT proof on the resource server
const xPayment = Buffer.from(JSON.stringify(paymentPayload)).toString('base64');
return fetch('https://api.example.com/data', {
headers: { 'X-PAYMENT': xPayment },
}).then((r) => r.json());
}
completePaymentFlow()
.then((data) => console.log('Data:', data))
.catch((error) => console.error('Error:', error));
Python
import base64
import json
import requests
# Real signing in Python depends on your wallet stack. The shape below is
# what flows over HTTP; produce it with your preferred signer.
API_BASE = 'http://localhost:3002' # or https://api.testnet.blocky402.com
def complete_payment_flow(payment_payload):
# 1. Discover supported networks
networks = requests.get(f'{API_BASE}/supported').json()
print('Supported networks:', networks['kinds'])
payment_requirements = payment_payload['accepted']
body = {
'x402Version': 2,
'paymentPayload': payment_payload,
'paymentRequirements': payment_requirements,
}
# 2. Verify
verification = requests.post(f'{API_BASE}/verify', json=body).json()
if not verification['isValid']:
reason = verification.get('invalidMessage') or verification.get('invalidReason')
raise Exception(f'Payment invalid: {reason}')
# 3. Settle
settlement = requests.post(f'{API_BASE}/settle', json=body).json()
if not settlement['success']:
reason = settlement.get('errorMessage') or settlement.get('errorReason')
raise Exception(f'Settlement failed: {reason}')
print('Transaction:', settlement['transaction'])
# 4. Use the paymentPayload as the X-PAYMENT proof
x_payment = base64.b64encode(json.dumps(payment_payload).encode()).decode()
return requests.get('https://api.example.com/data',
headers={'X-PAYMENT': x_payment}).json()
Checking Supported Networks
cURL
#!/bin/bash
API_BASE="http://localhost:3002"
curl -X GET "${API_BASE}/supported" | jq '.'
Node.js
async function getSupportedNetworks() {
const data = await fetch('http://localhost:3002/supported').then((r) => r.json());
console.log('Supported networks:');
data.kinds.forEach((kind) => {
console.log(` - ${kind.network} (${kind.scheme} scheme, v${kind.x402Version})`);
});
return data.kinds;
}
Verifying a Payment
Node.js with Error Handling
async function verifyPayment(paymentPayload, paymentRequirements) {
const response = await fetch('http://localhost:3002/verify', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ x402Version: 2, paymentPayload, paymentRequirements }),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.message ?? 'Verification failed');
}
return response.json();
}
const result = await verifyPayment(paymentPayload, requirements);
if (result.isValid) {
console.log('Payment is valid! Payer:', result.payer);
} else {
console.log('Invalid:', result.invalidMessage ?? result.invalidReason);
}
Settling a Payment
Node.js with Retry Logic
async function settlePayment(paymentPayload, paymentRequirements, maxRetries = 3) {
const body = JSON.stringify({ x402Version: 2, paymentPayload, paymentRequirements });
for (let attempt = 1; attempt <= maxRetries; attempt++) {
const response = await fetch('http://localhost:3002/settle', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body,
});
if (response.status === 429) {
const waitTime = Math.pow(2, attempt) * 1000; // exponential backoff
console.log(`Rate limited, waiting ${waitTime}ms before retry...`);
await new Promise((resolve) => setTimeout(resolve, waitTime));
continue;
}
const result = await response.json();
if (result.success) return result;
throw new Error(result.errorMessage ?? result.errorReason ?? 'Settlement failed');
}
throw new Error(`Settlement failed after ${maxRetries} attempts`);
}
try {
const settlement = await settlePayment(paymentPayload, requirements);
console.log('Settled! Transaction:', settlement.transaction);
} catch (error) {
console.error('Settlement failed:', error);
}
Using Payment Proof
Making a Paid API Request
async function fetchPaidData(apiUrl, paymentPayload) {
// X-PAYMENT is the base64-encoded JSON of the paymentPayload object.
const xPayment = Buffer.from(JSON.stringify(paymentPayload)).toString('base64');
const response = await fetch(apiUrl, {
headers: { 'X-PAYMENT': xPayment },
});
if (response.status === 402) {
throw new Error('Payment required — payment may have expired or is invalid');
}
if (!response.ok) {
throw new Error(`API error: ${response.statusText}`);
}
return response.json();
}
const data = await fetchPaidData('https://api.example.com/data', paymentPayload);
console.log('Data:', data);
Handling 402 Responses
Automatic Payment Flow
async function fetchWithAutoPayment(apiUrl, blocky402Client) {
let response = await fetch(apiUrl);
if (response.status === 402) {
// The resource server returns a v2 PaymentRequired body on 402.
const { accepts } = await response.json();
const requirements = accepts[0];
const paymentPayload = await blocky402Client.signPayment(requirements);
const verification = await blocky402Client.verify(paymentPayload, requirements);
if (verification.isValid) {
await blocky402Client.settle(paymentPayload, requirements);
const xPayment = Buffer.from(JSON.stringify(paymentPayload)).toString('base64');
response = await fetch(apiUrl, { headers: { 'X-PAYMENT': xPayment } });
}
}
return response.json();
}
Multi-Network Support
Network-Agnostic Client
class Blocky402Client {
constructor(apiBase = 'http://localhost:3002') {
this.apiBase = apiBase;
}
async getSupportedNetworks() {
return fetch(`${this.apiBase}/supported`).then((r) => r.json());
}
async verify(paymentPayload, paymentRequirements) {
return fetch(`${this.apiBase}/verify`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ x402Version: 2, paymentPayload, paymentRequirements }),
}).then((r) => r.json());
}
async settle(paymentPayload, paymentRequirements) {
return fetch(`${this.apiBase}/settle`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ x402Version: 2, paymentPayload, paymentRequirements }),
}).then((r) => r.json());
}
}
const client = new Blocky402Client();
const { kinds } = await client.getSupportedNetworks();
// Pick any supported network — the HTTP envelope is identical across families.
const network = 'hedera:testnet';
const verification = await client.verify(paymentPayload, { ...requirements, network });
Error Handling
Comprehensive Error Handler
async function handleBlocky402Request(url, options) {
let response;
try {
response = await fetch(url, options);
} catch (error) {
if (error instanceof TypeError) {
throw new Error('Network error. Check your connection.');
}
throw error;
}
const data = await response.json();
if (!response.ok) {
// NestJS error body: { statusCode, message, error }
switch (response.status) {
case 400:
throw new Error(`Bad Request: ${data.message}`);
case 401:
throw new Error('Authentication required (mainnet only)');
case 422:
throw new Error(`Payment Error: ${data.message}`);
case 429: {
const resetAt = response.headers.get('X-RateLimit-Reset');
throw new Error(`Rate limited. Reset at: ${resetAt}`);
}
case 500:
throw new Error('Server error. Please try again later.');
default:
throw new Error(`Request failed: ${data.message}`);
}
}
return data;
}
More Examples
- See Quickstart Guide for step-by-step tutorials
- Check API Reference for endpoint details
- Review Testnet Guide for testnet-specific examples