Chain Synchronization
Follow the Cardano blockchain in real-time using WebSocket
Chain synchronization is the process of following the Cardano blockchain in real-time. When you connect to a Cardano node via WebSocket, you can receive new blocks the moment they're produced—typically every 20 seconds on mainnet.
Think of it like a live feed of the blockchain. Instead of repeatedly polling "what's new?", the node pushes blocks to you as they arrive. This makes chain sync ideal for applications that need instant awareness of on-chain activity: wallets detecting payments, exchanges crediting deposits, or analytics tracking network activity.
The protocol is straightforward: you tell the node where you want to start (a specific block or the beginning of time), and it streams blocks forward from that point. If the chain reorganizes due to competing blocks, the node tells you to "roll back" and replay from the correct fork.
What can you build with Chain Sync?
| Use Case | Description |
|---|---|
| Payment Monitoring | Wallets and exchanges detect incoming transactions instantly. When ADA arrives at a watched address, notify users or credit accounts. |
| Blockchain Explorers | Index every block and transaction to make the chain searchable. Services like CardanoScan sync the entire history. |
| DeFi Protocols | DEXes track liquidity pool states, lending protocols monitor collateral positions, all in real-time. |
| NFT Marketplaces | Detect token mints, transfers, and metadata updates the moment they're confirmed on-chain. |
| Analytics Dashboards | Build real-time network statistics: TPS, active addresses, stake distribution changes. |
| Alerting Systems | Trigger webhooks or notifications when specific patterns occur (whale movements, smart contract calls). |
Choose Your Network
| Network | WebSocket Endpoint |
|---|---|
| Mainnet | wss://api.nacho.builders/v1/ogmios |
| Preprod | wss://api.nacho.builders/v1/preprod/ogmios |
Use Preprod for development and testing. Your API key works on both networks.
Chain sync is essential for:
- Monitoring addresses for incoming transactions
- Building blockchain explorers
- Tracking token movements
- Real-time notifications
Chain synchronization requires a WebSocket connection. HTTP requests are not supported for this feature.
Try it live! Connect to the Cardano blockchain and watch the chain sync protocol in action. You'll see the full flow: querying the tip, finding an intersection, and streaming blocks.
How It Works
1. Connect via WebSocket
2. Find an intersection point
3. Request blocks with nextBlock
4. Process each block (forward or rollback)
5. Repeat step 3Connect and Sync
// Load API key from environment variable
const API_KEY = process.env.NACHO_API_KEY;
// Connect to the Ogmios WebSocket endpoint with API key as query param
const ws = new WebSocket(`wss://api.nacho.builders/v1/ogmios?apikey=${API_KEY}`);
ws.onopen = () => {
console.log('Connected to Ogmios');
// STEP 1: Find an intersection point to start syncing from
// Using 'origin' syncs from genesis (use specific slot/hash for resuming)
ws.send(JSON.stringify({
jsonrpc: '2.0',
method: 'findIntersection',
params: {
points: ['origin'] // Can also be [{ slot: 12345, id: 'blockhash...' }]
},
id: 'find-intersection'
}));
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
// Handle the intersection response (tells us where we're starting)
if (data.id === 'find-intersection') {
console.log('Intersection found:', data.result.intersection);
console.log('Tip:', data.result.tip); // Current chain tip info
// STEP 2: Start the block stream
requestNextBlock();
} else {
// STEP 3: Process each block as it arrives
handleBlock(data.result);
}
};
function requestNextBlock() {
// Request the next block in sequence (this drives the sync loop)
ws.send(JSON.stringify({
jsonrpc: '2.0',
method: 'nextBlock',
id: 'next'
}));
}
function handleBlock(result) {
if (result.direction === 'forward') {
// New block received - process it
const block = result.block;
console.log(`Block ${block.height}: ${block.transactions?.length || 0} txs`);
// Iterate through all transactions in this block
block.transactions?.forEach(tx => {
processTransaction(tx);
});
} else if (result.direction === 'backward') {
// Chain rollback detected - undo state back to this point
// Rollbacks happen when competing blocks are resolved
console.log('Rollback to:', result.point);
}
// Continue the sync loop by requesting the next block
requestNextBlock();
}
function processTransaction(tx) {
console.log(' Tx:', tx.id);
// === YOUR TRANSACTION PROCESSING LOGIC HERE ===
// Check tx.outputs for payments to your addresses
// Check tx.inputs to detect spent UTxOs
}
ws.onerror = (error) => console.error('WebSocket error:', error);
ws.onclose = () => console.log('Connection closed');Finding an Intersection Point
The findIntersection method finds a common point between your view and the node's chain:
// Start from genesis (sync entire chain)
{ "points": ["origin"] }
// Start from a specific block
{
"points": [
{ "slot": 12345678, "id": "abc123..." }
]
}
// Try multiple points (node picks best match)
{
"points": [
{ "slot": 12345678, "id": "abc123..." },
{ "slot": 12345600, "id": "def456..." },
"origin"
]
}For real-time monitoring, first query queryNetwork/tip to get the current tip, then use that as your intersection point.
Handling Rollbacks
Cardano uses a longest-chain rule. Occasionally, blocks are rolled back:
function handleBlock(result) {
if (result.direction === 'forward') {
// New block - add to your state
applyBlock(result.block);
} else if (result.direction === 'backward') {
// Rollback - undo blocks back to this point
rollbackTo(result.point);
}
}
function rollbackTo(point) {
// Remove any blocks after this point
// Re-process transactions that were in rolled-back blocks
console.log(`Rolling back to slot ${point.slot}`);
}Rollbacks are typically 1-2 blocks but can be up to ~2160 blocks (security parameter k).
Monitoring Specific Addresses
Filter chain data for addresses you care about:
const watchedAddresses = new Set([
'addr1...',
'addr1...'
]);
function processTransaction(tx) {
// Check outputs
tx.outputs?.forEach((output, index) => {
if (watchedAddresses.has(output.address)) {
console.log(`Incoming transaction to ${output.address}`);
console.log(` TxHash: ${tx.id}`);
console.log(` Amount: ${output.value.ada.lovelace} lovelace`);
// Emit notification, update database, etc.
notifyIncoming(tx.id, output);
}
});
// Check inputs (for outgoing)
tx.inputs?.forEach(input => {
// You'd need to look up the input's address
// from your local UTxO tracking
});
}Pipelining Requests
For faster sync, pipeline multiple nextBlock requests:
// Number of concurrent requests to keep in flight
// Higher = faster sync, but uses more memory
const PIPELINE_DEPTH = 100;
let pending = 0; // Track how many requests are awaiting responses
function requestBlocks(count) {
// Send multiple nextBlock requests without waiting for responses
for (let i = 0; i < count; i++) {
ws.send(JSON.stringify({
jsonrpc: '2.0',
method: 'nextBlock',
id: `block-${Date.now()}-${i}` // Unique ID for each request
}));
pending++;
}
}
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
// Check if this is a block response (not intersection)
if (data.result?.block || data.result?.direction) {
handleBlock(data.result);
pending--; // One response received
// Refill the pipeline when it drops below half capacity
// This keeps blocks streaming in continuously
if (pending < PIPELINE_DEPTH / 2) {
requestBlocks(PIPELINE_DEPTH - pending);
}
}
};
// Initial setup
ws.onopen = () => {
findIntersection();
// After intersection found, fill the pipeline to start fast sync
};Reconnection Strategy
WebSocket connections can drop. Implement reconnection:
/**
* Chain sync client with automatic reconnection.
* Tracks last processed block to resume from the correct point.
*/
class ResilientChainSync {
constructor(apiKey, onBlock) {
this.apiKey = apiKey;
this.onBlock = onBlock;
// Store the last processed block point for resuming after reconnect
this.lastPoint = null;
this.connect();
}
connect() {
this.ws = new WebSocket(
`wss://api.nacho.builders/v1/ogmios?apikey=${this.apiKey}`
);
this.ws.onopen = () => {
// On reconnect, try to resume from last known point
// Falls back to 'origin' if no previous state
const points = this.lastPoint
? [this.lastPoint, 'origin'] // Try last point first, then origin
: ['origin']; // First connection - start from genesis
this.ws.send(JSON.stringify({
jsonrpc: '2.0',
method: 'findIntersection',
params: { points }
}));
};
this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.result?.block) {
// Save this block as our resume point for reconnection
this.lastPoint = {
slot: data.result.block.slot,
id: data.result.block.id
};
// Pass block to handler
this.onBlock(data.result.block);
}
// Continue the sync loop
this.requestNext();
};
this.ws.onclose = () => {
// Connection lost - schedule reconnection
// Production apps should use exponential backoff
console.log('Connection closed, reconnecting in 5s...');
setTimeout(() => this.connect(), 5000);
};
this.ws.onerror = (error) => {
console.error('WebSocket error:', error);
// Error will trigger onclose, which handles reconnection
};
}
requestNext() {
// Only send if connection is open (prevents errors during reconnect)
if (this.ws.readyState === WebSocket.OPEN) {
this.ws.send(JSON.stringify({
jsonrpc: '2.0',
method: 'nextBlock'
}));
}
}
}Performance Considerations
| Scenario | Recommendation |
|---|---|
| Full history sync | Use pipelining, store checkpoints |
| Real-time monitoring | Start from tip, single requests |
| Multiple addresses | Single connection, filter in code |
| High availability | Multiple connections with failover |
Chain sync maintains an open WebSocket. FREE tier allows 5 concurrent connections; PAID allows 50.
Billing
Chain sync messages are billed per message in both directions:
| Message Type | Direction | Credits (PAID) |
|---|---|---|
nextBlock request | Sent | 1 |
| Block response | Received | 1 |
findIntersection | Sent | 1 |
| Intersection result | Received | 1 |
Example costs:
- Following the tip (real-time): ~6 messages/minute = ~360 credits/hour
- Syncing 1,000 historical blocks: ~2,000 credits (1,000 requests + 1,000 responses)
FREE tier users are not charged credits but are subject to daily limits (100,000 messages/day) and rate limits (100 msg/sec).
Was this page helpful?