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