How It Works
Copy
1. App calls store()
|
2. SDK sends request to broker
|
3. Broker returns 402 Payment Required
|
4. SDK invokes your payment callback
|
5. Your callback executes payment
|
6. Callback returns { txHash, network }
|
7. SDK retries with payment proof
|
8. Broker verifies and stores data
|
9. SDK returns confirmed result
Basic Implementation
Copy
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
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
);
console.log(`Confirmed at block ${result.block_height}`);
X402Quote Structure
The quote object passed to your callback contains:Copy
interface X402Quote {
quoteId: string; // Unique quote identifier
totalCostTia: number; // Total cost in TIA
amountRaw: string; // Amount in smallest units (utia)
brokerAddress: string; // Address to pay
description: string; // Payment description
expiresAt: number; // Unix timestamp for expiry
chainType: 'cosmos' | 'evm' | 'solana';
network: string; // Network identifier
asset: string; // Asset identifier (e.g., "utia")
tokenSymbol: string; // Token symbol
tokenDecimals: number; // Decimal places
paymentMethod: 'native' | 'x402-facilitator';
facilitator?: string; // Facilitator URL if applicable
allOptions: X402PaymentRequirement[];
}
Callback Return Value
Your callback must return:Copy
interface PaymentResult {
txHash: string; // Transaction hash from blockchain
network: string; // Network identifier ('mocha-4' or 'celestia')
}
Wait for Confirmation Options
With Confirmation (Blocking)
Waits for blockchain confirmation before returning:Copy
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);
Without Confirmation (Non-Blocking)
Returns immediately with a task ticket:Copy
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);
Keplr Wallet Example
Copy
import { SigningStargateClient } from '@cosmjs/stargate';
const paymentCallback = async (quote) => {
// Connect to Keplr
await window.keplr.enable('mocha-4');
const signer = await window.keplr.getOfflineSignerOnlyAmino('mocha-4');
const accounts = await signer.getAccounts();
const userAddress = accounts[0].address;
// Create signing client
const client = await SigningStargateClient.connectWithSigner(
'https://rpc-mocha.pops.one',
signer
);
// Send payment
const result = await client.sendTokens(
userAddress,
quote.brokerAddress,
[{ denom: 'utia', amount: quote.amountRaw }],
{ amount: [{ denom: 'utia', amount: '20000' }], gas: '200000' }
);
return {
txHash: result.transactionHash,
network: 'mocha-4'
};
};
// Use the callback
await client.store(
{ collection: 'data', data: [{ content: 'example' }] },
paymentCallback,
true
);
Server-Side Example
Copy
import { DirectSecp256k1HdWallet } from '@cosmjs/proto-signing';
import { SigningStargateClient } from '@cosmjs/stargate';
const paymentCallback = async (quote) => {
const wallet = await DirectSecp256k1HdWallet.fromMnemonic(
process.env.MNEMONIC,
{ prefix: 'celestia' }
);
const [account] = await wallet.getAccounts();
const client = await SigningStargateClient.connectWithSigner(
'https://rpc-mocha.pops.one',
wallet
);
const result = await client.sendTokens(
account.address,
quote.brokerAddress,
[{ denom: 'utia', amount: quote.amountRaw }],
{ amount: [{ denom: 'utia', amount: '20000' }], gas: '200000' }
);
return {
txHash: result.transactionHash,
network: 'mocha-4'
};
};
Error Handling
Copy
import { PaymentVerificationError, OnchainDBError } from '@onchaindb/sdk';
try {
await client.store(
{ collection: 'data', data: [{ content: 'example' }] },
paymentCallback,
true
);
} catch (error) {
if (error instanceof PaymentVerificationError) {
console.log('Payment verification failed');
console.log('Transaction hash:', error.txHash);
console.log('Error:', error.message);
} else if (error instanceof OnchainDBError) {
console.log('OnchainDB error:', error.code, error.statusCode);
}
}