Skip to main content
OnchainDB supports paid read operations using the HTTP 402 Payment Required protocol. When a query requires payment, the SDK throws PaymentRequiredError containing a quote, allowing you to pay and retry.

How It Works

1. App executes query
       |
2. Server determines payment required
       |
3. SDK throws PaymentRequiredError with X402Quote
       |
4. App handles error and processes payment
       |
5. App re-queries with payment proof
       |
6. Server returns data

Using QueryBuilder

QueryBuilder.execute() throws PaymentRequiredError when payment is required:
import { createClient, PaymentRequiredError, X402Quote } from '@onchaindb/sdk';

try {
  const result = await client.queryBuilder()
    .collection('premium_data')
    .selectAll()
    .execute();

  // Query succeeded - process records
  console.log('Records:', result.records);
} catch (error) {
  if (error instanceof PaymentRequiredError) {
    const quote: X402Quote = error.quote;

    console.log('Payment required!');
    console.log('Quote ID:', quote.quoteId);
    console.log('Total cost:', quote.totalCostTia, 'TIA');
    console.log('Pay to:', quote.brokerAddress);
    console.log('Network:', quote.network);
    console.log('Quote expires at:', new Date(quote.expiresAt * 1000));
  }
}

Pay and Re-Query

After paying, include the quote_id and payment proof in your query:
// User makes payment to brokerAddress from X402Quote
const paymentTxHash = await makePayment(
  quote.brokerAddress,
  quote.totalCostTia
);

// Re-query with payment proof
const paidResult = await client.query({
  collection: 'premium_data',
  select: { sensitive_field: true, another_field: true },
  quote_id: quote.quoteId,
  payment_proof: paymentTxHash
});

// Now you get the actual data
console.log('Records:', paidResult.records);

Complete Payment Flow

import { createClient, PaymentRequiredError, X402Quote } from '@onchaindb/sdk';

const client = createClient({
  endpoint: 'https://api.onchaindb.io',
  appKey: 'your-app-key',
  appId: 'your-app-id'
});

async function queryWithPayment() {
  try {
    // Query using QueryBuilder
    const result = await client.queryBuilder()
      .collection('premium_data')
      .whereField('category').equals('exclusive')
      .selectFields(['title', 'content', 'price'])
      .limit(10)
      .execute();

    // No payment required - free query
    console.log(`Received ${result.records.length} records`);
    return result.records;

  } catch (error) {
    if (error instanceof PaymentRequiredError) {
      const quote: X402Quote = error.quote;

      console.log(`Payment required: ${quote.totalCostTia} TIA`);
      console.log(`Pay to: ${quote.brokerAddress}`);

      // Make payment using your wallet
      const paymentTx = await wallet.sendTokens(
        quote.brokerAddress,
        `${quote.totalCostTia}tia`,
        'OnchainDB Read Payment'
      );

      // Re-query with payment proof
      const paidResult = await client.query({
        collection: 'premium_data',
        find: { category: { is: 'exclusive' } },
        select: { title: true, content: true, price: true },
        limit: 10,
        quote_id: quote.quoteId,
        payment_proof: paymentTx.transactionHash
      });

      console.log(`Received ${paidResult.records.length} records`);
      return paidResult.records;
    }
    throw error;
  }
}

Error Types

import {
  PaymentRequiredError,
  PaymentVerificationError,
  OnchainDBError,
  X402Quote
} from '@onchaindb/sdk';

try {
  const result = await client.queryBuilder()
    .collection('paid_content')
    .selectAll()
    .execute();
} catch (error) {
  if (error instanceof PaymentRequiredError) {
    // Payment is required
    const quote: X402Quote = error.quote;
    console.log('Payment required');
    console.log('Quote ID:', quote.quoteId);
    console.log('Total cost:', quote.totalCostTia, 'TIA');
    console.log('Pay to:', quote.brokerAddress);
    console.log('Network:', quote.network);
    console.log('Expires at:', new Date(quote.expiresAt * 1000));
    console.log('All payment options:', quote.allOptions);
  } else if (error instanceof PaymentVerificationError) {
    // Payment verification failed
    console.log('Payment verification failed');
    console.log('Transaction hash:', error.txHash);
    console.log('Error:', error.message);
  } else if (error instanceof OnchainDBError) {
    // General OnchainDB error
    console.log('OnchainDB error:', error.code, error.statusCode);
  }
}

Type-Safe Payment Handling

import { PaymentRequiredError, X402Quote, QueryResponse } from '@onchaindb/sdk';

async function handleQuery<T extends Record<string, any>>(): Promise<T[] | null> {
  try {
    const result = await client.queryBuilder()
      .collection('data')
      .selectAll()
      .execute<T>();

    return result.records;
  } catch (error) {
    if (error instanceof PaymentRequiredError) {
      const quote: X402Quote = error.quote;
      // Handle payment flow with type-safe quote
      console.log('Payment required:', quote.totalCostTia, 'TIA');
      return null;
    }
    throw error;
  }
}

Use Cases

Use CaseDescription
Premium ContentCharge for access to exclusive data
API MonetizationMonetize data access with per-query pricing
Cost RecoveryRecover computational costs for expensive queries
Tiered AccessFree basic queries, paid for detailed fields
Data MarketplaceBuild marketplaces where data providers set prices

Next Steps