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:
- Attempts the store operation
- If the server returns 402 (payment required), the SDK invokes your callback with the payment quote
- Your callback executes the blockchain payment and returns
{ txHash, network }
- 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:
Method 1: x402 Payment Callback (Recommended)
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:
| Operation | Behavior |
|---|
| Create | Appends a new record to the blockchain with a unique ID and timestamp |
| Read | Queries return the latest version of records by default (based on timestamp) |
| Update | Does NOT modify existing data. Instead, appends a NEW record with the same ID but a newer timestamp |
| Delete | Performs 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
Data Retention
OnchainDB allows you to configure automatic data cleanup policies per collection to manage storage costs.
Retention Options
| Setting | Description | Cost |
|---|
| Permanent | Data stored indefinitely, no automatic cleanup | Standard storage fees |
| 30 days or less | Free tier - no additional retention costs | Free |
| 31+ days | Extended retention with monthly cost per KB | Variable |
How Retention Works
- Default behavior: Collections have permanent storage (no automatic cleanup)
- Setting retention: Configure a retention period in days per collection
- Automatic cleanup: Data older than the retention period is automatically removed
- Cost calculation: Monthly costs are based on data size (KB) and retention period
The first 30 days of retention are free for all collections. Costs only apply when you configure retention periods beyond 30 days.
Configuring Retention
From the Dashboard:
- Navigate to your application
- Select the Retention tab
- Expand a collection to view/edit its settings
- Enter the retention period in days (leave empty for permanent)
- Click Save to apply changes
Retention Configuration
Each collection’s retention config includes:
| Field | Description |
|---|
retention_days | Number of days to keep data (null = permanent) |
monthly_cost_per_kb | Cost per KB per month for extended retention |
status | ’active’ (has retention policy) or ‘permanent’ |
last_cleanup | Timestamp of last automatic cleanup |
Cost Estimation
The Dashboard shows projected costs for each collection:
- Current Size: Amount of data currently stored
- Monthly Cost: Current monthly retention cost in TIA
- Projected Size: Estimated size based on growth trends
- Projected Cost: Estimated next month’s cost
Use shorter retention periods for temporary data (logs, sessions, caches) to reduce storage costs. Keep permanent storage for critical business data.
Use Cases
| Data Type | Recommended Retention |
|---|
| Session data | 7-14 days |
| User activity logs | 30-90 days |
| Analytics events | 30-180 days |
| Transaction records | Permanent |
| User profiles | Permanent |
| Audit logs | Permanent or compliance-required period |
Once data is cleaned up by the retention policy, it cannot be recovered. Ensure you have appropriate backups for critical data before setting retention limits.
Next Steps