Live Streams (SSE)
The Explorer API provides real-time data streams using Server-Sent Events (SSE). These endpoints allow you to subscribe to blockchain events without polling.
Overview
SSE provides a persistent, one-way connection from server to client, perfect for:
- Real-time block notifications
- Live contract activity monitoring
- Transaction stream subscriptions
- Event-driven applications
Available Streams
| Endpoint | Event Type | Description |
|---|---|---|
/live/blocks | block | New blocks |
/live/contract-actions | contractAction | Contract actions (filterable) |
/live/zswap-ledger-events | zswapLedgerEvent | ZSwap ledger events |
/live/dust-ledger-events | dustLedgerEvent | Dust ledger events |
/live/unshielded-transactions | unshieldedTransaction | Unshielded transactions |
/live/shielded-transactions | shieldedTransaction | Shielded transactions |
Block Stream
GET
/live/blocksLIVEStreams new blocks as they are added to the chain.
Event Type: block
- JavaScript
- Python
const eventSource = new EventSource(
'http://explorer.nocy.io:4000/live/blocks'
);
eventSource.addEventListener('block', (event) => {
const block = JSON.parse(event.data);
console.log('New block:', block.height);
console.log('Hash:', block.hash);
console.log('Transactions:', block.transactionCount);
});
eventSource.onerror = (error) => {
console.error('SSE Error:', error);
// Implement reconnection logic
};
// Clean up when done
// eventSource.close();
import sseclient
import requests
import json
def stream_blocks():
url = 'http://explorer.nocy.io:4000/live/blocks'
response = requests.get(url, stream=True)
client = sseclient.SSEClient(response)
for event in client.events():
if event.event == 'block':
block = json.loads(event.data)
print(f"New block: {block['height']}")
print(f"Hash: {block['hash']}")
# Run the stream
stream_blocks()
Event Data:
{
"height": 125001,
"hash": "0x1234abcd...",
"timestamp": "2024-12-13T12:00:10.000Z",
"transactionCount": 5,
"parentHash": "0x0000abcd..."
}
Contract Actions Stream
GET
/live/contract-actionsLIVEStreams contract actions, optionally filtered by contract address.
Event Type: contractAction
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
address | string | No | Contract address to filter (hex format) |
- JavaScript
- Python
// Stream all contract actions
const allActions = new EventSource(
'http://explorer.nocy.io:4000/live/contract-actions'
);
// Stream actions for specific contract
const contractAddress = '0x1234abcd...';
const filteredActions = new EventSource(
`http://explorer.nocy.io:4000/live/contract-actions?address=${contractAddress}`
);
filteredActions.addEventListener('contractAction', (event) => {
const action = JSON.parse(event.data);
console.log('Contract action:', action.action);
console.log('Data:', action.data);
});
import sseclient
import requests
import json
def stream_contract_actions(contract_address=None):
url = 'http://explorer.nocy.io:4000/live/contract-actions'
if contract_address:
url += f'?address={contract_address}'
response = requests.get(url, stream=True)
client = sseclient.SSEClient(response)
for event in client.events():
if event.event == 'contractAction':
action = json.loads(event.data)
print(f"Action: {action['action']}")
stream_contract_actions('0x1234abcd...')
Event Data:
{
"id": "action-456",
"contractAddress": "0x1234abcd...",
"action": "transfer",
"blockHeight": 125001,
"transactionHash": "0xtx789...",
"timestamp": "2024-12-13T12:00:10.000Z",
"data": {
"from": "0xsender...",
"to": "0xreceiver...",
"amount": "1000"
}
}
ZSwap Ledger Events
GET
/live/zswap-ledger-eventsLIVEStreams ZSwap ledger events for swap activity monitoring.
Event Type: zswapLedgerEvent
const eventSource = new EventSource(
'http://explorer.nocy.io:4000/live/zswap-ledger-events'
);
eventSource.addEventListener('zswapLedgerEvent', (event) => {
const zswapEvent = JSON.parse(event.data);
console.log('ZSwap event:', zswapEvent);
});
Dust Ledger Events
GET
/live/dust-ledger-eventsLIVEStreams dust ledger events.
Event Type: dustLedgerEvent
const eventSource = new EventSource(
'http://explorer.nocy.io:4000/live/dust-ledger-events'
);
eventSource.addEventListener('dustLedgerEvent', (event) => {
const dustEvent = JSON.parse(event.data);
console.log('Dust event:', dustEvent);
});
Unshielded Transactions
GET
/live/unshielded-transactionsLIVEStreams unshielded (public) transactions.
Event Type: unshieldedTransaction
const eventSource = new EventSource(
'http://explorer.nocy.io:4000/live/unshielded-transactions'
);
eventSource.addEventListener('unshieldedTransaction', (event) => {
const tx = JSON.parse(event.data);
console.log('Unshielded tx:', tx.hash);
});
Shielded Transactions
GET
/live/shielded-transactionsLIVEStreams shielded (private) transactions.
Event Type: shieldedTransaction
const eventSource = new EventSource(
'http://explorer.nocy.io:4000/live/shielded-transactions'
);
eventSource.addEventListener('shieldedTransaction', (event) => {
const tx = JSON.parse(event.data);
console.log('Shielded tx:', tx);
});
Best Practices
Error Handling & Reconnection
class SSEManager {
constructor(url, eventType, onEvent) {
this.url = url;
this.eventType = eventType;
this.onEvent = onEvent;
this.eventSource = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 5;
this.reconnectDelay = 1000;
}
connect() {
this.eventSource = new EventSource(this.url);
this.eventSource.addEventListener(this.eventType, (event) => {
this.reconnectAttempts = 0; // Reset on successful event
try {
const data = JSON.parse(event.data);
this.onEvent(data);
} catch (error) {
console.error('Failed to parse event:', error);
}
});
this.eventSource.onerror = (error) => {
console.error('SSE error:', error);
this.eventSource.close();
this.attemptReconnect();
};
}
attemptReconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error('Max reconnection attempts reached');
return;
}
this.reconnectAttempts++;
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
console.log(`Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
setTimeout(() => this.connect(), delay);
}
disconnect() {
if (this.eventSource) {
this.eventSource.close();
this.eventSource = null;
}
}
}
// Usage
const blockStream = new SSEManager(
'http://explorer.nocy.io:4000/live/blocks',
'block',
(block) => console.log('New block:', block.height)
);
blockStream.connect();
// Later: blockStream.disconnect();
React Hook Example
import { useEffect, useState, useCallback } from 'react';
function useSSE(url, eventType) {
const [data, setData] = useState(null);
const [error, setError] = useState(null);
const [isConnected, setIsConnected] = useState(false);
useEffect(() => {
const eventSource = new EventSource(url);
eventSource.addEventListener(eventType, (event) => {
try {
setData(JSON.parse(event.data));
setIsConnected(true);
} catch (e) {
setError(e);
}
});
eventSource.onerror = (e) => {
setError(e);
setIsConnected(false);
};
eventSource.onopen = () => {
setIsConnected(true);
setError(null);
};
return () => eventSource.close();
}, [url, eventType]);
return { data, error, isConnected };
}
// Usage in component
function BlockMonitor() {
const { data: block, isConnected } = useSSE(
'http://explorer.nocy.io:4000/live/blocks',
'block'
);
return (
<div>
<span>{isConnected ? '🟢 Connected' : '🔴 Disconnected'}</span>
{block && <p>Latest block: {block.height}</p>}
</div>
);
}
Tips
Best Practices
- Always handle errors - SSE connections can drop; implement reconnection logic
- Close connections - Call
eventSource.close()when components unmount - Parse JSON safely - Wrap
JSON.parse()in try-catch - Use specific event listeners - Use
addEventListener(eventType, ...)instead ofonmessage - Implement backoff - Use exponential backoff for reconnection attempts