| Crates.io | chia-block-listener |
| lib.rs | chia-block-listener |
| version | 0.2.0 |
| created_at | 2026-01-20 18:31:11.03053+00 |
| updated_at | 2026-01-20 18:31:11.03053+00 |
| description | Event driven listener and messaging interface for Chia blockchain peers |
| homepage | |
| repository | |
| max_upload_size | |
| id | 2057171 |
| size | 575,174 |
A high-performance Chia blockchain listener for Node.js, built with Rust and NAPI bindings. This library provides real-time monitoring of the Chia blockchain with efficient peer connections and block parsing capabilities.
npm install @dignetwork/chia-block-listener
const { ChiaBlockListener, initTracing } = require('@dignetwork/chia-block-listener')
// Initialize tracing for debugging (optional)
initTracing()
// Create a new listener instance
const listener = new ChiaBlockListener()
// Listen for block events
listener.on('blockReceived', (block) => {
console.log(`New block received: ${block.height}`)
console.log(`Header hash: ${block.headerHash}`)
console.log(`Timestamp: ${new Date(block.timestamp * 1000)}`)
console.log(`Coin additions: ${block.coinAdditions.length}`)
console.log(`Coin removals: ${block.coinRemovals.length}`)
console.log(`Coin spends: ${block.coinSpends.length}`)
})
// Listen for peer connection events
listener.on('peerConnected', (peer) => {
console.log(`Connected to peer: ${peer.peerId} (${peer.host}:${peer.port})`)
})
listener.on('peerDisconnected', (peer) => {
console.log(`Disconnected from peer: ${peer.peerId}`)
if (peer.message) {
console.log(`Reason: ${peer.message}`)
}
})
// Connect to a Chia full node
const peerId = listener.addPeer('localhost', 8444, 'mainnet')
console.log(`Added peer: ${peerId}`)
// Keep the process running
process.on('SIGINT', () => {
console.log('Shutting down...')
listener.disconnectAllPeers()
process.exit(0)
})
This repository now provides a pure Rust API suitable for use in Tokio-based applications, while the N-API adapter remains a thin layer for Node.js. The Rust-facing entry point is BlockListener (previously named Listener).
Key points of the Rust API:
Example:
use chia_block_listener::{init_tracing, BlockListener, ListenerConfig};
use chia_block_listener::types::Event;
use tokio_stream::wrappers::BroadcastStream;
use tokio_stream::StreamExt;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Optional: enable logging
init_tracing();
// Configure and create the block listener (buffer defaults to 1024)
let block_listener = BlockListener::new(ListenerConfig::default())?;
// Subscribe to events (bounded, best-effort delivery)
let rx = block_listener.subscribe();
let mut events = BroadcastStream::new(rx);
// Connect a peer
let _peer_id = block_listener.add_peer("localhost".into(), 8444, "mainnet".into()).await?;
// Application-owned policy loop: read events and act
let policy = tokio::spawn(async move {
while let Some(item) = events.next().await {
match item {
Ok(Event::PeerConnected(e)) => {
println!("connected: {} ({}:{})", e.peer_id, e.host, e.port);
}
Ok(Event::PeerDisconnected(e)) => {
println!("disconnected: {} reason={:?}", e.peer_id, e.message);
}
Ok(Event::NewPeakHeight(e)) => {
println!("new peak: old={:?} new={} from {}", e.old_peak, e.new_peak, e.peer_id);
}
Ok(Event::BlockReceived(b)) => {
println!("block {} {}", b.height, b.header_hash);
}
Err(_lagged) => {
// One or more messages were missed (slow consumer). Best-effort model: recompute current state if needed.
// For catch-up use cases, you generally only need latest peak or most recent block.
}
}
}
});
// Shutdown example (e.g., on SIGINT/SIGTERM):
// Signal fast
block_listener.shutdown().await?;
// Wait for all internal tasks to end deterministically
block_listener.shutdown_and_wait().await?;
policy.await?;
Ok(())
}
Lagged(n) from the stream wrapper, meaning it missed n events. This affects slow subscribers only and does not cause block events to be dropped before entering the broadcast.get_block_by_height/ranges in addition to the live stream.add_peer establishes a dedicated streaming WebSocket reader (per peer) that passively consumes protocol messages and drives events. NewPeakWallet triggers a RequestBlock immediately; RespondBlock is parsed and emitted as Event::BlockReceived via the core event pipeline.PeerConnected, PeerDisconnected, NewPeakHeight, BlockReceived) flow through a single core mpsc sink and are forwarded to all subscribers via broadcast. Blocks use send().await into the sink (no pre-broadcast drop); informational events are best-effort try_send to avoid stalling I/O.add_peer multiple times. The pool tracks each peer independently with its own streaming reader and request worker. On-demand requests (get_block_by_height) use round-robin with cooldown and remove unhealthy peers based on repeated failures/timeouts/protocol errors.Lagged(n) from the broadcast wrapper; recompute state as needed. For guaranteed historical coverage, pair live listening with explicit get_block_by_height / range queries.shutdown() signals cancellation; shutdown_and_wait() awaits the dispatcher and all tracked peer/request tasks for deterministic teardown. Drop on Listener signals cancel without awaiting (non-blocking drop safety).BlockListenerConfig { auto_reconnect: true, network_id, default_port, max_auto_reconnect_retries, .. } when constructing BlockListener.subscribe() call. It keeps one active peer for you; manual add_peer calls still work and can coexist.network_id (default mainnet) and default_port (default 8444).max_auto_reconnect_retries (default 10 batches). Failure after all retries surfaces a best-effort PeerDisconnected event with an explanatory message.use chia_block_listener::{BlockListener, BlockListenerConfig};
let config = BlockListenerConfig {
auto_reconnect: true,
network_id: "mainnet".into(),
default_port: 8444,
max_auto_reconnect_retries: 10,
buffer: 1024,
};
let listener = BlockListener::new(config)?;
let mut rx = listener.subscribe(); // kicks off auto-reconnect
shutdown() signals cancellation and returns quickly.shutdown_and_wait() cancels and then awaits all internal tasks to finish (peer workers, request processor, dispatchers), providing deterministic shutdown.BlockListenerConfig exposes:
buffer (event buffer per subscriber, default 1024)auto_reconnect (enable DNS-based single-peer maintenance)network_id (e.g., mainnet, testnet11; used for DNS discovery)default_port (port used for discovered peers, default 8444)max_auto_reconnect_retries (discovery batches to attempt before surfacing an error event, default 10)const listener = new ChiaBlockListener()
Creates a new Chia block listener instance.
addPeer(host, port, networkId): stringConnects to a Chia full node and starts listening for blocks.
Parameters:
host (string): The hostname or IP address of the Chia nodeport (number): The port number (typically 8444 for mainnet)networkId (string): The network identifier ('mainnet', 'testnet', etc.)Returns: A unique peer ID string for this connection
disconnectPeer(peerId): booleanDisconnects from a specific peer.
Parameters:
peerId (string): The peer ID returned by addPeer()Returns: true if the peer was successfully disconnected, false otherwise
disconnectAllPeers(): voidDisconnects from all connected peers.
getConnectedPeers(): string[]Returns an array of currently connected peer IDs.
getBlockByHeight(peerId, height): BlockReceivedEventRetrieves a specific block by its height from a connected peer.
Parameters:
peerId (string): The peer ID to queryheight (number): The block height to retrieveReturns: A BlockReceivedEvent object containing the block data
getBlocksRange(peerId, startHeight, endHeight): BlockReceivedEvent[]Retrieves a range of blocks from a connected peer.
Parameters:
peerId (string): The peer ID to querystartHeight (number): The starting block height (inclusive)endHeight (number): The ending block height (inclusive)Returns: An array of BlockReceivedEvent objects
The ChiaPeerPool provides a managed pool of peer connections for retrieving historical blocks with automatic load balancing and intelligent failover across multiple peers. When a peer fails to provide a block or experiences protocol errors, the pool automatically tries alternative peers and removes problematic peers from the pool.
const pool = new ChiaPeerPool()
Creates a new peer pool instance with built-in rate limiting (500ms per peer).
addPeer(host, port, networkId): Promise<string>Adds a peer to the connection pool.
Parameters:
host (string): The hostname or IP address of the Chia nodeport (number): The port number (typically 8444 for mainnet)networkId (string): The network identifier ('mainnet', 'testnet', etc.)Returns: A Promise that resolves to a unique peer ID string
getBlockByHeight(height): Promise<BlockReceivedEvent>Retrieves a specific block by height using automatic peer selection and load balancing.
Parameters:
height (number): The block height to retrieveReturns: A Promise that resolves to a BlockReceivedEvent object
removePeer(peerId): Promise<boolean>Removes a peer from the pool.
Parameters:
peerId (string): The peer ID to removeReturns: A Promise that resolves to true if the peer was removed, false otherwise
shutdown(): Promise<void>Shuts down the pool and disconnects all peers.
getConnectedPeers(): Promise<string[]>Gets the list of currently connected peer IDs.
Returns: Array of peer ID strings (format: "host:port")
getPeakHeight(): Promise<number | null>Gets the highest blockchain peak height seen across all connected peers.
Returns: The highest peak height as a number, or null if no peaks have been received yet
on(event, callback): voidRegisters an event handler for pool events.
Parameters:
event (string): The event name ('peerConnected' or 'peerDisconnected')callback (function): The event handler functionoff(event, callback): voidRemoves an event handler.
Parameters:
event (string): The event name to stop listening forThe ChiaBlockListener emits the following events:
blockReceivedFired when a new block is received from any connected peer.
Callback: (event: BlockReceivedEvent) => void
peerConnectedFired when a connection to a peer is established.
Callback: (event: PeerConnectedEvent) => void
peerDisconnectedFired when a peer connection is lost.
Callback: (event: PeerDisconnectedEvent) => void
The ChiaPeerPool emits the following events:
peerConnectedFired when a peer is successfully added to the pool.
Callback: (event: PeerConnectedEvent) => void
peerDisconnectedFired when a peer is removed from the pool or disconnects.
Callback: (event: PeerDisconnectedEvent) => void
newPeakHeightFired when a new highest blockchain peak is discovered.
Callback: (event: NewPeakHeightEvent) => void
The DnsDiscoveryClient provides automatic peer discovery using Chia network DNS introducers with full IPv4 and IPv6 support.
const client = new DnsDiscoveryClient()
Creates a new DNS discovery client instance.
discoverMainnetPeers(): Promise<DiscoveryResultJS>Discovers peers for Chia mainnet using built-in DNS introducers.
Returns: Promise resolving to discovery results with separate IPv4 and IPv6 peer lists
discoverTestnet11Peers(): Promise<DiscoveryResultJS>Discovers peers for Chia testnet11 using built-in DNS introducers.
Returns: Promise resolving to discovery results
discoverPeers(introducers, port): Promise<DiscoveryResultJS>Discovers peers using custom DNS introducers.
Parameters:
introducers (string[]): Array of DNS introducer hostnamesport (number): Default port for discovered peersReturns: Promise resolving to discovery results
resolveIpv4(hostname): Promise<AddressResult>Resolves IPv4 addresses (A records) for a hostname.
Parameters:
hostname (string): Hostname to resolveReturns: Promise resolving to IPv4 addresses
resolveIpv6(hostname): Promise<AddressResult>Resolves IPv6 addresses (AAAA records) for a hostname.
Parameters:
hostname (string): Hostname to resolveReturns: Promise resolving to IPv6 addresses
resolveBoth(hostname, port): Promise<DiscoveryResultJS>Resolves both IPv4 and IPv6 addresses for a hostname.
Parameters:
hostname (string): Hostname to resolveport (number): Port for the peer addressesReturns: Promise resolving to discovery results
BlockReceivedEventinterface BlockReceivedEvent {
peerId: string // IP address of the peer that sent this block
height: number // Block height
weight: string // Block weight as string
headerHash: string // Block header hash (hex)
timestamp: number // Block timestamp (Unix time)
coinAdditions: CoinRecord[] // New coins created in this block
coinRemovals: CoinRecord[] // Coins spent in this block
coinSpends: CoinSpend[] // Detailed spend information
coinCreations: CoinRecord[] // Coins created by puzzles
hasTransactionsGenerator: boolean // Whether block has a generator
generatorSize: number // Size of the generator bytecode
}
PeerConnectedEventinterface PeerConnectedEvent {
peerId: string // Peer IP address
host: string // Peer hostname/IP
port: number // Peer port number
}
PeerDisconnectedEventinterface PeerDisconnectedEvent {
peerId: string // Peer IP address
host: string // Peer hostname/IP
port: number // Peer port number
message?: string // Optional disconnection reason
}
NewPeakHeightEventinterface NewPeakHeightEvent {
oldPeak: number | null // Previous highest peak (null if first peak)
newPeak: number // New highest peak height
peerId: string // Peer that discovered this peak
}
CoinRecordinterface CoinRecord {
parentCoinInfo: string // Parent coin ID (hex)
puzzleHash: string // Puzzle hash (hex)
amount: string // Coin amount as string
}
CoinSpendinterface CoinSpend {
coin: CoinRecord // The coin being spent
puzzleReveal: string // CLVM puzzle bytecode (hex)
solution: string // CLVM solution bytecode (hex)
offset: number // Offset in the generator bytecode
}
DiscoveryResultJSinterface DiscoveryResultJS {
ipv4Peers: PeerAddressJS[] // IPv4 peer addresses
ipv6Peers: PeerAddressJS[] // IPv6 peer addresses
totalCount: number // Total peers found
}
PeerAddressJSinterface PeerAddressJS {
host: string // IP address as string
port: number // Port number
isIpv6: boolean // Protocol indicator
displayAddress: string // Formatted for display/URLs
}
AddressResultinterface AddressResult {
addresses: string[] // List of IP addresses
count: number // Number of addresses
}
The ChiaPeerPool is designed for efficiently retrieving historical blocks with automatic load balancing and intelligent failover across multiple peers. When a peer fails to provide a block or experiences protocol errors, the pool automatically tries alternative peers and removes problematic peers from the pool.
const { ChiaPeerPool, initTracing } = require('@dignetwork/chia-block-listener')
async function main() {
// Initialize tracing
initTracing()
// Create a peer pool
const pool = new ChiaPeerPool()
// Listen for pool events
pool.on('peerConnected', (event) => {
console.log(`Peer connected to pool: ${event.peerId}`)
})
pool.on('peerDisconnected', (event) => {
console.log(`Peer disconnected from pool: ${event.peerId}`)
})
pool.on('newPeakHeight', (event) => {
console.log(`New blockchain peak detected!`)
console.log(` Previous: ${event.oldPeak || 'None'}`)
console.log(` New: ${event.newPeak}`)
console.log(` Discovered by: ${event.peerId}`)
})
// Add multiple peers
await pool.addPeer('node1.chia.net', 8444, 'mainnet')
await pool.addPeer('node2.chia.net', 8444, 'mainnet')
await pool.addPeer('node3.chia.net', 8444, 'mainnet')
// Fetch blocks with automatic load balancing
const block1 = await pool.getBlockByHeight(5000000)
const block2 = await pool.getBlockByHeight(5000001)
const block3 = await pool.getBlockByHeight(5000002)
console.log(`Block ${block1.height}: ${block1.coinSpends.length} spends`)
console.log(`Block ${block2.height}: ${block2.coinSpends.length} spends`)
console.log(`Block ${block3.height}: ${block3.coinSpends.length} spends`)
// Shutdown the pool
await pool.shutdown()
}
main().catch(console.error)
The pool automatically enforces a 500ms rate limit per peer for maximum performance while preventing node overload:
// Rapid requests are automatically queued and distributed
const promises = []
for (let i = 5000000; i < 5000100; i++) {
promises.push(pool.getBlockByHeight(i))
}
// All requests will be processed efficiently across all peers
// with automatic load balancing and rate limiting
const blocks = await Promise.all(promises)
console.log(`Retrieved ${blocks.length} blocks`)
The pool provides robust error handling with automatic failover:
// The pool automatically handles various error scenarios:
// 1. Connection failures - automatically tries other peers
try {
const block = await pool.getBlockByHeight(5000000)
console.log(`Retrieved block ${block.height}`)
} catch (error) {
// If all peers fail, you'll get an error after all retry attempts
console.error('All peers failed:', error.message)
}
// 2. Protocol errors - peers that refuse blocks are automatically disconnected
pool.on('peerDisconnected', (event) => {
console.log(`Peer ${event.peerId} disconnected: ${event.reason}`)
// Reasons include: "Block request rejected", "Protocol error", "Connection timeout"
})
// 3. Automatic peer cleanup - problematic peers are removed from the pool
console.log('Active peers before:', await pool.getConnectedPeers())
await pool.getBlockByHeight(5000000) // May trigger peer removal
console.log('Active peers after:', await pool.getConnectedPeers())
// 4. Multiple retry attempts - tries up to 3 different peers per request
// This happens automatically and transparently
const block = await pool.getBlockByHeight(5000000) // Will try multiple peers if needed
Error Types Handled Automatically:
// Monitor pool health
const peers = await pool.getConnectedPeers()
console.log(`Active peers in pool: ${peers.length}`)
// Remove underperforming peers
if (slowPeer) {
await pool.removePeer(slowPeer)
console.log('Removed slow peer from pool')
}
// Add new peers dynamically
if (peers.length < 3) {
await pool.addPeer('backup-node.chia.net', 8444, 'mainnet')
}
try {
const block = await pool.getBlockByHeight(5000000)
console.log(`Retrieved block ${block.height}`)
} catch (error) {
console.error('Failed to retrieve block:', error)
// The pool will automatically try other peers
// You can also add more peers if needed
const peers = await pool.getConnectedPeers()
if (peers.length === 0) {
console.log('No peers available, adding new ones...')
await pool.addPeer('node1.chia.net', 8444, 'mainnet')
}
}
// Monitor blockchain sync progress
const pool = new ChiaPeerPool()
// Track peak changes
let currentPeak = null
pool.on('newPeakHeight', (event) => {
currentPeak = event.newPeak
const progress = event.oldPeak
? `+${event.newPeak - event.oldPeak} blocks`
: 'Initial peak'
console.log(`Peak update: ${event.newPeak} (${progress})`)
})
// Add peers
await pool.addPeer('node1.chia.net', 8444, 'mainnet')
await pool.addPeer('node2.chia.net', 8444, 'mainnet')
// Check current peak
const peak = await pool.getPeakHeight()
console.log(`Current highest peak: ${peak || 'None yet'}`)
// Fetch some blocks to trigger peak updates
await pool.getBlockByHeight(5000000)
await pool.getBlockByHeight(5100000)
await pool.getBlockByHeight(5200000)
// Monitor sync status
setInterval(async () => {
const peak = await pool.getPeakHeight()
if (peak) {
const estimatedCurrent = 5200000 + Math.floor((Date.now() / 1000 - 1700000000) / 18.75)
const syncPercentage = (peak / estimatedCurrent * 100).toFixed(2)
console.log(`Sync status: ${syncPercentage}% (peak: ${peak})`)
}
}, 60000) // Check every minute
Use ChiaPeerPool when:
Use ChiaBlockListener when:
Both classes can be used together in the same application for different purposes.
The DnsDiscoveryClient enables automatic discovery of Chia network peers using DNS introducers, with full support for both IPv4 and IPv6 addresses.
const { DnsDiscoveryClient, initTracing } = require('@dignetwork/chia-block-listener')
async function discoverPeers() {
// Initialize tracing
initTracing()
// Create DNS discovery client
const client = new DnsDiscoveryClient()
// Discover mainnet peers
const result = await client.discoverMainnetPeers()
console.log(`Found ${result.totalCount} total peers:`)
console.log(` IPv4 peers: ${result.ipv4Peers.length}`)
console.log(` IPv6 peers: ${result.ipv6Peers.length}`)
// Use with peer connections
for (const peer of result.ipv4Peers.slice(0, 3)) {
console.log(`IPv4 peer: ${peer.displayAddress}`)
// peer.host and peer.port can be used with addPeer()
}
for (const peer of result.ipv6Peers.slice(0, 3)) {
console.log(`IPv6 peer: ${peer.displayAddress}`) // [2001:db8::1]:8444
// IPv6 addresses are properly formatted with brackets
}
}
discoverPeers().catch(console.error)
const { ChiaPeerPool, DnsDiscoveryClient } = require('@dignetwork/chia-block-listener')
async function setupPoolWithDnsDiscovery() {
const pool = new ChiaPeerPool()
const discovery = new DnsDiscoveryClient()
// Discover peers automatically
const peers = await discovery.discoverMainnetPeers()
// Add discovered peers to pool (both IPv4 and IPv6)
const allPeers = [...peers.ipv4Peers, ...peers.ipv6Peers]
for (const peer of allPeers.slice(0, 5)) {
await pool.addPeer(peer.host, peer.port, 'mainnet')
console.log(`Added peer: ${peer.displayAddress}`)
}
// Now use the pool for block retrieval
const block = await pool.getBlockByHeight(5000000)
console.log(`Retrieved block ${block.height}`)
await pool.shutdown()
}
setupPoolWithDnsDiscovery().catch(console.error)
const client = new DnsDiscoveryClient()
// Use custom introducers
const customIntroducers = [
'seeder.dexie.space',
'chia.hoffmang.com'
]
const result = await client.discoverPeers(customIntroducers, 8444)
console.log(`Found ${result.totalCount} peers from custom introducers`)
const client = new DnsDiscoveryClient()
const hostname = 'dns-introducer.chia.net'
// Resolve specific protocols
try {
const ipv4 = await client.resolveIpv4(hostname)
console.log(`IPv4 addresses: ${ipv4.addresses.join(', ')}`)
} catch (error) {
console.log(`IPv4 resolution failed: ${error.message}`)
}
try {
const ipv6 = await client.resolveIpv6(hostname)
console.log(`IPv6 addresses: ${ipv6.addresses.join(', ')}`)
} catch (error) {
console.log(`IPv6 resolution failed: ${error.message}`)
}
// Or resolve both at once
const both = await client.resolveBoth(hostname, 8444)
console.log(`Combined: ${both.totalCount} addresses`)
const client = new DnsDiscoveryClient()
try {
const result = await client.discoverMainnetPeers()
console.log(`Discovery successful: ${result.totalCount} peers`)
} catch (error) {
console.error('Discovery failed:', error.message)
// Handle different error types
if (error.message.includes('NoPeersFound')) {
console.log('No peers found from any introducer')
} else if (error.message.includes('ResolutionFailed')) {
console.log('DNS resolution failed')
}
}
The ChiaBlockParser provides direct access to the Rust-based block parsing engine, enabling efficient parsing of Chia FullBlock data with complete control over the parsing process. This is ideal for applications that need to process block data from external sources or implement custom block analysis.
const { ChiaBlockParser, initTracing } = require('@dignetwork/chia-block-listener')
async function parseBlockData() {
// Initialize tracing
initTracing()
// Create a block parser instance
const parser = new ChiaBlockParser()
// Parse a block from hex string
const blockHex = "your_full_block_hex_data_here"
const parsedBlock = parser.parseFullBlockFromHex(blockHex)
console.log(`Parsed block ${parsedBlock.height}:`)
console.log(` Header hash: ${parsedBlock.headerHash}`)
console.log(` Weight: ${parsedBlock.weight}`)
console.log(` Timestamp: ${new Date(parsedBlock.timestamp * 1000)}`)
console.log(` Coin additions: ${parsedBlock.coinAdditions.length}`)
console.log(` Coin removals: ${parsedBlock.coinRemovals.length}`)
console.log(` Coin spends: ${parsedBlock.coinSpends.length}`)
console.log(` Has generator: ${parsedBlock.hasTransactionsGenerator}`)
// Access detailed coin spend information
parsedBlock.coinSpends.forEach((spend, index) => {
console.log(` Spend ${index + 1}:`)
console.log(` Coin: ${spend.coin.amount} mojos`)
console.log(` Puzzle hash: ${spend.coin.puzzleHash}`)
console.log(` Puzzle reveal: ${spend.puzzleReveal.substring(0, 100)}...`)
console.log(` Solution: ${spend.solution.substring(0, 100)}...`)
console.log(` Created coins: ${spend.createdCoins.length}`)
})
}
parseBlockData().catch(console.error)
const parser = new ChiaBlockParser()
Creates a new block parser instance with access to the full Rust parsing engine.
parseFullBlockFromBytes(blockBytes): ParsedBlockJsParses a FullBlock from raw bytes.
Parameters:
blockBytes (Buffer): The serialized FullBlock dataReturns: A ParsedBlockJs object containing all parsed block information
const fs = require('fs')
const blockData = fs.readFileSync('block.bin')
const parsedBlock = parser.parseFullBlockFromBytes(blockData)
parseFullBlockFromHex(blockHex): ParsedBlockJsParses a FullBlock from a hex-encoded string.
Parameters:
blockHex (string): The hex-encoded FullBlock dataReturns: A ParsedBlockJs object containing all parsed block information
const blockHex = "deadbeef..." // Your hex-encoded block data
const parsedBlock = parser.parseFullBlockFromHex(blockHex)
extractGeneratorFromBlockBytes(blockBytes): string | nullExtracts only the transactions generator from a block without full parsing.
Parameters:
blockBytes (Buffer): The serialized FullBlock dataReturns: Hex-encoded generator bytecode or null if no generator exists
const blockData = fs.readFileSync('block.bin')
const generator = parser.extractGeneratorFromBlockBytes(blockData)
if (generator) {
console.log(`Generator size: ${generator.length / 2} bytes`)
}
getHeightAndTxStatusFromBlockBytes(blockBytes): BlockHeightInfoJsQuickly extracts basic block information without full parsing.
Parameters:
blockBytes (Buffer): The serialized FullBlock dataReturns: A BlockHeightInfoJs object with height and transaction status
const blockData = fs.readFileSync('block.bin')
const info = parser.getHeightAndTxStatusFromBlockBytes(blockData)
console.log(`Block ${info.height}, has transactions: ${info.isTransactionBlock}`)
parseBlockInfoFromBytes(blockBytes): GeneratorBlockInfoJsExtracts generator-related block metadata.
Parameters:
blockBytes (Buffer): The serialized FullBlock dataReturns: A GeneratorBlockInfoJs object with generator metadata
const blockData = fs.readFileSync('block.bin')
const blockInfo = parser.parseBlockInfoFromBytes(blockData)
console.log(`Previous hash: ${blockInfo.prevHeaderHash}`)
console.log(`Generator refs: ${blockInfo.transactionsGeneratorRefList.length}`)
const parser = new ChiaBlockParser()
const fs = require('fs')
const path = require('path')
// Process multiple block files
const blockFiles = fs.readdirSync('./blocks/').filter(f => f.endsWith('.bin'))
const stats = {
totalBlocks: 0,
totalSpends: 0,
totalCoins: 0,
generatorBlocks: 0
}
for (const filename of blockFiles) {
const blockData = fs.readFileSync(path.join('./blocks/', filename))
try {
const parsed = parser.parseFullBlockFromBytes(blockData)
stats.totalBlocks++
stats.totalSpends += parsed.coinSpends.length
stats.totalCoins += parsed.coinAdditions.length
if (parsed.hasTransactionsGenerator) {
stats.generatorBlocks++
}
console.log(`Processed block ${parsed.height} with ${parsed.coinSpends.length} spends`)
} catch (error) {
console.error(`Failed to parse ${filename}:`, error.message)
}
}
console.log('\nBatch processing complete:')
console.log(` Blocks processed: ${stats.totalBlocks}`)
console.log(` Total spends: ${stats.totalSpends}`)
console.log(` Total coins: ${stats.totalCoins}`)
console.log(` Generator blocks: ${stats.generatorBlocks}`)
const parser = new ChiaBlockParser()
function analyzeBlockGenerator(blockHex) {
// Parse the full block
const parsed = parser.parseFullBlockFromHex(blockHex)
if (!parsed.hasTransactionsGenerator) {
console.log('Block has no generator')
return
}
// Extract just the generator for analysis
const blockBytes = Buffer.from(blockHex, 'hex')
const generator = parser.extractGeneratorFromBlockBytes(blockBytes)
console.log(`Generator Analysis for Block ${parsed.height}:`)
console.log(` Generator size: ${parsed.generatorSize} bytes`)
console.log(` Hex length: ${generator.length} characters`)
console.log(` Coin spends extracted: ${parsed.coinSpends.length}`)
console.log(` Coins created: ${parsed.coinCreations.length}`)
// Analyze coin spends
parsed.coinSpends.forEach((spend, i) => {
console.log(` Spend ${i + 1}:`)
console.log(` Amount: ${spend.coin.amount} mojos`)
console.log(` Puzzle size: ${spend.puzzleReveal.length / 2} bytes`)
console.log(` Solution size: ${spend.solution.length / 2} bytes`)
console.log(` Creates ${spend.createdCoins.length} new coins`)
})
}
// Example usage
const blockHex = "your_generator_block_hex"
analyzeBlockGenerator(blockHex)
const { ChiaBlockParser, ChiaPeerPool, DnsDiscoveryClient } = require('@dignetwork/chia-block-listener')
async function integratedBlockAnalysis() {
// Set up components
const parser = new ChiaBlockParser()
const pool = new ChiaPeerPool()
const discovery = new DnsDiscoveryClient()
// Discover and connect to peers
const peers = await discovery.discoverMainnetPeers()
for (const peer of peers.ipv4Peers.slice(0, 3)) {
await pool.addPeer(peer.host, peer.port, 'mainnet')
}
// Fetch blocks and parse with enhanced detail
const heights = [5000000, 5000001, 5000002]
for (const height of heights) {
// Get block using peer pool
const blockEvent = await pool.getBlockByHeight(height)
// For more detailed analysis, you can also parse with ChiaBlockParser
// if you have access to the raw block bytes
console.log(`Block ${height}:`)
console.log(` From peer: ${blockEvent.peerId}`)
console.log(` Coin spends: ${blockEvent.coinSpends.length}`)
console.log(` Has generator: ${blockEvent.hasTransactionsGenerator}`)
// Analyze puzzle patterns
const puzzleHashes = new Set()
blockEvent.coinSpends.forEach(spend => {
puzzleHashes.add(spend.coin.puzzleHash)
})
console.log(` Unique puzzle hashes: ${puzzleHashes.size}`)
}
await pool.shutdown()
}
integratedBlockAnalysis().catch(console.error)
ParsedBlockJsinterface ParsedBlockJs {
height: number // Block height
weight: string // Block weight as string
headerHash: string // Block header hash (hex)
timestamp?: number // Block timestamp (Unix time)
coinAdditions: CoinInfoJs[] // New coins created
coinRemovals: CoinInfoJs[] // Coins spent
coinSpends: CoinSpendInfoJs[] // Detailed spend information
coinCreations: CoinInfoJs[] // Coins created by spends
hasTransactionsGenerator: boolean // Whether block has generator
generatorSize?: number // Generator size in bytes
}
CoinInfoJsinterface CoinInfoJs {
parentCoinInfo: string // Parent coin ID (hex)
puzzleHash: string // Puzzle hash (hex)
amount: string // Amount as string (to avoid JS precision issues)
}
CoinSpendInfoJsinterface CoinSpendInfoJs {
coin: CoinInfoJs // The coin being spent
puzzleReveal: string // CLVM puzzle bytecode (hex)
solution: string // CLVM solution bytecode (hex)
realData: boolean // Whether this is real transaction data
parsingMethod: string // Method used for parsing
offset: number // Offset in generator bytecode
createdCoins: CoinInfoJs[] // Coins created by this spend
}
GeneratorBlockInfoJsinterface GeneratorBlockInfoJs {
prevHeaderHash: string // Previous block hash (hex)
transactionsGenerator?: string // Generator bytecode (hex)
transactionsGeneratorRefList: number[] // Referenced block heights
}
BlockHeightInfoJsinterface BlockHeightInfoJs {
height: number // Block height
isTransactionBlock: boolean // Whether block contains transactions
}
Use ChiaBlockParser when:
Use with ChiaPeerPool when:
Use with ChiaBlockListener when:
The ChiaBlockParser complements the other classes by providing the lowest-level access to the Chia block parsing engine, enabling sophisticated analysis and processing workflows.
import {
ChiaBlockListener,
ChiaPeerPool,
ChiaBlockParser,
DnsDiscoveryClient,
BlockReceivedEvent,
PeerConnectedEvent,
PeerDisconnectedEvent,
NewPeakHeightEvent,
DiscoveryResultJS,
PeerAddressJS,
AddressResult,
CoinRecord,
CoinSpend,
ParsedBlockJs,
CoinInfoJs,
CoinSpendInfoJs,
GeneratorBlockInfoJs,
BlockHeightInfoJs,
initTracing,
getEventTypes
} from '@dignetwork/chia-block-listener'
// Initialize tracing for debugging
initTracing()
// Create listener with proper typing
const listener = new ChiaBlockListener()
// Type-safe event handlers
listener.on('blockReceived', (block: BlockReceivedEvent) => {
console.log(`Block ${block.height} from peer ${block.peerId}`)
// Process coin additions
block.coinAdditions.forEach((coin: CoinRecord) => {
console.log(`New coin: ${coin.amount} mojos`)
})
// Process coin spends
block.coinSpends.forEach((spend: CoinSpend) => {
console.log(`Spend: ${spend.coin.amount} mojos`)
console.log(`Puzzle: ${spend.puzzleReveal}`)
console.log(`Solution: ${spend.solution}`)
})
})
listener.on('peerConnected', (peer: PeerConnectedEvent) => {
console.log(`Connected: ${peer.peerId} at ${peer.host}:${peer.port}`)
})
listener.on('peerDisconnected', (peer: PeerDisconnectedEvent) => {
console.log(`Disconnected: ${peer.peerId}`)
if (peer.message) {
console.log(`Reason: ${peer.message}`)
}
})
// Connect to peers
const mainnetPeer = listener.addPeer('localhost', 8444, 'mainnet')
const testnetPeer = listener.addPeer('testnet-node.chia.net', 58444, 'testnet')
// Get historical blocks
async function getHistoricalBlocks() {
try {
const block = listener.getBlockByHeight(mainnetPeer, 1000000)
console.log(`Block 1000000 hash: ${block.headerHash}`)
const blocks = listener.getBlocksRange(mainnetPeer, 1000000, 1000010)
console.log(`Retrieved ${blocks.length} blocks`)
} catch (error) {
console.error('Error getting blocks:', error)
}
}
// Get event type constants
const eventTypes = getEventTypes()
console.log('Available events:', eventTypes)
// TypeScript support for ChiaPeerPool
const pool = new ChiaPeerPool()
// Type-safe event handling
pool.on('peerConnected', (event: PeerConnectedEvent) => {
console.log(`Pool peer connected: ${event.peerId}`)
})
pool.on('newPeakHeight', (event: NewPeakHeightEvent) => {
console.log(`New peak: ${event.oldPeak} → ${event.newPeak}`)
})
// Async/await with proper typing
async function fetchHistoricalData() {
const block: BlockReceivedEvent = await pool.getBlockByHeight(5000000)
const peers: string[] = await pool.getConnectedPeers()
const peak: number | null = await pool.getPeakHeight()
console.log(`Block ${block.height} has ${block.coinSpends.length} spends`)
console.log(`Pool has ${peers.length} active peers`)
console.log(`Current peak: ${peak || 'No peak yet'}`)
}
// TypeScript DNS Discovery
async function typedDnsDiscovery(): Promise<void> {
const client = new DnsDiscoveryClient()
// Type-safe discovery
const result: DiscoveryResultJS = await client.discoverMainnetPeers()
// Access with full type safety
result.ipv4Peers.forEach((peer: PeerAddressJS) => {
console.log(`IPv4: ${peer.host}:${peer.port} (${peer.displayAddress})`)
})
result.ipv6Peers.forEach((peer: PeerAddressJS) => {
console.log(`IPv6: ${peer.displayAddress} (isIpv6: ${peer.isIpv6})`)
})
// Individual resolution with types
const ipv4Result: AddressResult = await client.resolveIpv4('dns-introducer.chia.net')
const ipv6Result: AddressResult = await client.resolveIpv6('dns-introducer.chia.net')
console.log(`IPv4 count: ${ipv4Result.count}, IPv6 count: ${ipv6Result.count}`)
}
// TypeScript ChiaBlockParser
async function typedBlockParsing(): Promise<void> {
const parser = new ChiaBlockParser()
// Type-safe block parsing from hex
const blockHex = "your_block_hex_data"
const parsedBlock: ParsedBlockJs = parser.parseFullBlockFromHex(blockHex)
console.log(`Parsed block ${parsedBlock.height}:`)
console.log(` Header hash: ${parsedBlock.headerHash}`)
console.log(` Weight: ${parsedBlock.weight}`)
console.log(` Coin additions: ${parsedBlock.coinAdditions.length}`)
console.log(` Coin spends: ${parsedBlock.coinSpends.length}`)
console.log(` Has generator: ${parsedBlock.hasTransactionsGenerator}`)
// Type-safe access to coin spend details
parsedBlock.coinSpends.forEach((spend: CoinSpendInfoJs) => {
const coin: CoinInfoJs = spend.coin
console.log(`Spend: ${coin.amount} mojos from ${coin.puzzleHash}`)
console.log(` Puzzle reveal size: ${spend.puzzleReveal.length / 2} bytes`)
console.log(` Solution size: ${spend.solution.length / 2} bytes`)
console.log(` Creates ${spend.createdCoins.length} new coins`)
console.log(` Real data: ${spend.realData}`)
console.log(` Parsing method: ${spend.parsingMethod}`)
})
// Type-safe generator extraction
const blockBytes = Buffer.from(blockHex, 'hex')
const generator: string | null = parser.extractGeneratorFromBlockBytes(blockBytes)
if (generator) {
console.log(`Generator found: ${generator.length / 2} bytes`)
}
// Type-safe height and tx status
const heightInfo: BlockHeightInfoJs = parser.getHeightAndTxStatusFromBlockBytes(blockBytes)
console.log(`Block ${heightInfo.height}, is transaction block: ${heightInfo.isTransactionBlock}`)
// Type-safe block info extraction
const blockInfo: GeneratorBlockInfoJs = parser.parseBlockInfoFromBytes(blockBytes)
console.log(`Previous hash: ${blockInfo.prevHeaderHash}`)
console.log(`Generator refs: ${blockInfo.transactionsGeneratorRefList.length}`)
if (blockInfo.transactionsGenerator) {
console.log(`Generator size: ${blockInfo.transactionsGenerator.length / 2} bytes`)
}
}
// Monitor all coin spends for a specific puzzle hash
listener.on('blockReceived', (block) => {
const targetPuzzleHash = '0x1234...' // Your puzzle hash
block.coinSpends.forEach((spend) => {
if (spend.coin.puzzleHash === targetPuzzleHash) {
console.log('Found spend for our puzzle!')
console.log('Amount:', spend.coin.amount)
console.log('Solution:', spend.solution)
}
})
})
// Monitor both mainnet and testnet
const mainnetPeer = listener.addPeer('localhost', 8444, 'mainnet')
const testnetPeer = listener.addPeer('localhost', 58444, 'testnet')
listener.on('blockReceived', (block) => {
if (block.peerId === mainnetPeer) {
console.log(`Mainnet block ${block.height}`)
} else if (block.peerId === testnetPeer) {
console.log(`Testnet block ${block.height}`)
}
})
// Automatic reconnection
listener.on('peerDisconnected', (peer) => {
console.log(`Lost connection to ${peer.peerId}, reconnecting...`)
// Reconnect after 5 seconds
setTimeout(() => {
try {
listener.addPeer(peer.host, peer.port, 'mainnet')
console.log('Reconnected successfully')
} catch (error) {
console.error('Reconnection failed:', error)
}
}, 5000)
})
initTracing(): voidInitializes the Rust tracing system for debugging purposes. Call this before creating any ChiaBlockListener instances if you want to see debug output.
getEventTypes(): EventTypesReturns an object containing the event type constants:
const eventTypes = getEventTypes()
console.log(eventTypes)
// Output: { blockReceived: "blockReceived", peerConnected: "peerConnected", peerDisconnected: "peerDisconnected" }
# Clone and install dependencies
git clone <repository-url>
cd chia-block-listener
npm install
# Build the native module
npm run build
# Run tests
npm test
chia-block-listener/
├── src/ # Rust source code
│ ├── lib.rs # Main NAPI bindings
│ ├── peer.rs # Peer connection management
│ ├── protocol.rs # Chia protocol implementation
│ ├── event_emitter.rs # Event system
│ └── tls.rs # TLS connection handling
├── crate/ # Additional Rust crates
│ └── chia-generator-parser/ # CLVM parser
├── __test__/ # Test suite
├── npm/ # Platform-specific binaries
├── .github/workflows/ # CI/CD pipeline
├── Cargo.toml # Rust configuration
├── package.json # Node.js configuration
└── index.d.ts # TypeScript definitions
This project uses GitHub Actions for:
MIT
git checkout -b feature/amazing-feature)npm test)git commit -m 'Add some amazing feature')git push origin feature/amazing-feature)For issues and questions:
index.d.tsexamples/ directory for more usage examples