@dignetwork/chia-block-listener 0.1.16 → 0.1.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,1031 +1,1428 @@
1
- # Chia Block Listener
2
-
3
- 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.
4
-
5
- ## Features
6
-
7
- - **Real-time Block Monitoring**: Listen for new blocks as they're produced on the Chia network
8
- - **Peer Management**: Connect to multiple Chia full nodes simultaneously
9
- - **Automatic Failover**: Intelligent peer failover with automatic retry across multiple peers
10
- - **Enhanced Error Handling**: Automatic disconnection of peers that refuse blocks or have protocol errors
11
- - **Efficient Parsing**: Fast extraction of coin spends, additions, and removals from blocks
12
- - **Event-Driven Architecture**: TypeScript-friendly event system with full type safety
13
- - **Transaction Analysis**: Parse CLVM puzzles and solutions from coin spends
14
- - **Historical Block Access**: Retrieve blocks by height or ranges with automatic load balancing
15
- - **Connection Pool**: ChiaPeerPool provides automatic load balancing and rate limiting for historical queries
16
- - **Peak Height Tracking**: Monitor blockchain sync progress across all connected peers
17
- - **DNS Peer Discovery**: Automatic peer discovery using Chia network DNS introducers with IPv4/IPv6 support
18
- - **Cross-platform Support**: Works on Windows, macOS, and Linux (x64 and ARM64)
19
- - **TypeScript Support**: Complete TypeScript definitions with IntelliSense
20
-
21
- ## Installation
22
-
23
- ```bash
24
- npm install @dignetwork/chia-block-listener
25
- ```
26
-
27
- ## Quick Start
28
-
29
- ```javascript
30
- const { ChiaBlockListener, initTracing } = require('@dignetwork/chia-block-listener')
31
-
32
- // Initialize tracing for debugging (optional)
33
- initTracing()
34
-
35
- // Create a new listener instance
36
- const listener = new ChiaBlockListener()
37
-
38
- // Listen for block events
39
- listener.on('blockReceived', (block) => {
40
- console.log(`New block received: ${block.height}`)
41
- console.log(`Header hash: ${block.headerHash}`)
42
- console.log(`Timestamp: ${new Date(block.timestamp * 1000)}`)
43
- console.log(`Coin additions: ${block.coinAdditions.length}`)
44
- console.log(`Coin removals: ${block.coinRemovals.length}`)
45
- console.log(`Coin spends: ${block.coinSpends.length}`)
46
- })
47
-
48
- // Listen for peer connection events
49
- listener.on('peerConnected', (peer) => {
50
- console.log(`Connected to peer: ${peer.peerId} (${peer.host}:${peer.port})`)
51
- })
52
-
53
- listener.on('peerDisconnected', (peer) => {
54
- console.log(`Disconnected from peer: ${peer.peerId}`)
55
- if (peer.message) {
56
- console.log(`Reason: ${peer.message}`)
57
- }
58
- })
59
-
60
- // Connect to a Chia full node
61
- const peerId = listener.addPeer('localhost', 8444, 'mainnet')
62
- console.log(`Added peer: ${peerId}`)
63
-
64
- // Keep the process running
65
- process.on('SIGINT', () => {
66
- console.log('Shutting down...')
67
- listener.disconnectAllPeers()
68
- process.exit(0)
69
- })
70
- ```
71
- ## API Reference
72
-
73
- ### ChiaBlockListener Class
74
-
75
- #### Constructor
76
-
77
- ```javascript
78
- const listener = new ChiaBlockListener()
79
- ```
80
-
81
- Creates a new Chia block listener instance.
82
-
83
- #### Methods
84
-
85
- ##### `addPeer(host, port, networkId): string`
86
-
87
- Connects to a Chia full node and starts listening for blocks.
88
-
89
- **Parameters:**
90
- - `host` (string): The hostname or IP address of the Chia node
91
- - `port` (number): The port number (typically 8444 for mainnet)
92
- - `networkId` (string): The network identifier ('mainnet', 'testnet', etc.)
93
-
94
- **Returns:** A unique peer ID string for this connection
95
-
96
- ##### `disconnectPeer(peerId): boolean`
97
-
98
- Disconnects from a specific peer.
99
-
100
- **Parameters:**
101
- - `peerId` (string): The peer ID returned by `addPeer()`
102
-
103
- **Returns:** `true` if the peer was successfully disconnected, `false` otherwise
104
-
105
- ##### `disconnectAllPeers(): void`
106
-
107
- Disconnects from all connected peers.
108
-
109
- ##### `getConnectedPeers(): string[]`
110
-
111
- Returns an array of currently connected peer IDs.
112
-
113
- ##### `getBlockByHeight(peerId, height): BlockReceivedEvent`
114
-
115
- Retrieves a specific block by its height from a connected peer.
116
-
117
- **Parameters:**
118
- - `peerId` (string): The peer ID to query
119
- - `height` (number): The block height to retrieve
120
-
121
- **Returns:** A `BlockReceivedEvent` object containing the block data
122
-
123
- ##### `getBlocksRange(peerId, startHeight, endHeight): BlockReceivedEvent[]`
124
-
125
- Retrieves a range of blocks from a connected peer.
126
-
127
- **Parameters:**
128
- - `peerId` (string): The peer ID to query
129
- - `startHeight` (number): The starting block height (inclusive)
130
- - `endHeight` (number): The ending block height (inclusive)
131
-
132
- **Returns:** An array of `BlockReceivedEvent` objects
133
-
134
- ### ChiaPeerPool Class
135
-
136
- 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.
137
-
138
- #### Constructor
139
-
140
- ```javascript
141
- const pool = new ChiaPeerPool()
142
- ```
143
-
144
- Creates a new peer pool instance with built-in rate limiting (500ms per peer).
145
-
146
- #### Methods
147
-
148
- ##### `addPeer(host, port, networkId): Promise<string>`
149
-
150
- Adds a peer to the connection pool.
151
-
152
- **Parameters:**
153
- - `host` (string): The hostname or IP address of the Chia node
154
- - `port` (number): The port number (typically 8444 for mainnet)
155
- - `networkId` (string): The network identifier ('mainnet', 'testnet', etc.)
156
-
157
- **Returns:** A Promise that resolves to a unique peer ID string
158
-
159
- ##### `getBlockByHeight(height): Promise<BlockReceivedEvent>`
160
-
161
- Retrieves a specific block by height using automatic peer selection and load balancing.
162
-
163
- **Parameters:**
164
- - `height` (number): The block height to retrieve
165
-
166
- **Returns:** A Promise that resolves to a `BlockReceivedEvent` object
167
-
168
- ##### `removePeer(peerId): Promise<boolean>`
169
-
170
- Removes a peer from the pool.
171
-
172
- **Parameters:**
173
- - `peerId` (string): The peer ID to remove
174
-
175
- **Returns:** A Promise that resolves to `true` if the peer was removed, `false` otherwise
176
-
177
- ##### `shutdown(): Promise<void>`
178
-
179
- Shuts down the pool and disconnects all peers.
180
-
181
- ##### `getConnectedPeers(): Promise<string[]>`
182
-
183
- Gets the list of currently connected peer IDs.
184
-
185
- **Returns:** Array of peer ID strings (format: "host:port")
186
-
187
- ##### `getPeakHeight(): Promise<number | null>`
188
-
189
- Gets the highest blockchain peak height seen across all connected peers.
190
-
191
- **Returns:** The highest peak height as a number, or null if no peaks have been received yet
192
-
193
- ##### `on(event, callback): void`
194
-
195
- Registers an event handler for pool events.
196
-
197
- **Parameters:**
198
- - `event` (string): The event name ('peerConnected' or 'peerDisconnected')
199
- - `callback` (function): The event handler function
200
-
201
- ##### `off(event, callback): void`
202
-
203
- Removes an event handler.
204
-
205
- **Parameters:**
206
- - `event` (string): The event name to stop listening for
207
-
208
- ### Events
209
-
210
- The `ChiaBlockListener` emits the following events:
211
-
212
- #### `blockReceived`
213
-
214
- Fired when a new block is received from any connected peer.
215
-
216
- **Callback:** `(event: BlockReceivedEvent) => void`
217
-
218
- #### `peerConnected`
219
-
220
- Fired when a connection to a peer is established.
221
-
222
- **Callback:** `(event: PeerConnectedEvent) => void`
223
-
224
- #### `peerDisconnected`
225
-
226
- Fired when a peer connection is lost.
227
-
228
- **Callback:** `(event: PeerDisconnectedEvent) => void`
229
-
230
- ### ChiaPeerPool Events
231
-
232
- The `ChiaPeerPool` emits the following events:
233
-
234
- #### `peerConnected`
235
-
236
- Fired when a peer is successfully added to the pool.
237
-
238
- **Callback:** `(event: PeerConnectedEvent) => void`
239
-
240
- #### `peerDisconnected`
241
-
242
- Fired when a peer is removed from the pool or disconnects.
243
-
244
- **Callback:** `(event: PeerDisconnectedEvent) => void`
245
-
246
- #### `newPeakHeight`
247
-
248
- Fired when a new highest blockchain peak is discovered.
249
-
250
- **Callback:** `(event: NewPeakHeightEvent) => void`
251
-
252
- ### DnsDiscoveryClient Class
253
-
254
- The `DnsDiscoveryClient` provides automatic peer discovery using Chia network DNS introducers with full IPv4 and IPv6 support.
255
-
256
- #### Constructor
257
-
258
- ```javascript
259
- const client = new DnsDiscoveryClient()
260
- ```
261
-
262
- Creates a new DNS discovery client instance.
263
-
264
- #### Methods
265
-
266
- ##### `discoverMainnetPeers(): Promise<DiscoveryResultJS>`
267
-
268
- Discovers peers for Chia mainnet using built-in DNS introducers.
269
-
270
- **Returns:** Promise resolving to discovery results with separate IPv4 and IPv6 peer lists
271
-
272
- ##### `discoverTestnet11Peers(): Promise<DiscoveryResultJS>`
273
-
274
- Discovers peers for Chia testnet11 using built-in DNS introducers.
275
-
276
- **Returns:** Promise resolving to discovery results
277
-
278
- ##### `discoverPeers(introducers, port): Promise<DiscoveryResultJS>`
279
-
280
- Discovers peers using custom DNS introducers.
281
-
282
- **Parameters:**
283
- - `introducers` (string[]): Array of DNS introducer hostnames
284
- - `port` (number): Default port for discovered peers
285
-
286
- **Returns:** Promise resolving to discovery results
287
-
288
- ##### `resolveIpv4(hostname): Promise<AddressResult>`
289
-
290
- Resolves IPv4 addresses (A records) for a hostname.
291
-
292
- **Parameters:**
293
- - `hostname` (string): Hostname to resolve
294
-
295
- **Returns:** Promise resolving to IPv4 addresses
296
-
297
- ##### `resolveIpv6(hostname): Promise<AddressResult>`
298
-
299
- Resolves IPv6 addresses (AAAA records) for a hostname.
300
-
301
- **Parameters:**
302
- - `hostname` (string): Hostname to resolve
303
-
304
- **Returns:** Promise resolving to IPv6 addresses
305
-
306
- ##### `resolveBoth(hostname, port): Promise<DiscoveryResultJS>`
307
-
308
- Resolves both IPv4 and IPv6 addresses for a hostname.
309
-
310
- **Parameters:**
311
- - `hostname` (string): Hostname to resolve
312
- - `port` (number): Port for the peer addresses
313
-
314
- **Returns:** Promise resolving to discovery results
315
-
316
- ### Event Data Types
317
-
318
- #### `BlockReceivedEvent`
319
-
320
- ```typescript
321
- interface BlockReceivedEvent {
322
- peerId: string // IP address of the peer that sent this block
323
- height: number // Block height
324
- weight: string // Block weight as string
325
- headerHash: string // Block header hash (hex)
326
- timestamp: number // Block timestamp (Unix time)
327
- coinAdditions: CoinRecord[] // New coins created in this block
328
- coinRemovals: CoinRecord[] // Coins spent in this block
329
- coinSpends: CoinSpend[] // Detailed spend information
330
- coinCreations: CoinRecord[] // Coins created by puzzles
331
- hasTransactionsGenerator: boolean // Whether block has a generator
332
- generatorSize: number // Size of the generator bytecode
333
- }
334
- ```
335
-
336
- #### `PeerConnectedEvent`
337
-
338
- ```typescript
339
- interface PeerConnectedEvent {
340
- peerId: string // Peer IP address
341
- host: string // Peer hostname/IP
342
- port: number // Peer port number
343
- }
344
- ```
345
-
346
- #### `PeerDisconnectedEvent`
347
-
348
- ```typescript
349
- interface PeerDisconnectedEvent {
350
- peerId: string // Peer IP address
351
- host: string // Peer hostname/IP
352
- port: number // Peer port number
353
- message?: string // Optional disconnection reason
354
- }
355
- ```
356
-
357
- #### `NewPeakHeightEvent`
358
-
359
- ```typescript
360
- interface NewPeakHeightEvent {
361
- oldPeak: number | null // Previous highest peak (null if first peak)
362
- newPeak: number // New highest peak height
363
- peerId: string // Peer that discovered this peak
364
- }
365
- ```
366
-
367
- #### `CoinRecord`
368
-
369
- ```typescript
370
- interface CoinRecord {
371
- parentCoinInfo: string // Parent coin ID (hex)
372
- puzzleHash: string // Puzzle hash (hex)
373
- amount: string // Coin amount as string
374
- }
375
- ```
376
-
377
- #### `CoinSpend`
378
-
379
- ```typescript
380
- interface CoinSpend {
381
- coin: CoinRecord // The coin being spent
382
- puzzleReveal: string // CLVM puzzle bytecode (hex)
383
- solution: string // CLVM solution bytecode (hex)
384
- offset: number // Offset in the generator bytecode
385
- }
386
- ```
387
-
388
- #### `DiscoveryResultJS`
389
-
390
- ```typescript
391
- interface DiscoveryResultJS {
392
- ipv4Peers: PeerAddressJS[] // IPv4 peer addresses
393
- ipv6Peers: PeerAddressJS[] // IPv6 peer addresses
394
- totalCount: number // Total peers found
395
- }
396
- ```
397
-
398
- #### `PeerAddressJS`
399
-
400
- ```typescript
401
- interface PeerAddressJS {
402
- host: string // IP address as string
403
- port: number // Port number
404
- isIpv6: boolean // Protocol indicator
405
- displayAddress: string // Formatted for display/URLs
406
- }
407
- ```
408
-
409
- #### `AddressResult`
410
-
411
- ```typescript
412
- interface AddressResult {
413
- addresses: string[] // List of IP addresses
414
- count: number // Number of addresses
415
- }
416
- ```
417
-
418
- ## ChiaPeerPool Usage
419
-
420
- 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.
421
-
422
- ### Basic Usage
423
-
424
- ```javascript
425
- const { ChiaPeerPool, initTracing } = require('@dignetwork/chia-block-listener')
426
-
427
- async function main() {
428
- // Initialize tracing
429
- initTracing()
430
-
431
- // Create a peer pool
432
- const pool = new ChiaPeerPool()
433
-
434
- // Listen for pool events
435
- pool.on('peerConnected', (event) => {
436
- console.log(`Peer connected to pool: ${event.peerId}`)
437
- })
438
-
439
- pool.on('peerDisconnected', (event) => {
440
- console.log(`Peer disconnected from pool: ${event.peerId}`)
441
- })
442
-
443
- pool.on('newPeakHeight', (event) => {
444
- console.log(`New blockchain peak detected!`)
445
- console.log(` Previous: ${event.oldPeak || 'None'}`)
446
- console.log(` New: ${event.newPeak}`)
447
- console.log(` Discovered by: ${event.peerId}`)
448
- })
449
-
450
- // Add multiple peers
451
- await pool.addPeer('node1.chia.net', 8444, 'mainnet')
452
- await pool.addPeer('node2.chia.net', 8444, 'mainnet')
453
- await pool.addPeer('node3.chia.net', 8444, 'mainnet')
454
-
455
- // Fetch blocks with automatic load balancing
456
- const block1 = await pool.getBlockByHeight(5000000)
457
- const block2 = await pool.getBlockByHeight(5000001)
458
- const block3 = await pool.getBlockByHeight(5000002)
459
-
460
- console.log(`Block ${block1.height}: ${block1.coinSpends.length} spends`)
461
- console.log(`Block ${block2.height}: ${block2.coinSpends.length} spends`)
462
- console.log(`Block ${block3.height}: ${block3.coinSpends.length} spends`)
463
-
464
- // Shutdown the pool
465
- await pool.shutdown()
466
- }
467
-
468
- main().catch(console.error)
469
- ```
470
-
471
- ### Advanced Pool Features
472
-
473
- #### Rate Limiting
474
-
475
- The pool automatically enforces a 50ms rate limit per peer for maximum performance while preventing node overload:
476
-
477
- ```javascript
478
- // Rapid requests are automatically queued and distributed
479
- const promises = []
480
- for (let i = 5000000; i < 5000100; i++) {
481
- promises.push(pool.getBlockByHeight(i))
482
- }
483
-
484
- // All requests will be processed efficiently across all peers
485
- // with automatic load balancing and rate limiting
486
- const blocks = await Promise.all(promises)
487
- console.log(`Retrieved ${blocks.length} blocks`)
488
- ```
489
-
490
- #### Automatic Failover and Error Handling
491
-
492
- The pool provides robust error handling with automatic failover:
493
-
494
- ```javascript
495
- // The pool automatically handles various error scenarios:
496
-
497
- // 1. Connection failures - automatically tries other peers
498
- try {
499
- const block = await pool.getBlockByHeight(5000000)
500
- console.log(`Retrieved block ${block.height}`)
501
- } catch (error) {
502
- // If all peers fail, you'll get an error after all retry attempts
503
- console.error('All peers failed:', error.message)
504
- }
505
-
506
- // 2. Protocol errors - peers that refuse blocks are automatically disconnected
507
- pool.on('peerDisconnected', (event) => {
508
- console.log(`Peer ${event.peerId} disconnected: ${event.reason}`)
509
- // Reasons include: "Block request rejected", "Protocol error", "Connection timeout"
510
- })
511
-
512
- // 3. Automatic peer cleanup - problematic peers are removed from the pool
513
- console.log('Active peers before:', await pool.getConnectedPeers())
514
- await pool.getBlockByHeight(5000000) // May trigger peer removal
515
- console.log('Active peers after:', await pool.getConnectedPeers())
516
-
517
- // 4. Multiple retry attempts - tries up to 3 different peers per request
518
- // This happens automatically and transparently
519
- const block = await pool.getBlockByHeight(5000000) // Will try multiple peers if needed
520
- ```
521
-
522
- **Error Types Handled Automatically:**
523
- - **Connection Errors**: Timeouts, network failures, WebSocket errors
524
- - **Protocol Errors**: Block rejections, parsing failures, handshake failures
525
- - **Peer Misbehavior**: Unexpected responses, invalid data formats
526
-
527
- #### Dynamic Peer Management
528
-
529
- ```javascript
530
- // Monitor pool health
531
- const peers = await pool.getConnectedPeers()
532
- console.log(`Active peers in pool: ${peers.length}`)
533
-
534
- // Remove underperforming peers
535
- if (slowPeer) {
536
- await pool.removePeer(slowPeer)
537
- console.log('Removed slow peer from pool')
538
- }
539
-
540
- // Add new peers dynamically
541
- if (peers.length < 3) {
542
- await pool.addPeer('backup-node.chia.net', 8444, 'mainnet')
543
- }
544
- ```
545
-
546
- #### Error Handling
547
-
548
- ```javascript
549
- try {
550
- const block = await pool.getBlockByHeight(5000000)
551
- console.log(`Retrieved block ${block.height}`)
552
- } catch (error) {
553
- console.error('Failed to retrieve block:', error)
554
-
555
- // The pool will automatically try other peers
556
- // You can also add more peers if needed
557
- const peers = await pool.getConnectedPeers()
558
- if (peers.length === 0) {
559
- console.log('No peers available, adding new ones...')
560
- await pool.addPeer('node1.chia.net', 8444, 'mainnet')
561
- }
562
- }
563
- ```
564
-
565
- #### Peak Height Tracking
566
-
567
- ```javascript
568
- // Monitor blockchain sync progress
569
- const pool = new ChiaPeerPool()
570
-
571
- // Track peak changes
572
- let currentPeak = null
573
- pool.on('newPeakHeight', (event) => {
574
- currentPeak = event.newPeak
575
- const progress = event.oldPeak
576
- ? `+${event.newPeak - event.oldPeak} blocks`
577
- : 'Initial peak'
578
- console.log(`Peak update: ${event.newPeak} (${progress})`)
579
- })
580
-
581
- // Add peers
582
- await pool.addPeer('node1.chia.net', 8444, 'mainnet')
583
- await pool.addPeer('node2.chia.net', 8444, 'mainnet')
584
-
585
- // Check current peak
586
- const peak = await pool.getPeakHeight()
587
- console.log(`Current highest peak: ${peak || 'None yet'}`)
588
-
589
- // Fetch some blocks to trigger peak updates
590
- await pool.getBlockByHeight(5000000)
591
- await pool.getBlockByHeight(5100000)
592
- await pool.getBlockByHeight(5200000)
593
-
594
- // Monitor sync status
595
- setInterval(async () => {
596
- const peak = await pool.getPeakHeight()
597
- if (peak) {
598
- const estimatedCurrent = 5200000 + Math.floor((Date.now() / 1000 - 1700000000) / 18.75)
599
- const syncPercentage = (peak / estimatedCurrent * 100).toFixed(2)
600
- console.log(`Sync status: ${syncPercentage}% (peak: ${peak})`)
601
- }
602
- }, 60000) // Check every minute
603
- ```
604
-
605
- ### When to Use ChiaPeerPool vs ChiaBlockListener
606
-
607
- - **Use ChiaPeerPool when:**
608
- - You need to fetch historical blocks
609
- - You want automatic load balancing across multiple peers
610
- - You're making many block requests and need rate limiting
611
- - You don't need real-time block notifications
612
-
613
- - **Use ChiaBlockListener when:**
614
- - You need real-time notifications of new blocks
615
- - You want to monitor the blockchain as it grows
616
- - You need to track specific addresses or puzzle hashes in real-time
617
- - You're building applications that react to blockchain events
618
-
619
- Both classes can be used together in the same application for different purposes.
620
-
621
- ## DNS Discovery Usage
622
-
623
- The `DnsDiscoveryClient` enables automatic discovery of Chia network peers using DNS introducers, with full support for both IPv4 and IPv6 addresses.
624
-
625
- ### Basic DNS Discovery
626
-
627
- ```javascript
628
- const { DnsDiscoveryClient, initTracing } = require('@dignetwork/chia-block-listener')
629
-
630
- async function discoverPeers() {
631
- // Initialize tracing
632
- initTracing()
633
-
634
- // Create DNS discovery client
635
- const client = new DnsDiscoveryClient()
636
-
637
- // Discover mainnet peers
638
- const result = await client.discoverMainnetPeers()
639
-
640
- console.log(`Found ${result.totalCount} total peers:`)
641
- console.log(` IPv4 peers: ${result.ipv4Peers.length}`)
642
- console.log(` IPv6 peers: ${result.ipv6Peers.length}`)
643
-
644
- // Use with peer connections
645
- for (const peer of result.ipv4Peers.slice(0, 3)) {
646
- console.log(`IPv4 peer: ${peer.displayAddress}`)
647
- // peer.host and peer.port can be used with addPeer()
648
- }
649
-
650
- for (const peer of result.ipv6Peers.slice(0, 3)) {
651
- console.log(`IPv6 peer: ${peer.displayAddress}`) // [2001:db8::1]:8444
652
- // IPv6 addresses are properly formatted with brackets
653
- }
654
- }
655
-
656
- discoverPeers().catch(console.error)
657
- ```
658
-
659
- ### Integration with Peer Pool
660
-
661
- ```javascript
662
- const { ChiaPeerPool, DnsDiscoveryClient } = require('@dignetwork/chia-block-listener')
663
-
664
- async function setupPoolWithDnsDiscovery() {
665
- const pool = new ChiaPeerPool()
666
- const discovery = new DnsDiscoveryClient()
667
-
668
- // Discover peers automatically
669
- const peers = await discovery.discoverMainnetPeers()
670
-
671
- // Add discovered peers to pool (both IPv4 and IPv6)
672
- const allPeers = [...peers.ipv4Peers, ...peers.ipv6Peers]
673
- for (const peer of allPeers.slice(0, 5)) {
674
- await pool.addPeer(peer.host, peer.port, 'mainnet')
675
- console.log(`Added peer: ${peer.displayAddress}`)
676
- }
677
-
678
- // Now use the pool for block retrieval
679
- const block = await pool.getBlockByHeight(5000000)
680
- console.log(`Retrieved block ${block.height}`)
681
-
682
- await pool.shutdown()
683
- }
684
-
685
- setupPoolWithDnsDiscovery().catch(console.error)
686
- ```
687
-
688
- ### Custom DNS Introducers
689
-
690
- ```javascript
691
- const client = new DnsDiscoveryClient()
692
-
693
- // Use custom introducers
694
- const customIntroducers = [
695
- 'seeder.dexie.space',
696
- 'chia.hoffmang.com'
697
- ]
698
-
699
- const result = await client.discoverPeers(customIntroducers, 8444)
700
- console.log(`Found ${result.totalCount} peers from custom introducers`)
701
- ```
702
-
703
- ### Individual DNS Resolution
704
-
705
- ```javascript
706
- const client = new DnsDiscoveryClient()
707
- const hostname = 'dns-introducer.chia.net'
708
-
709
- // Resolve specific protocols
710
- try {
711
- const ipv4 = await client.resolveIpv4(hostname)
712
- console.log(`IPv4 addresses: ${ipv4.addresses.join(', ')}`)
713
- } catch (error) {
714
- console.log(`IPv4 resolution failed: ${error.message}`)
715
- }
716
-
717
- try {
718
- const ipv6 = await client.resolveIpv6(hostname)
719
- console.log(`IPv6 addresses: ${ipv6.addresses.join(', ')}`)
720
- } catch (error) {
721
- console.log(`IPv6 resolution failed: ${error.message}`)
722
- }
723
-
724
- // Or resolve both at once
725
- const both = await client.resolveBoth(hostname, 8444)
726
- console.log(`Combined: ${both.totalCount} addresses`)
727
- ```
728
-
729
-
730
-
731
- ### Error Handling
732
-
733
- ```javascript
734
- const client = new DnsDiscoveryClient()
735
-
736
- try {
737
- const result = await client.discoverMainnetPeers()
738
- console.log(`Discovery successful: ${result.totalCount} peers`)
739
- } catch (error) {
740
- console.error('Discovery failed:', error.message)
741
-
742
- // Handle different error types
743
- if (error.message.includes('NoPeersFound')) {
744
- console.log('No peers found from any introducer')
745
- } else if (error.message.includes('ResolutionFailed')) {
746
- console.log('DNS resolution failed')
747
- }
748
- }
749
- ```
750
-
751
- ### Key Features
752
-
753
- - **Dual Stack Support**: Separate IPv4 and IPv6 peer lists
754
- - **Proper DNS Lookups**: Uses A records for IPv4, AAAA records for IPv6
755
- - **Built-in Networks**: Ready configurations for mainnet and testnet11
756
- - **Custom Introducers**: Support for any DNS introducers
757
- - **IPv6 URL Formatting**: Automatic bracket formatting for IPv6 addresses
758
- - **Type Safety**: Full TypeScript support with detailed type definitions
759
-
760
- ## TypeScript Usage
761
-
762
- ```typescript
763
- import {
764
- ChiaBlockListener,
765
- ChiaPeerPool,
766
- DnsDiscoveryClient,
767
- BlockReceivedEvent,
768
- PeerConnectedEvent,
769
- PeerDisconnectedEvent,
770
- NewPeakHeightEvent,
771
- DiscoveryResultJS,
772
- PeerAddressJS,
773
- AddressResult,
774
- CoinRecord,
775
- CoinSpend,
776
- initTracing,
777
- getEventTypes
778
- } from '@dignetwork/chia-block-listener'
779
-
780
- // Initialize tracing for debugging
781
- initTracing()
782
-
783
- // Create listener with proper typing
784
- const listener = new ChiaBlockListener()
785
-
786
- // Type-safe event handlers
787
- listener.on('blockReceived', (block: BlockReceivedEvent) => {
788
- console.log(`Block ${block.height} from peer ${block.peerId}`)
789
-
790
- // Process coin additions
791
- block.coinAdditions.forEach((coin: CoinRecord) => {
792
- console.log(`New coin: ${coin.amount} mojos`)
793
- })
794
-
795
- // Process coin spends
796
- block.coinSpends.forEach((spend: CoinSpend) => {
797
- console.log(`Spend: ${spend.coin.amount} mojos`)
798
- console.log(`Puzzle: ${spend.puzzleReveal}`)
799
- console.log(`Solution: ${spend.solution}`)
800
- })
801
- })
802
-
803
- listener.on('peerConnected', (peer: PeerConnectedEvent) => {
804
- console.log(`Connected: ${peer.peerId} at ${peer.host}:${peer.port}`)
805
- })
806
-
807
- listener.on('peerDisconnected', (peer: PeerDisconnectedEvent) => {
808
- console.log(`Disconnected: ${peer.peerId}`)
809
- if (peer.message) {
810
- console.log(`Reason: ${peer.message}`)
811
- }
812
- })
813
-
814
- // Connect to peers
815
- const mainnetPeer = listener.addPeer('localhost', 8444, 'mainnet')
816
- const testnetPeer = listener.addPeer('testnet-node.chia.net', 58444, 'testnet')
817
-
818
- // Get historical blocks
819
- async function getHistoricalBlocks() {
820
- try {
821
- const block = listener.getBlockByHeight(mainnetPeer, 1000000)
822
- console.log(`Block 1000000 hash: ${block.headerHash}`)
823
-
824
- const blocks = listener.getBlocksRange(mainnetPeer, 1000000, 1000010)
825
- console.log(`Retrieved ${blocks.length} blocks`)
826
- } catch (error) {
827
- console.error('Error getting blocks:', error)
828
- }
829
- }
830
-
831
- // Get event type constants
832
- const eventTypes = getEventTypes()
833
- console.log('Available events:', eventTypes)
834
-
835
- // TypeScript support for ChiaPeerPool
836
- const pool = new ChiaPeerPool()
837
-
838
- // Type-safe event handling
839
- pool.on('peerConnected', (event: PeerConnectedEvent) => {
840
- console.log(`Pool peer connected: ${event.peerId}`)
841
- })
842
-
843
- pool.on('newPeakHeight', (event: NewPeakHeightEvent) => {
844
- console.log(`New peak: ${event.oldPeak} ${event.newPeak}`)
845
- })
846
-
847
- // Async/await with proper typing
848
- async function fetchHistoricalData() {
849
- const block: BlockReceivedEvent = await pool.getBlockByHeight(5000000)
850
- const peers: string[] = await pool.getConnectedPeers()
851
- const peak: number | null = await pool.getPeakHeight()
852
-
853
- console.log(`Block ${block.height} has ${block.coinSpends.length} spends`)
854
- console.log(`Pool has ${peers.length} active peers`)
855
- console.log(`Current peak: ${peak || 'No peak yet'}`)
856
- }
857
-
858
- // TypeScript DNS Discovery
859
- async function typedDnsDiscovery(): Promise<void> {
860
- const client = new DnsDiscoveryClient()
861
-
862
- // Type-safe discovery
863
- const result: DiscoveryResultJS = await client.discoverMainnetPeers()
864
-
865
- // Access with full type safety
866
- result.ipv4Peers.forEach((peer: PeerAddressJS) => {
867
- console.log(`IPv4: ${peer.host}:${peer.port} (${peer.displayAddress})`)
868
- })
869
-
870
- result.ipv6Peers.forEach((peer: PeerAddressJS) => {
871
- console.log(`IPv6: ${peer.displayAddress} (isIpv6: ${peer.isIpv6})`)
872
- })
873
-
874
- // Individual resolution with types
875
- const ipv4Result: AddressResult = await client.resolveIpv4('dns-introducer.chia.net')
876
- const ipv6Result: AddressResult = await client.resolveIpv6('dns-introducer.chia.net')
877
-
878
- console.log(`IPv4 count: ${ipv4Result.count}, IPv6 count: ${ipv6Result.count}`)
879
- }
880
- ```
881
-
882
- ## Advanced Usage
883
-
884
- ### Monitoring Specific Transactions
885
-
886
- ```javascript
887
- // Monitor all coin spends for a specific puzzle hash
888
- listener.on('blockReceived', (block) => {
889
- const targetPuzzleHash = '0x1234...' // Your puzzle hash
890
-
891
- block.coinSpends.forEach((spend) => {
892
- if (spend.coin.puzzleHash === targetPuzzleHash) {
893
- console.log('Found spend for our puzzle!')
894
- console.log('Amount:', spend.coin.amount)
895
- console.log('Solution:', spend.solution)
896
- }
897
- })
898
- })
899
- ```
900
-
901
- ### Multiple Network Monitoring
902
-
903
- ```javascript
904
- // Monitor both mainnet and testnet
905
- const mainnetPeer = listener.addPeer('localhost', 8444, 'mainnet')
906
- const testnetPeer = listener.addPeer('localhost', 58444, 'testnet')
907
-
908
- listener.on('blockReceived', (block) => {
909
- if (block.peerId === mainnetPeer) {
910
- console.log(`Mainnet block ${block.height}`)
911
- } else if (block.peerId === testnetPeer) {
912
- console.log(`Testnet block ${block.height}`)
913
- }
914
- })
915
- ```
916
-
917
- ### Connection Management
918
-
919
- ```javascript
920
- // Automatic reconnection
921
- listener.on('peerDisconnected', (peer) => {
922
- console.log(`Lost connection to ${peer.peerId}, reconnecting...`)
923
-
924
- // Reconnect after 5 seconds
925
- setTimeout(() => {
926
- try {
927
- listener.addPeer(peer.host, peer.port, 'mainnet')
928
- console.log('Reconnected successfully')
929
- } catch (error) {
930
- console.error('Reconnection failed:', error)
931
- }
932
- }, 5000)
933
- })
934
- ```
935
-
936
- ## Utility Functions
937
-
938
- ### `initTracing(): void`
939
-
940
- Initializes the Rust tracing system for debugging purposes. Call this before creating any `ChiaBlockListener` instances if you want to see debug output.
941
-
942
- ### `getEventTypes(): EventTypes`
943
-
944
- Returns an object containing the event type constants:
945
-
946
- ```javascript
947
- const eventTypes = getEventTypes()
948
- console.log(eventTypes)
949
- // Output: { blockReceived: "blockReceived", peerConnected: "peerConnected", peerDisconnected: "peerDisconnected" }
950
- ```
951
-
952
-
953
-
954
- ## Performance Tips
955
-
956
- 1. **Use specific event handlers**: Only listen for the events you need
957
- 2. **Process blocks efficiently**: Avoid heavy computation in event handlers
958
- 3. **Manage connections**: Don't create too many peer connections simultaneously
959
- 4. **Handle errors gracefully**: Always wrap peer operations in try-catch blocks
960
-
961
- ## Development
962
-
963
- ### Prerequisites
964
-
965
- - [Rust](https://rustup.rs/) (latest stable)
966
- - [Node.js](https://nodejs.org/) (20 or later)
967
- - [npm](https://www.npmjs.com/)
968
-
969
- ### Setup
970
-
971
- ```bash
972
- # Clone and install dependencies
973
- git clone <repository-url>
974
- cd chia-block-listener
975
- npm install
976
-
977
- # Build the native module
978
- npm run build
979
-
980
- # Run tests
981
- npm test
982
- ```
983
-
984
- ### Project Structure
985
-
986
- ```
987
- chia-block-listener/
988
- ├── src/ # Rust source code
989
- │ ├── lib.rs # Main NAPI bindings
990
- │ ├── peer.rs # Peer connection management
991
- │ ├── protocol.rs # Chia protocol implementation
992
- │ ├── event_emitter.rs # Event system
993
- │ └── tls.rs # TLS connection handling
994
- ├── crate/ # Additional Rust crates
995
- │ └── chia-generator-parser/ # CLVM parser
996
- ├── __test__/ # Test suite
997
- ├── npm/ # Platform-specific binaries
998
- ├── .github/workflows/ # CI/CD pipeline
999
- ├── Cargo.toml # Rust configuration
1000
- ├── package.json # Node.js configuration
1001
- └── index.d.ts # TypeScript definitions
1002
- ```
1003
-
1004
- ## CI/CD & Publishing
1005
-
1006
- This project uses GitHub Actions for:
1007
- - Cross-platform builds (Windows, macOS, Linux)
1008
- - Multiple architectures (x64, ARM64)
1009
- - Automated testing on all platforms
1010
- - npm publishing based on git tags
1011
-
1012
- ## License
1013
-
1014
- MIT
1015
-
1016
- ## Contributing
1017
-
1018
- 1. Fork the repository
1019
- 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
1020
- 3. Make your changes and add tests
1021
- 4. Ensure all tests pass (`npm test`)
1022
- 5. Commit your changes (`git commit -m 'Add some amazing feature'`)
1023
- 6. Push to the branch (`git push origin feature/amazing-feature`)
1024
- 7. Open a Pull Request
1025
-
1026
- ## Support
1027
-
1028
- For issues and questions:
1029
- - GitHub Issues: [Report bugs or request features](https://github.com/DIG-Network/chia-block-listener/issues)
1030
- - Documentation: Check the TypeScript definitions in `index.d.ts`
1
+ # Chia Block Listener
2
+
3
+ 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.
4
+
5
+ ## Features
6
+
7
+ - **Real-time Block Monitoring**: Listen for new blocks as they're produced on the Chia network
8
+ - **Peer Management**: Connect to multiple Chia full nodes simultaneously
9
+ - **Automatic Failover**: Intelligent peer failover with automatic retry across multiple peers
10
+ - **Enhanced Error Handling**: Automatic disconnection of peers that refuse blocks or have protocol errors
11
+ - **Efficient Parsing**: Fast extraction of coin spends, additions, and removals from blocks
12
+ - **Event-Driven Architecture**: TypeScript-friendly event system with full type safety
13
+ - **Transaction Analysis**: Parse CLVM puzzles and solutions from coin spends
14
+ - **Historical Block Access**: Retrieve blocks by height or ranges with automatic load balancing
15
+ - **Connection Pool**: ChiaPeerPool provides automatic load balancing and rate limiting for historical queries
16
+ - **Peak Height Tracking**: Monitor blockchain sync progress across all connected peers
17
+ - **DNS Peer Discovery**: Automatic peer discovery using Chia network DNS introducers with IPv4/IPv6 support
18
+ - **Cross-platform Support**: Works on Windows, macOS, and Linux (x64 and ARM64)
19
+ - **TypeScript Support**: Complete TypeScript definitions with IntelliSense
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ npm install @dignetwork/chia-block-listener
25
+ ```
26
+
27
+ ## Quick Start
28
+
29
+ ```javascript
30
+ const { ChiaBlockListener, initTracing } = require('@dignetwork/chia-block-listener')
31
+
32
+ // Initialize tracing for debugging (optional)
33
+ initTracing()
34
+
35
+ // Create a new listener instance
36
+ const listener = new ChiaBlockListener()
37
+
38
+ // Listen for block events
39
+ listener.on('blockReceived', (block) => {
40
+ console.log(`New block received: ${block.height}`)
41
+ console.log(`Header hash: ${block.headerHash}`)
42
+ console.log(`Timestamp: ${new Date(block.timestamp * 1000)}`)
43
+ console.log(`Coin additions: ${block.coinAdditions.length}`)
44
+ console.log(`Coin removals: ${block.coinRemovals.length}`)
45
+ console.log(`Coin spends: ${block.coinSpends.length}`)
46
+ })
47
+
48
+ // Listen for peer connection events
49
+ listener.on('peerConnected', (peer) => {
50
+ console.log(`Connected to peer: ${peer.peerId} (${peer.host}:${peer.port})`)
51
+ })
52
+
53
+ listener.on('peerDisconnected', (peer) => {
54
+ console.log(`Disconnected from peer: ${peer.peerId}`)
55
+ if (peer.message) {
56
+ console.log(`Reason: ${peer.message}`)
57
+ }
58
+ })
59
+
60
+ // Connect to a Chia full node
61
+ const peerId = listener.addPeer('localhost', 8444, 'mainnet')
62
+ console.log(`Added peer: ${peerId}`)
63
+
64
+ // Keep the process running
65
+ process.on('SIGINT', () => {
66
+ console.log('Shutting down...')
67
+ listener.disconnectAllPeers()
68
+ process.exit(0)
69
+ })
70
+ ```
71
+ ## API Reference
72
+
73
+ ### ChiaBlockListener Class
74
+
75
+ #### Constructor
76
+
77
+ ```javascript
78
+ const listener = new ChiaBlockListener()
79
+ ```
80
+
81
+ Creates a new Chia block listener instance.
82
+
83
+ #### Methods
84
+
85
+ ##### `addPeer(host, port, networkId): string`
86
+
87
+ Connects to a Chia full node and starts listening for blocks.
88
+
89
+ **Parameters:**
90
+ - `host` (string): The hostname or IP address of the Chia node
91
+ - `port` (number): The port number (typically 8444 for mainnet)
92
+ - `networkId` (string): The network identifier ('mainnet', 'testnet', etc.)
93
+
94
+ **Returns:** A unique peer ID string for this connection
95
+
96
+ ##### `disconnectPeer(peerId): boolean`
97
+
98
+ Disconnects from a specific peer.
99
+
100
+ **Parameters:**
101
+ - `peerId` (string): The peer ID returned by `addPeer()`
102
+
103
+ **Returns:** `true` if the peer was successfully disconnected, `false` otherwise
104
+
105
+ ##### `disconnectAllPeers(): void`
106
+
107
+ Disconnects from all connected peers.
108
+
109
+ ##### `getConnectedPeers(): string[]`
110
+
111
+ Returns an array of currently connected peer IDs.
112
+
113
+ ##### `getBlockByHeight(peerId, height): BlockReceivedEvent`
114
+
115
+ Retrieves a specific block by its height from a connected peer.
116
+
117
+ **Parameters:**
118
+ - `peerId` (string): The peer ID to query
119
+ - `height` (number): The block height to retrieve
120
+
121
+ **Returns:** A `BlockReceivedEvent` object containing the block data
122
+
123
+ ##### `getBlocksRange(peerId, startHeight, endHeight): BlockReceivedEvent[]`
124
+
125
+ Retrieves a range of blocks from a connected peer.
126
+
127
+ **Parameters:**
128
+ - `peerId` (string): The peer ID to query
129
+ - `startHeight` (number): The starting block height (inclusive)
130
+ - `endHeight` (number): The ending block height (inclusive)
131
+
132
+ **Returns:** An array of `BlockReceivedEvent` objects
133
+
134
+ ### ChiaPeerPool Class
135
+
136
+ 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.
137
+
138
+ #### Constructor
139
+
140
+ ```javascript
141
+ const pool = new ChiaPeerPool()
142
+ ```
143
+
144
+ Creates a new peer pool instance with built-in rate limiting (500ms per peer).
145
+
146
+ #### Methods
147
+
148
+ ##### `addPeer(host, port, networkId): Promise<string>`
149
+
150
+ Adds a peer to the connection pool.
151
+
152
+ **Parameters:**
153
+ - `host` (string): The hostname or IP address of the Chia node
154
+ - `port` (number): The port number (typically 8444 for mainnet)
155
+ - `networkId` (string): The network identifier ('mainnet', 'testnet', etc.)
156
+
157
+ **Returns:** A Promise that resolves to a unique peer ID string
158
+
159
+ ##### `getBlockByHeight(height): Promise<BlockReceivedEvent>`
160
+
161
+ Retrieves a specific block by height using automatic peer selection and load balancing.
162
+
163
+ **Parameters:**
164
+ - `height` (number): The block height to retrieve
165
+
166
+ **Returns:** A Promise that resolves to a `BlockReceivedEvent` object
167
+
168
+ ##### `removePeer(peerId): Promise<boolean>`
169
+
170
+ Removes a peer from the pool.
171
+
172
+ **Parameters:**
173
+ - `peerId` (string): The peer ID to remove
174
+
175
+ **Returns:** A Promise that resolves to `true` if the peer was removed, `false` otherwise
176
+
177
+ ##### `shutdown(): Promise<void>`
178
+
179
+ Shuts down the pool and disconnects all peers.
180
+
181
+ ##### `getConnectedPeers(): Promise<string[]>`
182
+
183
+ Gets the list of currently connected peer IDs.
184
+
185
+ **Returns:** Array of peer ID strings (format: "host:port")
186
+
187
+ ##### `getPeakHeight(): Promise<number | null>`
188
+
189
+ Gets the highest blockchain peak height seen across all connected peers.
190
+
191
+ **Returns:** The highest peak height as a number, or null if no peaks have been received yet
192
+
193
+ ##### `on(event, callback): void`
194
+
195
+ Registers an event handler for pool events.
196
+
197
+ **Parameters:**
198
+ - `event` (string): The event name ('peerConnected' or 'peerDisconnected')
199
+ - `callback` (function): The event handler function
200
+
201
+ ##### `off(event, callback): void`
202
+
203
+ Removes an event handler.
204
+
205
+ **Parameters:**
206
+ - `event` (string): The event name to stop listening for
207
+
208
+ ### Events
209
+
210
+ The `ChiaBlockListener` emits the following events:
211
+
212
+ #### `blockReceived`
213
+
214
+ Fired when a new block is received from any connected peer.
215
+
216
+ **Callback:** `(event: BlockReceivedEvent) => void`
217
+
218
+ #### `peerConnected`
219
+
220
+ Fired when a connection to a peer is established.
221
+
222
+ **Callback:** `(event: PeerConnectedEvent) => void`
223
+
224
+ #### `peerDisconnected`
225
+
226
+ Fired when a peer connection is lost.
227
+
228
+ **Callback:** `(event: PeerDisconnectedEvent) => void`
229
+
230
+ ### ChiaPeerPool Events
231
+
232
+ The `ChiaPeerPool` emits the following events:
233
+
234
+ #### `peerConnected`
235
+
236
+ Fired when a peer is successfully added to the pool.
237
+
238
+ **Callback:** `(event: PeerConnectedEvent) => void`
239
+
240
+ #### `peerDisconnected`
241
+
242
+ Fired when a peer is removed from the pool or disconnects.
243
+
244
+ **Callback:** `(event: PeerDisconnectedEvent) => void`
245
+
246
+ #### `newPeakHeight`
247
+
248
+ Fired when a new highest blockchain peak is discovered.
249
+
250
+ **Callback:** `(event: NewPeakHeightEvent) => void`
251
+
252
+ ### DnsDiscoveryClient Class
253
+
254
+ The `DnsDiscoveryClient` provides automatic peer discovery using Chia network DNS introducers with full IPv4 and IPv6 support.
255
+
256
+ #### Constructor
257
+
258
+ ```javascript
259
+ const client = new DnsDiscoveryClient()
260
+ ```
261
+
262
+ Creates a new DNS discovery client instance.
263
+
264
+ #### Methods
265
+
266
+ ##### `discoverMainnetPeers(): Promise<DiscoveryResultJS>`
267
+
268
+ Discovers peers for Chia mainnet using built-in DNS introducers.
269
+
270
+ **Returns:** Promise resolving to discovery results with separate IPv4 and IPv6 peer lists
271
+
272
+ ##### `discoverTestnet11Peers(): Promise<DiscoveryResultJS>`
273
+
274
+ Discovers peers for Chia testnet11 using built-in DNS introducers.
275
+
276
+ **Returns:** Promise resolving to discovery results
277
+
278
+ ##### `discoverPeers(introducers, port): Promise<DiscoveryResultJS>`
279
+
280
+ Discovers peers using custom DNS introducers.
281
+
282
+ **Parameters:**
283
+ - `introducers` (string[]): Array of DNS introducer hostnames
284
+ - `port` (number): Default port for discovered peers
285
+
286
+ **Returns:** Promise resolving to discovery results
287
+
288
+ ##### `resolveIpv4(hostname): Promise<AddressResult>`
289
+
290
+ Resolves IPv4 addresses (A records) for a hostname.
291
+
292
+ **Parameters:**
293
+ - `hostname` (string): Hostname to resolve
294
+
295
+ **Returns:** Promise resolving to IPv4 addresses
296
+
297
+ ##### `resolveIpv6(hostname): Promise<AddressResult>`
298
+
299
+ Resolves IPv6 addresses (AAAA records) for a hostname.
300
+
301
+ **Parameters:**
302
+ - `hostname` (string): Hostname to resolve
303
+
304
+ **Returns:** Promise resolving to IPv6 addresses
305
+
306
+ ##### `resolveBoth(hostname, port): Promise<DiscoveryResultJS>`
307
+
308
+ Resolves both IPv4 and IPv6 addresses for a hostname.
309
+
310
+ **Parameters:**
311
+ - `hostname` (string): Hostname to resolve
312
+ - `port` (number): Port for the peer addresses
313
+
314
+ **Returns:** Promise resolving to discovery results
315
+
316
+ ### Event Data Types
317
+
318
+ #### `BlockReceivedEvent`
319
+
320
+ ```typescript
321
+ interface BlockReceivedEvent {
322
+ peerId: string // IP address of the peer that sent this block
323
+ height: number // Block height
324
+ weight: string // Block weight as string
325
+ headerHash: string // Block header hash (hex)
326
+ timestamp: number // Block timestamp (Unix time)
327
+ coinAdditions: CoinRecord[] // New coins created in this block
328
+ coinRemovals: CoinRecord[] // Coins spent in this block
329
+ coinSpends: CoinSpend[] // Detailed spend information
330
+ coinCreations: CoinRecord[] // Coins created by puzzles
331
+ hasTransactionsGenerator: boolean // Whether block has a generator
332
+ generatorSize: number // Size of the generator bytecode
333
+ }
334
+ ```
335
+
336
+ #### `PeerConnectedEvent`
337
+
338
+ ```typescript
339
+ interface PeerConnectedEvent {
340
+ peerId: string // Peer IP address
341
+ host: string // Peer hostname/IP
342
+ port: number // Peer port number
343
+ }
344
+ ```
345
+
346
+ #### `PeerDisconnectedEvent`
347
+
348
+ ```typescript
349
+ interface PeerDisconnectedEvent {
350
+ peerId: string // Peer IP address
351
+ host: string // Peer hostname/IP
352
+ port: number // Peer port number
353
+ message?: string // Optional disconnection reason
354
+ }
355
+ ```
356
+
357
+ #### `NewPeakHeightEvent`
358
+
359
+ ```typescript
360
+ interface NewPeakHeightEvent {
361
+ oldPeak: number | null // Previous highest peak (null if first peak)
362
+ newPeak: number // New highest peak height
363
+ peerId: string // Peer that discovered this peak
364
+ }
365
+ ```
366
+
367
+ #### `CoinRecord`
368
+
369
+ ```typescript
370
+ interface CoinRecord {
371
+ parentCoinInfo: string // Parent coin ID (hex)
372
+ puzzleHash: string // Puzzle hash (hex)
373
+ amount: string // Coin amount as string
374
+ }
375
+ ```
376
+
377
+ #### `CoinSpend`
378
+
379
+ ```typescript
380
+ interface CoinSpend {
381
+ coin: CoinRecord // The coin being spent
382
+ puzzleReveal: string // CLVM puzzle bytecode (hex)
383
+ solution: string // CLVM solution bytecode (hex)
384
+ offset: number // Offset in the generator bytecode
385
+ }
386
+ ```
387
+
388
+ #### `DiscoveryResultJS`
389
+
390
+ ```typescript
391
+ interface DiscoveryResultJS {
392
+ ipv4Peers: PeerAddressJS[] // IPv4 peer addresses
393
+ ipv6Peers: PeerAddressJS[] // IPv6 peer addresses
394
+ totalCount: number // Total peers found
395
+ }
396
+ ```
397
+
398
+ #### `PeerAddressJS`
399
+
400
+ ```typescript
401
+ interface PeerAddressJS {
402
+ host: string // IP address as string
403
+ port: number // Port number
404
+ isIpv6: boolean // Protocol indicator
405
+ displayAddress: string // Formatted for display/URLs
406
+ }
407
+ ```
408
+
409
+ #### `AddressResult`
410
+
411
+ ```typescript
412
+ interface AddressResult {
413
+ addresses: string[] // List of IP addresses
414
+ count: number // Number of addresses
415
+ }
416
+ ```
417
+
418
+ ## ChiaPeerPool Usage
419
+
420
+ 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.
421
+
422
+ ### Basic Usage
423
+
424
+ ```javascript
425
+ const { ChiaPeerPool, initTracing } = require('@dignetwork/chia-block-listener')
426
+
427
+ async function main() {
428
+ // Initialize tracing
429
+ initTracing()
430
+
431
+ // Create a peer pool
432
+ const pool = new ChiaPeerPool()
433
+
434
+ // Listen for pool events
435
+ pool.on('peerConnected', (event) => {
436
+ console.log(`Peer connected to pool: ${event.peerId}`)
437
+ })
438
+
439
+ pool.on('peerDisconnected', (event) => {
440
+ console.log(`Peer disconnected from pool: ${event.peerId}`)
441
+ })
442
+
443
+ pool.on('newPeakHeight', (event) => {
444
+ console.log(`New blockchain peak detected!`)
445
+ console.log(` Previous: ${event.oldPeak || 'None'}`)
446
+ console.log(` New: ${event.newPeak}`)
447
+ console.log(` Discovered by: ${event.peerId}`)
448
+ })
449
+
450
+ // Add multiple peers
451
+ await pool.addPeer('node1.chia.net', 8444, 'mainnet')
452
+ await pool.addPeer('node2.chia.net', 8444, 'mainnet')
453
+ await pool.addPeer('node3.chia.net', 8444, 'mainnet')
454
+
455
+ // Fetch blocks with automatic load balancing
456
+ const block1 = await pool.getBlockByHeight(5000000)
457
+ const block2 = await pool.getBlockByHeight(5000001)
458
+ const block3 = await pool.getBlockByHeight(5000002)
459
+
460
+ console.log(`Block ${block1.height}: ${block1.coinSpends.length} spends`)
461
+ console.log(`Block ${block2.height}: ${block2.coinSpends.length} spends`)
462
+ console.log(`Block ${block3.height}: ${block3.coinSpends.length} spends`)
463
+
464
+ // Shutdown the pool
465
+ await pool.shutdown()
466
+ }
467
+
468
+ main().catch(console.error)
469
+ ```
470
+
471
+ ### Advanced Pool Features
472
+
473
+ #### Rate Limiting
474
+
475
+ The pool automatically enforces a 500ms rate limit per peer for maximum performance while preventing node overload:
476
+
477
+ ```javascript
478
+ // Rapid requests are automatically queued and distributed
479
+ const promises = []
480
+ for (let i = 5000000; i < 5000100; i++) {
481
+ promises.push(pool.getBlockByHeight(i))
482
+ }
483
+
484
+ // All requests will be processed efficiently across all peers
485
+ // with automatic load balancing and rate limiting
486
+ const blocks = await Promise.all(promises)
487
+ console.log(`Retrieved ${blocks.length} blocks`)
488
+ ```
489
+
490
+ #### Automatic Failover and Error Handling
491
+
492
+ The pool provides robust error handling with automatic failover:
493
+
494
+ ```javascript
495
+ // The pool automatically handles various error scenarios:
496
+
497
+ // 1. Connection failures - automatically tries other peers
498
+ try {
499
+ const block = await pool.getBlockByHeight(5000000)
500
+ console.log(`Retrieved block ${block.height}`)
501
+ } catch (error) {
502
+ // If all peers fail, you'll get an error after all retry attempts
503
+ console.error('All peers failed:', error.message)
504
+ }
505
+
506
+ // 2. Protocol errors - peers that refuse blocks are automatically disconnected
507
+ pool.on('peerDisconnected', (event) => {
508
+ console.log(`Peer ${event.peerId} disconnected: ${event.reason}`)
509
+ // Reasons include: "Block request rejected", "Protocol error", "Connection timeout"
510
+ })
511
+
512
+ // 3. Automatic peer cleanup - problematic peers are removed from the pool
513
+ console.log('Active peers before:', await pool.getConnectedPeers())
514
+ await pool.getBlockByHeight(5000000) // May trigger peer removal
515
+ console.log('Active peers after:', await pool.getConnectedPeers())
516
+
517
+ // 4. Multiple retry attempts - tries up to 3 different peers per request
518
+ // This happens automatically and transparently
519
+ const block = await pool.getBlockByHeight(5000000) // Will try multiple peers if needed
520
+ ```
521
+
522
+ **Error Types Handled Automatically:**
523
+ - **Connection Errors**: Timeouts, network failures, WebSocket errors
524
+ - **Protocol Errors**: Block rejections, parsing failures, handshake failures
525
+ - **Peer Misbehavior**: Unexpected responses, invalid data formats
526
+
527
+ #### Dynamic Peer Management
528
+
529
+ ```javascript
530
+ // Monitor pool health
531
+ const peers = await pool.getConnectedPeers()
532
+ console.log(`Active peers in pool: ${peers.length}`)
533
+
534
+ // Remove underperforming peers
535
+ if (slowPeer) {
536
+ await pool.removePeer(slowPeer)
537
+ console.log('Removed slow peer from pool')
538
+ }
539
+
540
+ // Add new peers dynamically
541
+ if (peers.length < 3) {
542
+ await pool.addPeer('backup-node.chia.net', 8444, 'mainnet')
543
+ }
544
+ ```
545
+
546
+ #### Error Handling
547
+
548
+ ```javascript
549
+ try {
550
+ const block = await pool.getBlockByHeight(5000000)
551
+ console.log(`Retrieved block ${block.height}`)
552
+ } catch (error) {
553
+ console.error('Failed to retrieve block:', error)
554
+
555
+ // The pool will automatically try other peers
556
+ // You can also add more peers if needed
557
+ const peers = await pool.getConnectedPeers()
558
+ if (peers.length === 0) {
559
+ console.log('No peers available, adding new ones...')
560
+ await pool.addPeer('node1.chia.net', 8444, 'mainnet')
561
+ }
562
+ }
563
+ ```
564
+
565
+ #### Peak Height Tracking
566
+
567
+ ```javascript
568
+ // Monitor blockchain sync progress
569
+ const pool = new ChiaPeerPool()
570
+
571
+ // Track peak changes
572
+ let currentPeak = null
573
+ pool.on('newPeakHeight', (event) => {
574
+ currentPeak = event.newPeak
575
+ const progress = event.oldPeak
576
+ ? `+${event.newPeak - event.oldPeak} blocks`
577
+ : 'Initial peak'
578
+ console.log(`Peak update: ${event.newPeak} (${progress})`)
579
+ })
580
+
581
+ // Add peers
582
+ await pool.addPeer('node1.chia.net', 8444, 'mainnet')
583
+ await pool.addPeer('node2.chia.net', 8444, 'mainnet')
584
+
585
+ // Check current peak
586
+ const peak = await pool.getPeakHeight()
587
+ console.log(`Current highest peak: ${peak || 'None yet'}`)
588
+
589
+ // Fetch some blocks to trigger peak updates
590
+ await pool.getBlockByHeight(5000000)
591
+ await pool.getBlockByHeight(5100000)
592
+ await pool.getBlockByHeight(5200000)
593
+
594
+ // Monitor sync status
595
+ setInterval(async () => {
596
+ const peak = await pool.getPeakHeight()
597
+ if (peak) {
598
+ const estimatedCurrent = 5200000 + Math.floor((Date.now() / 1000 - 1700000000) / 18.75)
599
+ const syncPercentage = (peak / estimatedCurrent * 100).toFixed(2)
600
+ console.log(`Sync status: ${syncPercentage}% (peak: ${peak})`)
601
+ }
602
+ }, 60000) // Check every minute
603
+ ```
604
+
605
+ ### When to Use ChiaPeerPool vs ChiaBlockListener
606
+
607
+ - **Use ChiaPeerPool when:**
608
+ - You need to fetch historical blocks
609
+ - You want automatic load balancing across multiple peers
610
+ - You're making many block requests and need rate limiting
611
+ - You don't need real-time block notifications
612
+
613
+ - **Use ChiaBlockListener when:**
614
+ - You need real-time notifications of new blocks
615
+ - You want to monitor the blockchain as it grows
616
+ - You need to track specific addresses or puzzle hashes in real-time
617
+ - You're building applications that react to blockchain events
618
+
619
+ Both classes can be used together in the same application for different purposes.
620
+
621
+ ## DNS Discovery Usage
622
+
623
+ The `DnsDiscoveryClient` enables automatic discovery of Chia network peers using DNS introducers, with full support for both IPv4 and IPv6 addresses.
624
+
625
+ ### Basic DNS Discovery
626
+
627
+ ```javascript
628
+ const { DnsDiscoveryClient, initTracing } = require('@dignetwork/chia-block-listener')
629
+
630
+ async function discoverPeers() {
631
+ // Initialize tracing
632
+ initTracing()
633
+
634
+ // Create DNS discovery client
635
+ const client = new DnsDiscoveryClient()
636
+
637
+ // Discover mainnet peers
638
+ const result = await client.discoverMainnetPeers()
639
+
640
+ console.log(`Found ${result.totalCount} total peers:`)
641
+ console.log(` IPv4 peers: ${result.ipv4Peers.length}`)
642
+ console.log(` IPv6 peers: ${result.ipv6Peers.length}`)
643
+
644
+ // Use with peer connections
645
+ for (const peer of result.ipv4Peers.slice(0, 3)) {
646
+ console.log(`IPv4 peer: ${peer.displayAddress}`)
647
+ // peer.host and peer.port can be used with addPeer()
648
+ }
649
+
650
+ for (const peer of result.ipv6Peers.slice(0, 3)) {
651
+ console.log(`IPv6 peer: ${peer.displayAddress}`) // [2001:db8::1]:8444
652
+ // IPv6 addresses are properly formatted with brackets
653
+ }
654
+ }
655
+
656
+ discoverPeers().catch(console.error)
657
+ ```
658
+
659
+ ### Integration with Peer Pool
660
+
661
+ ```javascript
662
+ const { ChiaPeerPool, DnsDiscoveryClient } = require('@dignetwork/chia-block-listener')
663
+
664
+ async function setupPoolWithDnsDiscovery() {
665
+ const pool = new ChiaPeerPool()
666
+ const discovery = new DnsDiscoveryClient()
667
+
668
+ // Discover peers automatically
669
+ const peers = await discovery.discoverMainnetPeers()
670
+
671
+ // Add discovered peers to pool (both IPv4 and IPv6)
672
+ const allPeers = [...peers.ipv4Peers, ...peers.ipv6Peers]
673
+ for (const peer of allPeers.slice(0, 5)) {
674
+ await pool.addPeer(peer.host, peer.port, 'mainnet')
675
+ console.log(`Added peer: ${peer.displayAddress}`)
676
+ }
677
+
678
+ // Now use the pool for block retrieval
679
+ const block = await pool.getBlockByHeight(5000000)
680
+ console.log(`Retrieved block ${block.height}`)
681
+
682
+ await pool.shutdown()
683
+ }
684
+
685
+ setupPoolWithDnsDiscovery().catch(console.error)
686
+ ```
687
+
688
+ ### Custom DNS Introducers
689
+
690
+ ```javascript
691
+ const client = new DnsDiscoveryClient()
692
+
693
+ // Use custom introducers
694
+ const customIntroducers = [
695
+ 'seeder.dexie.space',
696
+ 'chia.hoffmang.com'
697
+ ]
698
+
699
+ const result = await client.discoverPeers(customIntroducers, 8444)
700
+ console.log(`Found ${result.totalCount} peers from custom introducers`)
701
+ ```
702
+
703
+ ### Individual DNS Resolution
704
+
705
+ ```javascript
706
+ const client = new DnsDiscoveryClient()
707
+ const hostname = 'dns-introducer.chia.net'
708
+
709
+ // Resolve specific protocols
710
+ try {
711
+ const ipv4 = await client.resolveIpv4(hostname)
712
+ console.log(`IPv4 addresses: ${ipv4.addresses.join(', ')}`)
713
+ } catch (error) {
714
+ console.log(`IPv4 resolution failed: ${error.message}`)
715
+ }
716
+
717
+ try {
718
+ const ipv6 = await client.resolveIpv6(hostname)
719
+ console.log(`IPv6 addresses: ${ipv6.addresses.join(', ')}`)
720
+ } catch (error) {
721
+ console.log(`IPv6 resolution failed: ${error.message}`)
722
+ }
723
+
724
+ // Or resolve both at once
725
+ const both = await client.resolveBoth(hostname, 8444)
726
+ console.log(`Combined: ${both.totalCount} addresses`)
727
+ ```
728
+
729
+
730
+
731
+ ### Error Handling
732
+
733
+ ```javascript
734
+ const client = new DnsDiscoveryClient()
735
+
736
+ try {
737
+ const result = await client.discoverMainnetPeers()
738
+ console.log(`Discovery successful: ${result.totalCount} peers`)
739
+ } catch (error) {
740
+ console.error('Discovery failed:', error.message)
741
+
742
+ // Handle different error types
743
+ if (error.message.includes('NoPeersFound')) {
744
+ console.log('No peers found from any introducer')
745
+ } else if (error.message.includes('ResolutionFailed')) {
746
+ console.log('DNS resolution failed')
747
+ }
748
+ }
749
+ ```
750
+
751
+ ### Key Features
752
+
753
+ - **Dual Stack Support**: Separate IPv4 and IPv6 peer lists
754
+ - **Proper DNS Lookups**: Uses A records for IPv4, AAAA records for IPv6
755
+ - **Built-in Networks**: Ready configurations for mainnet and testnet11
756
+ - **Custom Introducers**: Support for any DNS introducers
757
+ - **IPv6 URL Formatting**: Automatic bracket formatting for IPv6 addresses
758
+ - **Type Safety**: Full TypeScript support with detailed type definitions
759
+
760
+ ## ChiaBlockParser Usage
761
+
762
+ 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.
763
+
764
+ ### Basic Block Parsing
765
+
766
+ ```javascript
767
+ const { ChiaBlockParser, initTracing } = require('@dignetwork/chia-block-listener')
768
+
769
+ async function parseBlockData() {
770
+ // Initialize tracing
771
+ initTracing()
772
+
773
+ // Create a block parser instance
774
+ const parser = new ChiaBlockParser()
775
+
776
+ // Parse a block from hex string
777
+ const blockHex = "your_full_block_hex_data_here"
778
+ const parsedBlock = parser.parseFullBlockFromHex(blockHex)
779
+
780
+ console.log(`Parsed block ${parsedBlock.height}:`)
781
+ console.log(` Header hash: ${parsedBlock.headerHash}`)
782
+ console.log(` Weight: ${parsedBlock.weight}`)
783
+ console.log(` Timestamp: ${new Date(parsedBlock.timestamp * 1000)}`)
784
+ console.log(` Coin additions: ${parsedBlock.coinAdditions.length}`)
785
+ console.log(` Coin removals: ${parsedBlock.coinRemovals.length}`)
786
+ console.log(` Coin spends: ${parsedBlock.coinSpends.length}`)
787
+ console.log(` Has generator: ${parsedBlock.hasTransactionsGenerator}`)
788
+
789
+ // Access detailed coin spend information
790
+ parsedBlock.coinSpends.forEach((spend, index) => {
791
+ console.log(` Spend ${index + 1}:`)
792
+ console.log(` Coin: ${spend.coin.amount} mojos`)
793
+ console.log(` Puzzle hash: ${spend.coin.puzzleHash}`)
794
+ console.log(` Puzzle reveal: ${spend.puzzleReveal.substring(0, 100)}...`)
795
+ console.log(` Solution: ${spend.solution.substring(0, 100)}...`)
796
+ console.log(` Created coins: ${spend.createdCoins.length}`)
797
+ })
798
+ }
799
+
800
+ parseBlockData().catch(console.error)
801
+ ```
802
+
803
+ ### ChiaBlockParser Class
804
+
805
+ #### Constructor
806
+
807
+ ```javascript
808
+ const parser = new ChiaBlockParser()
809
+ ```
810
+
811
+ Creates a new block parser instance with access to the full Rust parsing engine.
812
+
813
+ #### Methods
814
+
815
+ ##### `parseFullBlockFromBytes(blockBytes): ParsedBlockJs`
816
+
817
+ Parses a FullBlock from raw bytes.
818
+
819
+ **Parameters:**
820
+ - `blockBytes` (Buffer): The serialized FullBlock data
821
+
822
+ **Returns:** A `ParsedBlockJs` object containing all parsed block information
823
+
824
+ ```javascript
825
+ const fs = require('fs')
826
+ const blockData = fs.readFileSync('block.bin')
827
+ const parsedBlock = parser.parseFullBlockFromBytes(blockData)
828
+ ```
829
+
830
+ ##### `parseFullBlockFromHex(blockHex): ParsedBlockJs`
831
+
832
+ Parses a FullBlock from a hex-encoded string.
833
+
834
+ **Parameters:**
835
+ - `blockHex` (string): The hex-encoded FullBlock data
836
+
837
+ **Returns:** A `ParsedBlockJs` object containing all parsed block information
838
+
839
+ ```javascript
840
+ const blockHex = "deadbeef..." // Your hex-encoded block data
841
+ const parsedBlock = parser.parseFullBlockFromHex(blockHex)
842
+ ```
843
+
844
+ ##### `extractGeneratorFromBlockBytes(blockBytes): string | null`
845
+
846
+ Extracts only the transactions generator from a block without full parsing.
847
+
848
+ **Parameters:**
849
+ - `blockBytes` (Buffer): The serialized FullBlock data
850
+
851
+ **Returns:** Hex-encoded generator bytecode or `null` if no generator exists
852
+
853
+ ```javascript
854
+ const blockData = fs.readFileSync('block.bin')
855
+ const generator = parser.extractGeneratorFromBlockBytes(blockData)
856
+ if (generator) {
857
+ console.log(`Generator size: ${generator.length / 2} bytes`)
858
+ }
859
+ ```
860
+
861
+ ##### `getHeightAndTxStatusFromBlockBytes(blockBytes): BlockHeightInfoJs`
862
+
863
+ Quickly extracts basic block information without full parsing.
864
+
865
+ **Parameters:**
866
+ - `blockBytes` (Buffer): The serialized FullBlock data
867
+
868
+ **Returns:** A `BlockHeightInfoJs` object with height and transaction status
869
+
870
+ ```javascript
871
+ const blockData = fs.readFileSync('block.bin')
872
+ const info = parser.getHeightAndTxStatusFromBlockBytes(blockData)
873
+ console.log(`Block ${info.height}, has transactions: ${info.isTransactionBlock}`)
874
+ ```
875
+
876
+ ##### `parseBlockInfoFromBytes(blockBytes): GeneratorBlockInfoJs`
877
+
878
+ Extracts generator-related block metadata.
879
+
880
+ **Parameters:**
881
+ - `blockBytes` (Buffer): The serialized FullBlock data
882
+
883
+ **Returns:** A `GeneratorBlockInfoJs` object with generator metadata
884
+
885
+ ```javascript
886
+ const blockData = fs.readFileSync('block.bin')
887
+ const blockInfo = parser.parseBlockInfoFromBytes(blockData)
888
+ console.log(`Previous hash: ${blockInfo.prevHeaderHash}`)
889
+ console.log(`Generator refs: ${blockInfo.transactionsGeneratorRefList.length}`)
890
+ ```
891
+
892
+ ### Advanced Parsing Features
893
+
894
+ #### Batch Block Processing
895
+
896
+ ```javascript
897
+ const parser = new ChiaBlockParser()
898
+ const fs = require('fs')
899
+ const path = require('path')
900
+
901
+ // Process multiple block files
902
+ const blockFiles = fs.readdirSync('./blocks/').filter(f => f.endsWith('.bin'))
903
+
904
+ const stats = {
905
+ totalBlocks: 0,
906
+ totalSpends: 0,
907
+ totalCoins: 0,
908
+ generatorBlocks: 0
909
+ }
910
+
911
+ for (const filename of blockFiles) {
912
+ const blockData = fs.readFileSync(path.join('./blocks/', filename))
913
+
914
+ try {
915
+ const parsed = parser.parseFullBlockFromBytes(blockData)
916
+
917
+ stats.totalBlocks++
918
+ stats.totalSpends += parsed.coinSpends.length
919
+ stats.totalCoins += parsed.coinAdditions.length
920
+
921
+ if (parsed.hasTransactionsGenerator) {
922
+ stats.generatorBlocks++
923
+ }
924
+
925
+ console.log(`Processed block ${parsed.height} with ${parsed.coinSpends.length} spends`)
926
+
927
+ } catch (error) {
928
+ console.error(`Failed to parse ${filename}:`, error.message)
929
+ }
930
+ }
931
+
932
+ console.log('\nBatch processing complete:')
933
+ console.log(` Blocks processed: ${stats.totalBlocks}`)
934
+ console.log(` Total spends: ${stats.totalSpends}`)
935
+ console.log(` Total coins: ${stats.totalCoins}`)
936
+ console.log(` Generator blocks: ${stats.generatorBlocks}`)
937
+ ```
938
+
939
+ #### Generator Analysis
940
+
941
+ ```javascript
942
+ const parser = new ChiaBlockParser()
943
+
944
+ function analyzeBlockGenerator(blockHex) {
945
+ // Parse the full block
946
+ const parsed = parser.parseFullBlockFromHex(blockHex)
947
+
948
+ if (!parsed.hasTransactionsGenerator) {
949
+ console.log('Block has no generator')
950
+ return
951
+ }
952
+
953
+ // Extract just the generator for analysis
954
+ const blockBytes = Buffer.from(blockHex, 'hex')
955
+ const generator = parser.extractGeneratorFromBlockBytes(blockBytes)
956
+
957
+ console.log(`Generator Analysis for Block ${parsed.height}:`)
958
+ console.log(` Generator size: ${parsed.generatorSize} bytes`)
959
+ console.log(` Hex length: ${generator.length} characters`)
960
+ console.log(` Coin spends extracted: ${parsed.coinSpends.length}`)
961
+ console.log(` Coins created: ${parsed.coinCreations.length}`)
962
+
963
+ // Analyze coin spends
964
+ parsed.coinSpends.forEach((spend, i) => {
965
+ console.log(` Spend ${i + 1}:`)
966
+ console.log(` Amount: ${spend.coin.amount} mojos`)
967
+ console.log(` Puzzle size: ${spend.puzzleReveal.length / 2} bytes`)
968
+ console.log(` Solution size: ${spend.solution.length / 2} bytes`)
969
+ console.log(` Creates ${spend.createdCoins.length} new coins`)
970
+ })
971
+ }
972
+
973
+ // Example usage
974
+ const blockHex = "your_generator_block_hex"
975
+ analyzeBlockGenerator(blockHex)
976
+ ```
977
+
978
+ #### Integration with Other Components
979
+
980
+ ```javascript
981
+ const { ChiaBlockParser, ChiaPeerPool, DnsDiscoveryClient } = require('@dignetwork/chia-block-listener')
982
+
983
+ async function integratedBlockAnalysis() {
984
+ // Set up components
985
+ const parser = new ChiaBlockParser()
986
+ const pool = new ChiaPeerPool()
987
+ const discovery = new DnsDiscoveryClient()
988
+
989
+ // Discover and connect to peers
990
+ const peers = await discovery.discoverMainnetPeers()
991
+ for (const peer of peers.ipv4Peers.slice(0, 3)) {
992
+ await pool.addPeer(peer.host, peer.port, 'mainnet')
993
+ }
994
+
995
+ // Fetch blocks and parse with enhanced detail
996
+ const heights = [5000000, 5000001, 5000002]
997
+
998
+ for (const height of heights) {
999
+ // Get block using peer pool
1000
+ const blockEvent = await pool.getBlockByHeight(height)
1001
+
1002
+ // For more detailed analysis, you can also parse with ChiaBlockParser
1003
+ // if you have access to the raw block bytes
1004
+ console.log(`Block ${height}:`)
1005
+ console.log(` From peer: ${blockEvent.peerId}`)
1006
+ console.log(` Coin spends: ${blockEvent.coinSpends.length}`)
1007
+ console.log(` Has generator: ${blockEvent.hasTransactionsGenerator}`)
1008
+
1009
+ // Analyze puzzle patterns
1010
+ const puzzleHashes = new Set()
1011
+ blockEvent.coinSpends.forEach(spend => {
1012
+ puzzleHashes.add(spend.coin.puzzleHash)
1013
+ })
1014
+ console.log(` Unique puzzle hashes: ${puzzleHashes.size}`)
1015
+ }
1016
+
1017
+ await pool.shutdown()
1018
+ }
1019
+
1020
+ integratedBlockAnalysis().catch(console.error)
1021
+ ```
1022
+
1023
+ ### Data Types
1024
+
1025
+ #### `ParsedBlockJs`
1026
+
1027
+ ```typescript
1028
+ interface ParsedBlockJs {
1029
+ height: number // Block height
1030
+ weight: string // Block weight as string
1031
+ headerHash: string // Block header hash (hex)
1032
+ timestamp?: number // Block timestamp (Unix time)
1033
+ coinAdditions: CoinInfoJs[] // New coins created
1034
+ coinRemovals: CoinInfoJs[] // Coins spent
1035
+ coinSpends: CoinSpendInfoJs[] // Detailed spend information
1036
+ coinCreations: CoinInfoJs[] // Coins created by spends
1037
+ hasTransactionsGenerator: boolean // Whether block has generator
1038
+ generatorSize?: number // Generator size in bytes
1039
+ }
1040
+ ```
1041
+
1042
+ #### `CoinInfoJs`
1043
+
1044
+ ```typescript
1045
+ interface CoinInfoJs {
1046
+ parentCoinInfo: string // Parent coin ID (hex)
1047
+ puzzleHash: string // Puzzle hash (hex)
1048
+ amount: string // Amount as string (to avoid JS precision issues)
1049
+ }
1050
+ ```
1051
+
1052
+ #### `CoinSpendInfoJs`
1053
+
1054
+ ```typescript
1055
+ interface CoinSpendInfoJs {
1056
+ coin: CoinInfoJs // The coin being spent
1057
+ puzzleReveal: string // CLVM puzzle bytecode (hex)
1058
+ solution: string // CLVM solution bytecode (hex)
1059
+ realData: boolean // Whether this is real transaction data
1060
+ parsingMethod: string // Method used for parsing
1061
+ offset: number // Offset in generator bytecode
1062
+ createdCoins: CoinInfoJs[] // Coins created by this spend
1063
+ }
1064
+ ```
1065
+
1066
+ #### `GeneratorBlockInfoJs`
1067
+
1068
+ ```typescript
1069
+ interface GeneratorBlockInfoJs {
1070
+ prevHeaderHash: string // Previous block hash (hex)
1071
+ transactionsGenerator?: string // Generator bytecode (hex)
1072
+ transactionsGeneratorRefList: number[] // Referenced block heights
1073
+ }
1074
+ ```
1075
+
1076
+ #### `BlockHeightInfoJs`
1077
+
1078
+ ```typescript
1079
+ interface BlockHeightInfoJs {
1080
+ height: number // Block height
1081
+ isTransactionBlock: boolean // Whether block contains transactions
1082
+ }
1083
+ ```
1084
+
1085
+ ### When to Use ChiaBlockParser
1086
+
1087
+ - **Use ChiaBlockParser when:**
1088
+ - You have raw block data from external sources
1089
+ - You need detailed CLVM puzzle and solution analysis
1090
+ - You're implementing custom block processing logic
1091
+ - You need to extract generators for analysis
1092
+ - You want fine-grained control over parsing
1093
+
1094
+ - **Use with ChiaPeerPool when:**
1095
+ - You need both block retrieval and detailed parsing
1096
+ - You're analyzing historical blocks in detail
1097
+ - You want to combine network access with parsing
1098
+
1099
+ - **Use with ChiaBlockListener when:**
1100
+ - You're processing real-time blocks with custom logic
1101
+ - You need both live monitoring and detailed parsing
1102
+
1103
+ The `ChiaBlockParser` complements the other classes by providing the lowest-level access to the Chia block parsing engine, enabling sophisticated analysis and processing workflows.
1104
+
1105
+ ## TypeScript Usage
1106
+
1107
+ ```typescript
1108
+ import {
1109
+ ChiaBlockListener,
1110
+ ChiaPeerPool,
1111
+ ChiaBlockParser,
1112
+ DnsDiscoveryClient,
1113
+ BlockReceivedEvent,
1114
+ PeerConnectedEvent,
1115
+ PeerDisconnectedEvent,
1116
+ NewPeakHeightEvent,
1117
+ DiscoveryResultJS,
1118
+ PeerAddressJS,
1119
+ AddressResult,
1120
+ CoinRecord,
1121
+ CoinSpend,
1122
+ ParsedBlockJs,
1123
+ CoinInfoJs,
1124
+ CoinSpendInfoJs,
1125
+ GeneratorBlockInfoJs,
1126
+ BlockHeightInfoJs,
1127
+ initTracing,
1128
+ getEventTypes
1129
+ } from '@dignetwork/chia-block-listener'
1130
+
1131
+ // Initialize tracing for debugging
1132
+ initTracing()
1133
+
1134
+ // Create listener with proper typing
1135
+ const listener = new ChiaBlockListener()
1136
+
1137
+ // Type-safe event handlers
1138
+ listener.on('blockReceived', (block: BlockReceivedEvent) => {
1139
+ console.log(`Block ${block.height} from peer ${block.peerId}`)
1140
+
1141
+ // Process coin additions
1142
+ block.coinAdditions.forEach((coin: CoinRecord) => {
1143
+ console.log(`New coin: ${coin.amount} mojos`)
1144
+ })
1145
+
1146
+ // Process coin spends
1147
+ block.coinSpends.forEach((spend: CoinSpend) => {
1148
+ console.log(`Spend: ${spend.coin.amount} mojos`)
1149
+ console.log(`Puzzle: ${spend.puzzleReveal}`)
1150
+ console.log(`Solution: ${spend.solution}`)
1151
+ })
1152
+ })
1153
+
1154
+ listener.on('peerConnected', (peer: PeerConnectedEvent) => {
1155
+ console.log(`Connected: ${peer.peerId} at ${peer.host}:${peer.port}`)
1156
+ })
1157
+
1158
+ listener.on('peerDisconnected', (peer: PeerDisconnectedEvent) => {
1159
+ console.log(`Disconnected: ${peer.peerId}`)
1160
+ if (peer.message) {
1161
+ console.log(`Reason: ${peer.message}`)
1162
+ }
1163
+ })
1164
+
1165
+ // Connect to peers
1166
+ const mainnetPeer = listener.addPeer('localhost', 8444, 'mainnet')
1167
+ const testnetPeer = listener.addPeer('testnet-node.chia.net', 58444, 'testnet')
1168
+
1169
+ // Get historical blocks
1170
+ async function getHistoricalBlocks() {
1171
+ try {
1172
+ const block = listener.getBlockByHeight(mainnetPeer, 1000000)
1173
+ console.log(`Block 1000000 hash: ${block.headerHash}`)
1174
+
1175
+ const blocks = listener.getBlocksRange(mainnetPeer, 1000000, 1000010)
1176
+ console.log(`Retrieved ${blocks.length} blocks`)
1177
+ } catch (error) {
1178
+ console.error('Error getting blocks:', error)
1179
+ }
1180
+ }
1181
+
1182
+ // Get event type constants
1183
+ const eventTypes = getEventTypes()
1184
+ console.log('Available events:', eventTypes)
1185
+
1186
+ // TypeScript support for ChiaPeerPool
1187
+ const pool = new ChiaPeerPool()
1188
+
1189
+ // Type-safe event handling
1190
+ pool.on('peerConnected', (event: PeerConnectedEvent) => {
1191
+ console.log(`Pool peer connected: ${event.peerId}`)
1192
+ })
1193
+
1194
+ pool.on('newPeakHeight', (event: NewPeakHeightEvent) => {
1195
+ console.log(`New peak: ${event.oldPeak} → ${event.newPeak}`)
1196
+ })
1197
+
1198
+ // Async/await with proper typing
1199
+ async function fetchHistoricalData() {
1200
+ const block: BlockReceivedEvent = await pool.getBlockByHeight(5000000)
1201
+ const peers: string[] = await pool.getConnectedPeers()
1202
+ const peak: number | null = await pool.getPeakHeight()
1203
+
1204
+ console.log(`Block ${block.height} has ${block.coinSpends.length} spends`)
1205
+ console.log(`Pool has ${peers.length} active peers`)
1206
+ console.log(`Current peak: ${peak || 'No peak yet'}`)
1207
+ }
1208
+
1209
+ // TypeScript DNS Discovery
1210
+ async function typedDnsDiscovery(): Promise<void> {
1211
+ const client = new DnsDiscoveryClient()
1212
+
1213
+ // Type-safe discovery
1214
+ const result: DiscoveryResultJS = await client.discoverMainnetPeers()
1215
+
1216
+ // Access with full type safety
1217
+ result.ipv4Peers.forEach((peer: PeerAddressJS) => {
1218
+ console.log(`IPv4: ${peer.host}:${peer.port} (${peer.displayAddress})`)
1219
+ })
1220
+
1221
+ result.ipv6Peers.forEach((peer: PeerAddressJS) => {
1222
+ console.log(`IPv6: ${peer.displayAddress} (isIpv6: ${peer.isIpv6})`)
1223
+ })
1224
+
1225
+ // Individual resolution with types
1226
+ const ipv4Result: AddressResult = await client.resolveIpv4('dns-introducer.chia.net')
1227
+ const ipv6Result: AddressResult = await client.resolveIpv6('dns-introducer.chia.net')
1228
+
1229
+ console.log(`IPv4 count: ${ipv4Result.count}, IPv6 count: ${ipv6Result.count}`)
1230
+ }
1231
+
1232
+ // TypeScript ChiaBlockParser
1233
+ async function typedBlockParsing(): Promise<void> {
1234
+ const parser = new ChiaBlockParser()
1235
+
1236
+ // Type-safe block parsing from hex
1237
+ const blockHex = "your_block_hex_data"
1238
+ const parsedBlock: ParsedBlockJs = parser.parseFullBlockFromHex(blockHex)
1239
+
1240
+ console.log(`Parsed block ${parsedBlock.height}:`)
1241
+ console.log(` Header hash: ${parsedBlock.headerHash}`)
1242
+ console.log(` Weight: ${parsedBlock.weight}`)
1243
+ console.log(` Coin additions: ${parsedBlock.coinAdditions.length}`)
1244
+ console.log(` Coin spends: ${parsedBlock.coinSpends.length}`)
1245
+ console.log(` Has generator: ${parsedBlock.hasTransactionsGenerator}`)
1246
+
1247
+ // Type-safe access to coin spend details
1248
+ parsedBlock.coinSpends.forEach((spend: CoinSpendInfoJs) => {
1249
+ const coin: CoinInfoJs = spend.coin
1250
+ console.log(`Spend: ${coin.amount} mojos from ${coin.puzzleHash}`)
1251
+ console.log(` Puzzle reveal size: ${spend.puzzleReveal.length / 2} bytes`)
1252
+ console.log(` Solution size: ${spend.solution.length / 2} bytes`)
1253
+ console.log(` Creates ${spend.createdCoins.length} new coins`)
1254
+ console.log(` Real data: ${spend.realData}`)
1255
+ console.log(` Parsing method: ${spend.parsingMethod}`)
1256
+ })
1257
+
1258
+ // Type-safe generator extraction
1259
+ const blockBytes = Buffer.from(blockHex, 'hex')
1260
+ const generator: string | null = parser.extractGeneratorFromBlockBytes(blockBytes)
1261
+ if (generator) {
1262
+ console.log(`Generator found: ${generator.length / 2} bytes`)
1263
+ }
1264
+
1265
+ // Type-safe height and tx status
1266
+ const heightInfo: BlockHeightInfoJs = parser.getHeightAndTxStatusFromBlockBytes(blockBytes)
1267
+ console.log(`Block ${heightInfo.height}, is transaction block: ${heightInfo.isTransactionBlock}`)
1268
+
1269
+ // Type-safe block info extraction
1270
+ const blockInfo: GeneratorBlockInfoJs = parser.parseBlockInfoFromBytes(blockBytes)
1271
+ console.log(`Previous hash: ${blockInfo.prevHeaderHash}`)
1272
+ console.log(`Generator refs: ${blockInfo.transactionsGeneratorRefList.length}`)
1273
+ if (blockInfo.transactionsGenerator) {
1274
+ console.log(`Generator size: ${blockInfo.transactionsGenerator.length / 2} bytes`)
1275
+ }
1276
+ }
1277
+ ```
1278
+
1279
+ ## Advanced Usage
1280
+
1281
+ ### Monitoring Specific Transactions
1282
+
1283
+ ```javascript
1284
+ // Monitor all coin spends for a specific puzzle hash
1285
+ listener.on('blockReceived', (block) => {
1286
+ const targetPuzzleHash = '0x1234...' // Your puzzle hash
1287
+
1288
+ block.coinSpends.forEach((spend) => {
1289
+ if (spend.coin.puzzleHash === targetPuzzleHash) {
1290
+ console.log('Found spend for our puzzle!')
1291
+ console.log('Amount:', spend.coin.amount)
1292
+ console.log('Solution:', spend.solution)
1293
+ }
1294
+ })
1295
+ })
1296
+ ```
1297
+
1298
+ ### Multiple Network Monitoring
1299
+
1300
+ ```javascript
1301
+ // Monitor both mainnet and testnet
1302
+ const mainnetPeer = listener.addPeer('localhost', 8444, 'mainnet')
1303
+ const testnetPeer = listener.addPeer('localhost', 58444, 'testnet')
1304
+
1305
+ listener.on('blockReceived', (block) => {
1306
+ if (block.peerId === mainnetPeer) {
1307
+ console.log(`Mainnet block ${block.height}`)
1308
+ } else if (block.peerId === testnetPeer) {
1309
+ console.log(`Testnet block ${block.height}`)
1310
+ }
1311
+ })
1312
+ ```
1313
+
1314
+ ### Connection Management
1315
+
1316
+ ```javascript
1317
+ // Automatic reconnection
1318
+ listener.on('peerDisconnected', (peer) => {
1319
+ console.log(`Lost connection to ${peer.peerId}, reconnecting...`)
1320
+
1321
+ // Reconnect after 5 seconds
1322
+ setTimeout(() => {
1323
+ try {
1324
+ listener.addPeer(peer.host, peer.port, 'mainnet')
1325
+ console.log('Reconnected successfully')
1326
+ } catch (error) {
1327
+ console.error('Reconnection failed:', error)
1328
+ }
1329
+ }, 5000)
1330
+ })
1331
+ ```
1332
+
1333
+ ## Utility Functions
1334
+
1335
+ ### `initTracing(): void`
1336
+
1337
+ Initializes the Rust tracing system for debugging purposes. Call this before creating any `ChiaBlockListener` instances if you want to see debug output.
1338
+
1339
+ ### `getEventTypes(): EventTypes`
1340
+
1341
+ Returns an object containing the event type constants:
1342
+
1343
+ ```javascript
1344
+ const eventTypes = getEventTypes()
1345
+ console.log(eventTypes)
1346
+ // Output: { blockReceived: "blockReceived", peerConnected: "peerConnected", peerDisconnected: "peerDisconnected" }
1347
+ ```
1348
+
1349
+
1350
+
1351
+ ## Performance Tips
1352
+
1353
+ 1. **Use specific event handlers**: Only listen for the events you need
1354
+ 2. **Process blocks efficiently**: Avoid heavy computation in event handlers
1355
+ 3. **Manage connections**: Don't create too many peer connections simultaneously
1356
+ 4. **Handle errors gracefully**: Always wrap peer operations in try-catch blocks
1357
+
1358
+ ## Development
1359
+
1360
+ ### Prerequisites
1361
+
1362
+ - [Rust](https://rustup.rs/) (latest stable)
1363
+ - [Node.js](https://nodejs.org/) (20 or later)
1364
+ - [npm](https://www.npmjs.com/)
1365
+
1366
+ ### Setup
1367
+
1368
+ ```bash
1369
+ # Clone and install dependencies
1370
+ git clone <repository-url>
1371
+ cd chia-block-listener
1372
+ npm install
1373
+
1374
+ # Build the native module
1375
+ npm run build
1376
+
1377
+ # Run tests
1378
+ npm test
1379
+ ```
1380
+
1381
+ ### Project Structure
1382
+
1383
+ ```
1384
+ chia-block-listener/
1385
+ ├── src/ # Rust source code
1386
+ │ ├── lib.rs # Main NAPI bindings
1387
+ │ ├── peer.rs # Peer connection management
1388
+ │ ├── protocol.rs # Chia protocol implementation
1389
+ │ ├── event_emitter.rs # Event system
1390
+ │ └── tls.rs # TLS connection handling
1391
+ ├── crate/ # Additional Rust crates
1392
+ │ └── chia-generator-parser/ # CLVM parser
1393
+ ├── __test__/ # Test suite
1394
+ ├── npm/ # Platform-specific binaries
1395
+ ├── .github/workflows/ # CI/CD pipeline
1396
+ ├── Cargo.toml # Rust configuration
1397
+ ├── package.json # Node.js configuration
1398
+ └── index.d.ts # TypeScript definitions
1399
+ ```
1400
+
1401
+ ## CI/CD & Publishing
1402
+
1403
+ This project uses GitHub Actions for:
1404
+ - Cross-platform builds (Windows, macOS, Linux)
1405
+ - Multiple architectures (x64, ARM64)
1406
+ - Automated testing on all platforms
1407
+ - npm publishing based on git tags
1408
+
1409
+ ## License
1410
+
1411
+ MIT
1412
+
1413
+ ## Contributing
1414
+
1415
+ 1. Fork the repository
1416
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
1417
+ 3. Make your changes and add tests
1418
+ 4. Ensure all tests pass (`npm test`)
1419
+ 5. Commit your changes (`git commit -m 'Add some amazing feature'`)
1420
+ 6. Push to the branch (`git push origin feature/amazing-feature`)
1421
+ 7. Open a Pull Request
1422
+
1423
+ ## Support
1424
+
1425
+ For issues and questions:
1426
+ - GitHub Issues: [Report bugs or request features](https://github.com/DIG-Network/chia-block-listener/issues)
1427
+ - Documentation: Check the TypeScript definitions in `index.d.ts`
1031
1428
  - Examples: See the `examples/` directory for more usage examples