Skip to main content
OnchainDB stores JSON documents with verifiable storage and automatic payment handling through the x402 protocol.

How It Works

The SDK automatically handles HTTP 402 responses internally. When you pass a payment callback as the second parameter to store(), the SDK:
  1. Attempts the store operation
  2. If the server returns 402 (payment required), the SDK invokes your callback with the payment quote
  3. Your callback executes the blockchain payment and returns { txHash, network }
  4. The SDK automatically retries the store operation with the payment proof
You do NOT need to catch 402 errors or make a second API call - the SDK handles the entire flow.

Store with Payment Callback

// Store with payment callback (x402 flow)
const result = await client.store(
  {
    collection: 'posts',
    data: [{
      title: 'My First Post',
      content: 'Stored on blockchain!',
      author: 'alice'
    }]
  },
  // Payment callback - SDK invokes this when server returns 402
  async (quote) => {
    console.log('Payment required:', quote.totalCostTia, 'TIA');
    console.log('Pay to:', quote.brokerAddress);

    // Execute blockchain payment (e.g., via Keplr)
    const txHash = await wallet.sendTokens(
      quote.brokerAddress,
      quote.totalCostTia
    );

    // Return format is required - SDK uses this to retry the store
    return {
      txHash: txHash,
      network: 'mocha-4' // or 'celestia' for mainnet
    };
  },
  true // waitForConfirmation
);

// SDK automatically retried with payment proof - result contains confirmed data
console.log(`Confirmed at block ${result.block_height}`);

Store Without Payment Callback

If Auto-Pay is enabled (userKey with authz), no callback needed:
// With Auto-Pay enabled, payment happens automatically
const result = await client.store({
  collection: 'data',
  data: [{ example: 'data' }]
});

Wait for Confirmation

The waitForConfirmation option ensures data is confirmed on-chain before returning:
// Without confirmation (returns immediately with ticket)
const quick = await client.store(
  { collection: 'products', data: products },
  paymentCallback,
  false  // Returns immediately
);

console.log('Task ticket:', quick.ticket_id);

// Check status later
const status = await client.getTaskStatus(quick.ticket_id);
console.log('Status:', status.status);

// With confirmation (waits for blockchain)
const confirmed = await client.store(
  { collection: 'products', data: products },
  paymentCallback,
  true  // Waits for on-chain confirmation
);

console.log('Data confirmed at height:', confirmed.block_height);

Payment Methods

OnchainDB supports multiple payment methods:
await client.store(
  { collection: 'data', data: [{ content: 'example' }] },
  async (quote) => {
    const txHash = await wallet.pay(quote.brokerAddress, quote.totalCostTia);
    return { txHash, network: 'mocha-4' };
  }
);

Method 2: Auto-Pay

// Just store without callback - payment is automatic
await client.store({
  collection: 'data',
  data: [{ content: 'example' }]
});

Method 3: Pre-paid with payment_tx_hash

await client.store({
  collection: 'data',
  data: [{ content: 'example' }],
  payment_tx_hash: 'ABC123...'
});

Method 4: Signed Transaction

await client.store({
  collection: 'data',
  data: [{ content: 'example' }],
  signed_payment_tx: {
    signed_tx_bytes: 'base64_encoded_tx',
    user_address: 'celestia1...',
    broker_address: 'celestia1broker...',
    amount_utia: 100000,
    purpose: 'data_storage'
  }
});

Browser CORS Considerations

When using the SDK in a browser, blockchain RPC/REST endpoints don’t include CORS headers. You must create server-side API routes to proxy these requests:
// Required API routes for browser-based apps:
// /api/wallet/account-info - Proxy to blockchain REST API for account info
// /api/wallet/broadcast    - Proxy to blockchain REST API for tx broadcast

// Example Next.js API route: /api/wallet/broadcast
export async function POST(request: Request) {
  const { tx_bytes, mode } = await request.json();

  const response = await fetch('https://api-mocha.pops.one/cosmos/tx/v1beta1/txs', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ tx_bytes, mode })
  });

  return Response.json(await response.json());
}

Keplr Wallet Integration

When integrating with Keplr wallet for payments:
import { Registry } from '@cosmjs/proto-signing';
import { defaultRegistryTypes } from '@cosmjs/stargate';
import { TxRaw } from 'cosmjs-types/cosmos/tx/v1beta1/tx';

// Use Amino signer (more compatible)
const signer = await window.keplr.getOfflineSignerOnlyAmino(chainId);
const accounts = await signer.getAccounts();

// Create and sign transaction
const registry = new Registry(defaultRegistryTypes);
const signedTx = await client.sign(
  accounts[0].address,
  [sendMsg],
  fee,
  memo
);

// Convert to protobuf and broadcast via REST API
const txBytes = TxRaw.encode(signedTx).finish();
const txBytesBase64 = Buffer.from(txBytes).toString('base64');

// Broadcast through your API route (avoids CORS)
const result = await fetch('/api/wallet/broadcast', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ tx_bytes: txBytesBase64, mode: 'BROADCAST_MODE_SYNC' })
});

Append-Only Storage

OnchainDB uses append-only storage on a Data Availability layer:
OperationBehavior
CreateAppends a new record to the blockchain with a unique ID and timestamp
ReadQueries return the latest version of records by default (based on timestamp)
UpdateDoes NOT modify existing data. Instead, appends a NEW record with the same ID but a newer timestamp
DeletePerforms a soft delete by appending a new record with deleted: true
Benefits:
  • Complete audit trail - all historical versions are preserved on-chain
  • Immutable history - past states cannot be altered
  • Blockchain verifiability - every change is recorded on-chain

Next Steps