@xdc.org/interaction-detector 1.0.0

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.
Files changed (74) hide show
  1. package/README.md +767 -0
  2. package/dist/checkpoint/checkpoint.d.ts +32 -0
  3. package/dist/checkpoint/checkpoint.d.ts.map +1 -0
  4. package/dist/checkpoint/checkpoint.js +68 -0
  5. package/dist/checkpoint/checkpoint.js.map +1 -0
  6. package/dist/decoder/abi-registry.d.ts +49 -0
  7. package/dist/decoder/abi-registry.d.ts.map +1 -0
  8. package/dist/decoder/abi-registry.js +88 -0
  9. package/dist/decoder/abi-registry.js.map +1 -0
  10. package/dist/decoder/event-decoder.d.ts +31 -0
  11. package/dist/decoder/event-decoder.d.ts.map +1 -0
  12. package/dist/decoder/event-decoder.js +142 -0
  13. package/dist/decoder/event-decoder.js.map +1 -0
  14. package/dist/explorer/explorer-client.d.ts +65 -0
  15. package/dist/explorer/explorer-client.d.ts.map +1 -0
  16. package/dist/explorer/explorer-client.js +164 -0
  17. package/dist/explorer/explorer-client.js.map +1 -0
  18. package/dist/explorer/rate-limiter.d.ts +31 -0
  19. package/dist/explorer/rate-limiter.d.ts.map +1 -0
  20. package/dist/explorer/rate-limiter.js +79 -0
  21. package/dist/explorer/rate-limiter.js.map +1 -0
  22. package/dist/index.d.ts +24 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +26 -0
  25. package/dist/index.js.map +1 -0
  26. package/dist/rpc/rpc-client.d.ts +70 -0
  27. package/dist/rpc/rpc-client.d.ts.map +1 -0
  28. package/dist/rpc/rpc-client.js +136 -0
  29. package/dist/rpc/rpc-client.js.map +1 -0
  30. package/dist/rpc/ws-manager.d.ts +27 -0
  31. package/dist/rpc/ws-manager.d.ts.map +1 -0
  32. package/dist/rpc/ws-manager.js +161 -0
  33. package/dist/rpc/ws-manager.js.map +1 -0
  34. package/dist/scanner/block-scanner.d.ts +45 -0
  35. package/dist/scanner/block-scanner.d.ts.map +1 -0
  36. package/dist/scanner/block-scanner.js +180 -0
  37. package/dist/scanner/block-scanner.js.map +1 -0
  38. package/dist/tracer/call-tree-parser.d.ts +25 -0
  39. package/dist/tracer/call-tree-parser.d.ts.map +1 -0
  40. package/dist/tracer/call-tree-parser.js +80 -0
  41. package/dist/tracer/call-tree-parser.js.map +1 -0
  42. package/dist/tracer/state-diff-parser.d.ts +13 -0
  43. package/dist/tracer/state-diff-parser.d.ts.map +1 -0
  44. package/dist/tracer/state-diff-parser.js +70 -0
  45. package/dist/tracer/state-diff-parser.js.map +1 -0
  46. package/dist/tracer/transaction-tracer.d.ts +52 -0
  47. package/dist/tracer/transaction-tracer.d.ts.map +1 -0
  48. package/dist/tracer/transaction-tracer.js +107 -0
  49. package/dist/tracer/transaction-tracer.js.map +1 -0
  50. package/dist/types/index.d.ts +262 -0
  51. package/dist/types/index.d.ts.map +1 -0
  52. package/dist/types/index.js +8 -0
  53. package/dist/types/index.js.map +1 -0
  54. package/dist/utils/address.d.ts +29 -0
  55. package/dist/utils/address.d.ts.map +1 -0
  56. package/dist/utils/address.js +49 -0
  57. package/dist/utils/address.js.map +1 -0
  58. package/dist/utils/format.d.ts +20 -0
  59. package/dist/utils/format.d.ts.map +1 -0
  60. package/dist/utils/format.js +53 -0
  61. package/dist/utils/format.js.map +1 -0
  62. package/dist/utils/logger.d.ts +16 -0
  63. package/dist/utils/logger.d.ts.map +1 -0
  64. package/dist/utils/logger.js +58 -0
  65. package/dist/utils/logger.js.map +1 -0
  66. package/dist/watcher/contract-watcher.d.ts +56 -0
  67. package/dist/watcher/contract-watcher.d.ts.map +1 -0
  68. package/dist/watcher/contract-watcher.js +353 -0
  69. package/dist/watcher/contract-watcher.js.map +1 -0
  70. package/dist/watcher/log-poller.d.ts +24 -0
  71. package/dist/watcher/log-poller.d.ts.map +1 -0
  72. package/dist/watcher/log-poller.js +82 -0
  73. package/dist/watcher/log-poller.js.map +1 -0
  74. package/package.json +57 -0
package/README.md ADDED
@@ -0,0 +1,767 @@
1
+ # XDC Interaction Detector
2
+
3
+ Standalone TypeScript library for detecting **all on-chain contract interactions** on XDC and EVM-compatible chains.
4
+
5
+ Combines: **events** + **direct calls** + **internal calls** + **transaction tracing** into a single unified detection engine.
6
+
7
+ **Framework-agnostic. Zero runtime dependencies on any specific backend.** Just detection — you decide what to do with the results.
8
+
9
+ ## Table of Contents
10
+
11
+ - [XDC Interaction Detector](#xdc-interaction-detector)
12
+ - [Table of Contents](#table-of-contents)
13
+ - [Features](#features)
14
+ - [Installation](#installation)
15
+ - [Quick Start](#quick-start)
16
+ - [Core Classes](#core-classes)
17
+ - [1. ContractWatcher — Real-Time Monitoring](#1-contractwatcher--real-time-monitoring)
18
+ - [2. BlockScanner — Historical Queries](#2-blockscanner--historical-queries)
19
+ - [3. TransactionTracer — Deep Transaction Analysis](#3-transactiontracer--deep-transaction-analysis)
20
+ - [Explorer API Client](#explorer-api-client)
21
+ - [Event Decoder \& ABI Registry](#event-decoder--abi-registry)
22
+ - [Checkpoint Persistence](#checkpoint-persistence)
23
+ - [Utility Functions](#utility-functions)
24
+ - [Configuration Reference](#configuration-reference)
25
+ - [InteractionDetectorConfig (ContractWatcher)](#interactiondetectorconfig-contractwatcher)
26
+ - [ContractConfig](#contractconfig)
27
+ - [ExplorerConfig](#explorerconfig)
28
+ - [PollingConfig](#pollingconfig)
29
+ - [WsConfig](#wsconfig)
30
+ - [CheckpointConfig](#checkpointconfig)
31
+ - [XDC-Specific Notes](#xdc-specific-notes)
32
+ - [Architecture](#architecture)
33
+ - [Examples](#examples)
34
+ - [Development](#development)
35
+ - [License](#license)
36
+
37
+ ---
38
+
39
+ ## Features
40
+
41
+ - **Real-time event monitoring** — WebSocket push + HTTP polling with automatic deduplication
42
+ - **Historical block scanning** — Query any block range with auto-chunked fetching
43
+ - **Transaction tracing** — Full call trees, state diffs, and balance changes via `debug_traceTransaction`
44
+ - **Explorer API integration** — Direct & internal transaction collection via XDCScan / Etherscan-compatible APIs
45
+ - **XDC-first, EVM-compatible** — Handles XDC's non-standard ABI encoding, `xdc` address prefix, and 100-block range limit
46
+ - **Pluggable checkpoints** — Memory, file, or custom backends for restart persistence
47
+ - **Zero framework dependency** — Works in any Node.js environment: Express, Fastify, serverless functions, CLI scripts, background workers
48
+
49
+ ---
50
+
51
+ ## Installation
52
+
53
+ ```bash
54
+ npm install xdc-interaction-detector
55
+ ```
56
+
57
+ **Dependencies:** `ethers` v6, `axios`, `ws` — all installed automatically.
58
+
59
+ ---
60
+
61
+ ## Quick Start
62
+
63
+ ```typescript
64
+ import { ContractWatcher } from 'xdc-interaction-detector';
65
+
66
+ const watcher = new ContractWatcher({
67
+ // RPC endpoints
68
+ rpcUrl: 'https://rpc.xinfin.network',
69
+ wsUrl: 'wss://ws.xinfin.network', // optional — enables real-time push
70
+ chainId: 50, // XDC Mainnet
71
+
72
+ // Contracts to monitor
73
+ contracts: [
74
+ {
75
+ address: '0x0000000000000000000000000000000000000088',
76
+ abi: ['event Vote(address indexed _voter, address indexed _candidate, uint256 _cap)'],
77
+ name: 'XDCValidator',
78
+ },
79
+ ],
80
+
81
+ // Explorer API — enables direct & internal call detection
82
+ explorer: {
83
+ apiUrl: 'https://xdc.blocksscan.io/api',
84
+ apiKey: 'YOUR_API_KEY', // optional — higher rate limits
85
+ rateLimitPerSec: 5,
86
+ },
87
+
88
+ // Checkpoint — survive restarts
89
+ checkpoint: { backend: 'file', path: './checkpoints' },
90
+ });
91
+
92
+ // Decoded contract events (Transfer, Swap, Vote, etc.)
93
+ watcher.on('event', event => {
94
+ console.log(`${event.name} from ${event.contractName} at block ${event.blockNumber}`);
95
+ console.log(' Args:', event.args);
96
+ });
97
+
98
+ // ALL interactions (events + direct + internal + delegate + static calls)
99
+ watcher.on('interaction', interaction => {
100
+ console.log(`[${interaction.type}] tx ${interaction.txHash} (source: ${interaction.source})`);
101
+ });
102
+
103
+ await watcher.start();
104
+ ```
105
+
106
+ ---
107
+
108
+ ## Core Classes
109
+
110
+ ### 1. ContractWatcher — Real-Time Monitoring
111
+
112
+ Watches one or more contract addresses for all interactions in real-time using three complementary detection paths:
113
+
114
+ | Path | Method | What it catches | Latency |
115
+ | ------------ | --------------------------- | ----------------------------------- | ------------- |
116
+ | WebSocket | `eth_subscribe("logs")` | Contract events | ~2 seconds |
117
+ | HTTP Polling | `eth_getLogs` | Contract events (reliable fallback) | 15–30 seconds |
118
+ | Explorer API | `txlist` + `txlistinternal` | Direct calls + internal calls | 30–60 seconds |
119
+
120
+ **Creating a watcher:**
121
+
122
+ ```typescript
123
+ import { ContractWatcher } from 'xdc-interaction-detector';
124
+
125
+ const watcher = new ContractWatcher({
126
+ // ─── Required ───────────────────────────────────────────
127
+ rpcUrl: 'https://rpc.xinfin.network',
128
+ contracts: [
129
+ {
130
+ address: '0xContractAddress', // 0x or xdc prefix both work
131
+ abi: [...], // optional — enables event decoding
132
+ name: 'MyDeFiPool', // optional — human label
133
+ },
134
+ // Watch multiple contracts simultaneously
135
+ { address: '0xAnotherContract', name: 'Governance' },
136
+ ],
137
+
138
+ // ─── Optional: WebSocket (real-time push) ───────────────
139
+ wsUrl: 'wss://ws.xinfin.network',
140
+ chainId: 50, // default: 50 (XDC Mainnet)
141
+
142
+ // ─── Optional: Explorer API (direct + internal calls) ───
143
+ explorer: {
144
+ apiUrl: 'https://xdc.blocksscan.io/api',
145
+ apiKey: 'YOUR_XDCSCAN_API_KEY', // optional — get higher rate limits
146
+ chainId: 50, // required for Etherscan v2
147
+ rateLimitPerSec: 5, // default: 5 req/s
148
+ pollIntervalMs: 60_000, // how often to check explorer (default: 60s)
149
+ },
150
+
151
+ // ─── Optional: Polling tuning ───────────────────────────
152
+ polling: {
153
+ intervalMs: 15_000, // default: 30s
154
+ maxBlockRange: 100, // XDC limit — don't change unless using another chain
155
+ concurrency: 3, // parallel chunk fetches
156
+ },
157
+
158
+ // ─── Optional: WebSocket tuning ─────────────────────────
159
+ ws: {
160
+ enabled: true, // default: true
161
+ reconnectDelayBaseMs: 5_000,
162
+ reconnectDelayMaxMs: 30_000,
163
+ maxReconnectAttempts: 20,
164
+ heartbeatIntervalMs: 60_000,
165
+ heartbeatTimeoutMs: 10_000,
166
+ },
167
+
168
+ // ─── Optional: Checkpoint persistence ───────────────────
169
+ checkpoint: {
170
+ backend: 'file', // 'memory' | 'file' | 'custom'
171
+ path: './checkpoints',
172
+ },
173
+
174
+ // ─── Optional: Fallback RPCs ────────────────────────────
175
+ fallbackRpcUrls: ['https://rpc1.xinfin.network'],
176
+
177
+ // ─── Optional: Log level ────────────────────────────────
178
+ logLevel: 'info', // 'debug' | 'info' | 'warn' | 'error' | 'silent'
179
+ });
180
+ ```
181
+
182
+ **Listening for events:**
183
+
184
+ ```typescript
185
+ // ── Decoded contract events ──────────────────────────────────
186
+ watcher.on('event', event => {
187
+ console.log(event.contract); // '0xcontractaddress'
188
+ console.log(event.contractName); // 'MyDeFiPool'
189
+ console.log(event.name); // 'Swap'
190
+ console.log(event.args); // { sender: '0x...', amount0In: 1000n, ... }
191
+ console.log(event.blockNumber); // 75123456
192
+ console.log(event.txHash); // '0xabc...'
193
+ console.log(event.logIndex); // 3
194
+ console.log(event.timestamp); // 1711792800 (Unix seconds)
195
+ console.log(event.signature); // 'Swap(address,uint256,uint256,address)'
196
+ console.log(event.raw); // { topics: [...], data: '0x...' }
197
+ });
198
+
199
+ // ── ALL interactions (unified type) ──────────────────────────
200
+ watcher.on('interaction', interaction => {
201
+ console.log(interaction.type); // 'event' | 'direct_call' | 'internal_call' | 'delegate_call' | 'static_call'
202
+ console.log(interaction.source); // 'rpc_logs' | 'ws_logs' | 'explorer_txlist' | 'explorer_internal'
203
+ console.log(interaction.txHash);
204
+ console.log(interaction.from); // sender (when available)
205
+ console.log(interaction.to); // contract address
206
+ console.log(interaction.methodId); // '0xa9059cbb' (for direct/internal calls)
207
+ console.log(interaction.methodName); // 'transfer(address,uint256)' (if ABI found)
208
+ console.log(interaction.value); // native value in wei
209
+ console.log(interaction.isError); // true if call reverted
210
+ });
211
+
212
+ // ── Raw logs (for contracts without ABI) ─────────────────────
213
+ watcher.on('log', log => {
214
+ console.log(log.topics[0]); // event signature hash
215
+ console.log(log.data); // raw encoded data
216
+ });
217
+
218
+ // ── Lifecycle events ─────────────────────────────────────────
219
+ watcher.on('connected', info => {
220
+ console.log(`Connected via ${info.type}: ${info.url}`);
221
+ });
222
+
223
+ watcher.on('disconnected', info => {
224
+ console.log(`Disconnected: ${info.reason}`);
225
+ });
226
+
227
+ watcher.on('checkpoint', blockNumber => {
228
+ console.log(`Checkpoint saved at block ${blockNumber}`);
229
+ });
230
+
231
+ watcher.on('error', err => {
232
+ console.error(`Error: ${err.message}`);
233
+ });
234
+ ```
235
+
236
+ **Starting and stopping:**
237
+
238
+ ```typescript
239
+ // Start monitoring
240
+ await watcher.start();
241
+
242
+ // ... later, graceful shutdown
243
+ await watcher.stop();
244
+ ```
245
+
246
+ **Deduplication:** Events detected by both WebSocket and polling are automatically deduplicated using `txHash + logIndex` composite keys. This prevents double-counting when both detection paths catch the same event.
247
+
248
+ ---
249
+
250
+ ### 2. BlockScanner — Historical Queries
251
+
252
+ Scans a block range for all events and interactions involving a contract. Automatically handles XDC's 100-block `eth_getLogs` limit with chunked parallel fetching.
253
+
254
+ **Scanning for events:**
255
+
256
+ ```typescript
257
+ import { BlockScanner } from 'xdc-interaction-detector';
258
+
259
+ const scanner = new BlockScanner({
260
+ rpcUrl: 'https://rpc.xinfin.network',
261
+ maxBlockRange: 100, // default: 100 (XDC limit)
262
+ concurrency: 3, // parallel chunk fetches
263
+ logLevel: 'info',
264
+
265
+ // Optional: explorer for direct + internal tx enrichment
266
+ explorer: {
267
+ apiUrl: 'https://xdc.blocksscan.io/api',
268
+ apiKey: 'YOUR_API_KEY',
269
+ },
270
+ });
271
+
272
+ // ── Scan for decoded events ──────────────────────────────────
273
+ const events = await scanner.getEvents({
274
+ address: '0x0000000000000000000000000000000000000088',
275
+ abi: [
276
+ 'event Vote(address indexed _voter, address indexed _candidate, uint256 _cap)',
277
+ 'event Unvote(address indexed _voter, address indexed _candidate, uint256 _cap)',
278
+ ],
279
+ fromBlock: 75_000_000,
280
+ toBlock: 75_100_000,
281
+
282
+ // Optional: filter by specific event name
283
+ eventFilter: 'Vote',
284
+
285
+ // Optional: progress callback for large scans
286
+ onProgress: (processed, total) => {
287
+ console.log(`${Math.round((processed / total) * 100)}% complete`);
288
+ },
289
+ });
290
+
291
+ // events is DecodedEvent[]
292
+ for (const event of events) {
293
+ console.log(`[block ${event.blockNumber}] ${event.name}`, event.args);
294
+ }
295
+ ```
296
+
297
+ **Scanning for all interactions (events + explorer txlist + txlistinternal):**
298
+
299
+ ```typescript
300
+ const interactions = await scanner.getInteractions({
301
+ address: '0x0000000000000000000000000000000000000088',
302
+ abi: [...],
303
+ fromBlock: 75_000_000,
304
+ toBlock: 75_100_000,
305
+ });
306
+
307
+ // interactions is ContractInteraction[]
308
+ for (const i of interactions) {
309
+ console.log(`[${i.type}] block ${i.blockNumber} tx ${i.txHash}`);
310
+ if (i.type === 'event') console.log(' Event:', i.event?.name, i.event?.args);
311
+ if (i.type === 'direct_call') console.log(' Method:', i.methodName);
312
+ if (i.type === 'internal_call') console.log(' From:', i.from);
313
+ }
314
+ ```
315
+
316
+ ---
317
+
318
+ ### 3. TransactionTracer — Deep Transaction Analysis
319
+
320
+ Traces a single transaction to extract the full execution story: call tree, state diffs, balance changes, and all events.
321
+
322
+ > **Note:** Requires an RPC endpoint with the `debug` namespace enabled. For historical transactions, an archive node is required. For recent/current blocks, a regular full node works.
323
+
324
+ **Full trace:**
325
+
326
+ ```typescript
327
+ import { TransactionTracer } from 'xdc-interaction-detector';
328
+
329
+ const tracer = new TransactionTracer({
330
+ rpcUrl: 'https://archive-rpc.xinfin.network', // must support debug_traceTransaction
331
+ timeoutMs: 120_000, // tracing can be slow on complex txs
332
+ logLevel: 'info',
333
+ });
334
+
335
+ // Register ABIs for method name decoding in call trees
336
+ tracer.registerABI(
337
+ '0xContractAddress',
338
+ ['function swap(uint256,uint256,address,bytes)', 'function transfer(address,uint256)'],
339
+ 'MyDEX',
340
+ );
341
+
342
+ const result = await tracer.trace('0xTransactionHash');
343
+ ```
344
+
345
+ **Using the trace result:**
346
+
347
+ ```typescript
348
+ // ── Call Tree ────────────────────────────────────────────────
349
+ // Nested structure showing every CALL, STATICCALL, DELEGATECALL
350
+ console.log(result.callTree.type); // 'CALL'
351
+ console.log(result.callTree.from); // '0xSender'
352
+ console.log(result.callTree.to); // '0xContract'
353
+ console.log(result.callTree.method); // 'swap(uint256,uint256,address,bytes)' (if ABI registered)
354
+ console.log(result.callTree.value); // '0x0' (native value)
355
+ console.log(result.callTree.calls); // CallTreeNode[] — nested sub-calls
356
+ // Example nested call:
357
+ // CALL 0xRouter → swap(...)
358
+ // CALL 0xPool → mint(...)
359
+ // STATICCALL 0xOracle → getPrice()
360
+ // CALL 0xTokenA → transfer(...)
361
+
362
+ // ── State Diffs ──────────────────────────────────────────────
363
+ // Storage slot changes (before/after for each modified slot)
364
+ for (const diff of result.stateDiffs) {
365
+ console.log(`${diff.contract} slot ${diff.slot}`);
366
+ console.log(` before: ${diff.before}`);
367
+ console.log(` after: ${diff.after}`);
368
+ }
369
+
370
+ // ── Balance Changes ──────────────────────────────────────────
371
+ // Native token balance changes per address
372
+ for (const change of result.balanceChanges) {
373
+ console.log(`${change.address}: ${change.delta} wei (${change.token})`);
374
+ }
375
+
376
+ // ── Events ───────────────────────────────────────────────────
377
+ // All events emitted during execution (decoded if ABI registered)
378
+ for (const event of result.events) {
379
+ console.log(`${event.name} from ${event.contract}`, event.args);
380
+ }
381
+
382
+ // ── Metadata ─────────────────────────────────────────────────
383
+ console.log(result.gasUsed); // 234567
384
+ console.log(result.involvedContracts); // ['0x...', '0x...', '0x...']
385
+ console.log(result.blockNumber); // 75123456
386
+ ```
387
+
388
+ **Partial traces (lighter weight):**
389
+
390
+ ```typescript
391
+ // Just the call tree (no state diffs)
392
+ const callTree = await tracer.traceCallTree('0xTxHash');
393
+
394
+ // Just the state diffs + balance changes (no call tree)
395
+ const { stateDiffs, balanceChanges } = await tracer.traceStateDiffs('0xTxHash');
396
+ ```
397
+
398
+ **Call tree utilities:**
399
+
400
+ ```typescript
401
+ import { flattenCallTree, findCallsTo, extractInvolvedContracts } from 'xdc-interaction-detector';
402
+
403
+ // Flatten the nested tree into a linear array
404
+ const allCalls = flattenCallTree(result.callTree);
405
+ console.log(`Total calls in execution: ${allCalls.length}`);
406
+
407
+ // Find all calls targeting a specific contract
408
+ const tokenCalls = findCallsTo(result.callTree, '0xTokenAddress');
409
+
410
+ // Get all unique addresses involved
411
+ const addresses = extractInvolvedContracts(result.callTree);
412
+ ```
413
+
414
+ ---
415
+
416
+ ## Explorer API Client
417
+
418
+ Standalone Etherscan-compatible API client. Works with XDCScan, Etherscan v2, BSCScan, PolygonScan, and any Etherscan-compatible explorer.
419
+
420
+ ```typescript
421
+ import { ExplorerClient } from 'xdc-interaction-detector';
422
+
423
+ const explorer = new ExplorerClient({
424
+ apiUrl: 'https://xdc.blocksscan.io/api', // XDCScan
425
+ // apiUrl: 'https://api.etherscan.io/v2/api', // Etherscan v2 (80+ chains)
426
+ // apiUrl: 'https://api.bscscan.com/api', // BSCScan
427
+ apiKey: 'YOUR_API_KEY', // optional — higher rate limits
428
+ chainId: 50, // required for Etherscan v2
429
+ rateLimitPerSec: 5, // built-in token-bucket rate limiter
430
+ });
431
+ ```
432
+
433
+ **Available methods:**
434
+
435
+ ```typescript
436
+ // ── Transaction lists ────────────────────────────────────────
437
+ // Get external transactions to/from a contract
438
+ const txs = await explorer.getTransactions('0xAddress', {
439
+ startBlock: 75_000_000,
440
+ endBlock: 75_100_000,
441
+ sort: 'asc', // or 'desc'
442
+ });
443
+
444
+ // Get internal transactions (CALL, DELEGATECALL from other contracts)
445
+ const internalTxs = await explorer.getInternalTransactions('0xAddress', {
446
+ startBlock: 75_000_000,
447
+ endBlock: 75_100_000,
448
+ });
449
+
450
+ // Get internal transactions within a specific tx
451
+ const txInternals = await explorer.getInternalTransactionsByTx('0xTxHash');
452
+
453
+ // ── Events ───────────────────────────────────────────────────
454
+ // Get event logs with optional topic filter
455
+ const logs = await explorer.getLogs('0xAddress', {
456
+ fromBlock: 75_000_000,
457
+ toBlock: 75_100_000,
458
+ topic0: '0xddf252ad...', // optional — filter by event signature
459
+ });
460
+
461
+ // ── Contract ABI ─────────────────────────────────────────────
462
+ // Auto-fetch verified contract ABI
463
+ const abi = await explorer.getContractABI('0xAddress');
464
+ if (abi) {
465
+ console.log(`Fetched ABI with ${abi.length} entries`);
466
+ }
467
+
468
+ // ── Token transfers ──────────────────────────────────────────
469
+ const transfers = await explorer.getTokenTransfers('0xAddress', {
470
+ startBlock: 75_000_000,
471
+ endBlock: 75_100_000,
472
+ });
473
+
474
+ // ── Balance ──────────────────────────────────────────────────
475
+ const balance = await explorer.getBalance('0xAddress');
476
+
477
+ // ── Cleanup ──────────────────────────────────────────────────
478
+ explorer.destroy(); // Cleans up rate limiter timers
479
+ ```
480
+
481
+ **Explorer compatibility:**
482
+
483
+ | Explorer | Base URL | Chain | Free Rate Limit |
484
+ | ---------------- | --------------------------------- | ------------------------ | --------------- |
485
+ | **XDCScan** | `https://xdc.blocksscan.io/api` | XDC Mainnet (50) | 5 req/s |
486
+ | **Etherscan v2** | `https://api.etherscan.io/v2/api` | 80+ chains via `chainId` | 5 req/s |
487
+ | **BSCScan** | `https://api.bscscan.com/api` | BSC (56) | 5 req/s |
488
+ | **PolygonScan** | `https://api.polygonscan.com/api` | Polygon (137) | 5 req/s |
489
+ | **Custom** | Any Etherscan-compatible URL | Any EVM chain | Configurable |
490
+
491
+ ---
492
+
493
+ ## Event Decoder & ABI Registry
494
+
495
+ The decoder handles both standard Solidity ABI encoding and XDC's non-standard encoding (where all params are packed into the `data` field).
496
+
497
+ ```typescript
498
+ import { AbiRegistry, EventDecoder } from 'xdc-interaction-detector';
499
+
500
+ // ── Register ABIs ────────────────────────────────────────────
501
+ const registry = new AbiRegistry();
502
+
503
+ // Register manually
504
+ registry.register(
505
+ '0xContractAddress',
506
+ [
507
+ 'event Transfer(address indexed from, address indexed to, uint256 value)',
508
+ 'event Approval(address indexed owner, address indexed spender, uint256 value)',
509
+ ],
510
+ 'MyToken',
511
+ );
512
+
513
+ // Register from a JSON ABI array
514
+ registry.register('0xAnotherContract', require('./MyContract.json').abi, 'MyContract');
515
+
516
+ // Auto-fetch from block explorer (verified contracts only)
517
+ const explorer = new ExplorerClient({ apiUrl: 'https://xdc.blocksscan.io/api' });
518
+ const success = await registry.registerFromExplorer('0xVerifiedContract', explorer, 'VerifiedToken');
519
+ console.log(success ? 'ABI fetched!' : 'Contract not verified');
520
+
521
+ // ── Query the registry ───────────────────────────────────────
522
+ registry.has('0xContractAddress'); // true
523
+ registry.getName('0xContractAddress'); // 'MyToken'
524
+ registry.getAddresses(); // ['0x...', '0x...']
525
+ registry.getEventName('0xddf252ad...'); // 'Transfer' (from any registered contract)
526
+
527
+ // ── Decode raw logs ──────────────────────────────────────────
528
+ const decoder = new EventDecoder(registry);
529
+
530
+ const decoded = decoder.decode(rawLog);
531
+ if (decoded) {
532
+ console.log(decoded.name); // 'Transfer'
533
+ console.log(decoded.args.from); // '0x...'
534
+ console.log(decoded.args.to); // '0x...'
535
+ console.log(decoded.args.value); // 1000000000000000000n
536
+ }
537
+ ```
538
+
539
+ ---
540
+
541
+ ## Checkpoint Persistence
542
+
543
+ Checkpoints let the watcher resume from where it left off after a restart.
544
+
545
+ ```typescript
546
+ import { MemoryCheckpoint, FileCheckpoint, createCheckpointBackend } from 'xdc-interaction-detector';
547
+
548
+ // ── Memory (development / testing) ───────────────────────────
549
+ const memCp = new MemoryCheckpoint();
550
+ await memCp.save('watcher-key', 75_000_000);
551
+ await memCp.load('watcher-key'); // 75000000
552
+
553
+ // ── File (simple production) ─────────────────────────────────
554
+ const fileCp = new FileCheckpoint('./checkpoints');
555
+ await fileCp.save('watcher-key', 75_000_000);
556
+ // Persists to ./checkpoints/checkpoints.json
557
+ // Survives process restarts
558
+
559
+ // ── Custom backend (bring your own) ──────────────────────────
560
+ const customCp = {
561
+ async save(key: string, blockNumber: number) {
562
+ await redis.set(`checkpoint:${key}`, blockNumber);
563
+ },
564
+ async load(key: string) {
565
+ const val = await redis.get(`checkpoint:${key}`);
566
+ return val ? parseInt(val) : null;
567
+ },
568
+ };
569
+
570
+ // ── Factory function ─────────────────────────────────────────
571
+ const cp = createCheckpointBackend({ backend: 'file', path: './data' });
572
+ const cp2 = createCheckpointBackend({ backend: 'custom', custom: customCp });
573
+ ```
574
+
575
+ ---
576
+
577
+ ## Utility Functions
578
+
579
+ ```typescript
580
+ import {
581
+ // Address utilities
582
+ normalizeAddress, // '0xABC...' or 'xdcABC...' → '0xabc...'
583
+ toXdcAddress, // '0xabc...' → 'xdcabc...'
584
+ toEthAddress, // 'xdcabc...' → '0xabc...'
585
+ isAddress, // validate hex address (20 bytes)
586
+ addressEqual, // compare addresses (case-insensitive, prefix-aware)
587
+
588
+ // Formatting
589
+ formatXDC, // bigint wei → '1.23M XDC' / '456.78K XDC'
590
+ formatWei, // bigint wei → '1.23M' (generic, no unit)
591
+ parseHexOrDecimal, // '0x100' → 256, '256' → 256
592
+ toHex, // 256 → '0x100'
593
+ shortAddress, // '0x1234...abcd'
594
+ sleep, // await sleep(1000)
595
+
596
+ // Logger
597
+ Logger, // new Logger('MyModule', 'info')
598
+ } from 'xdc-interaction-detector';
599
+ ```
600
+
601
+ ---
602
+
603
+ ## Configuration Reference
604
+
605
+ ### InteractionDetectorConfig (ContractWatcher)
606
+
607
+ | Option | Type | Default | Description |
608
+ | ----------------- | ------------------ | ---------- | -------------------------------------------- |
609
+ | `rpcUrl` | `string` | _required_ | Primary HTTP RPC URL |
610
+ | `wsUrl` | `string` | — | WebSocket RPC URL (enables real-time events) |
611
+ | `contracts` | `ContractConfig[]` | _required_ | Contracts to monitor |
612
+ | `explorer` | `ExplorerConfig` | — | Block explorer API settings |
613
+ | `polling` | `PollingConfig` | — | HTTP polling tuning |
614
+ | `ws` | `WsConfig` | — | WebSocket tuning |
615
+ | `checkpoint` | `CheckpointConfig` | `memory` | Checkpoint persistence |
616
+ | `chainId` | `number` | `50` | Chain ID |
617
+ | `fallbackRpcUrls` | `string[]` | `[]` | Fallback RPC endpoints |
618
+ | `logLevel` | `LogLevel` | `'info'` | Log verbosity |
619
+
620
+ ### ContractConfig
621
+
622
+ | Option | Type | Default | Description |
623
+ | --------- | -------- | ---------- | ----------------------------------------------------- |
624
+ | `address` | `string` | _required_ | Contract address (`0x` or `xdc` prefix) |
625
+ | `abi` | `any[]` | — | ABI for event decoding. Human-readable or JSON format |
626
+ | `name` | `string` | — | Human-readable label (shown in decoded events) |
627
+
628
+ ### ExplorerConfig
629
+
630
+ | Option | Type | Default | Description |
631
+ | ----------------- | -------- | ---------- | --------------------------------------- |
632
+ | `apiUrl` | `string` | _required_ | Explorer API base URL |
633
+ | `apiKey` | `string` | — | API key for higher rate limits |
634
+ | `chainId` | `number` | — | Chain ID (required for Etherscan v2) |
635
+ | `rateLimitPerSec` | `number` | `5` | Max requests per second |
636
+ | `pollIntervalMs` | `number` | `60000` | How often to poll txlist/txlistinternal |
637
+
638
+ ### PollingConfig
639
+
640
+ | Option | Type | Default | Description |
641
+ | --------------- | -------- | ------- | ---------------------------------------- |
642
+ | `intervalMs` | `number` | `30000` | How often to poll eth_getLogs |
643
+ | `maxBlockRange` | `number` | `100` | Max blocks per request (XDC limit = 100) |
644
+ | `concurrency` | `number` | `3` | Parallel chunk fetches |
645
+
646
+ ### WsConfig
647
+
648
+ | Option | Type | Default | Description |
649
+ | ---------------------- | --------- | ------- | --------------------------------------------- |
650
+ | `enabled` | `boolean` | `true` | Enable WebSocket subscription |
651
+ | `reconnectDelayBaseMs` | `number` | `5000` | Base reconnect delay |
652
+ | `reconnectDelayMaxMs` | `number` | `30000` | Max reconnect delay (exponential backoff cap) |
653
+ | `maxReconnectAttempts` | `number` | `20` | Max attempts before 5-min cooldown |
654
+ | `heartbeatIntervalMs` | `number` | `60000` | Heartbeat ping interval |
655
+ | `heartbeatTimeoutMs` | `number` | `10000` | Heartbeat response timeout |
656
+
657
+ ### CheckpointConfig
658
+
659
+ | Option | Type | Default | Description |
660
+ | --------- | ------------------- | ----------------- | -------------------------------------------- |
661
+ | `backend` | `string` | `'memory'` | `'memory'`, `'file'`, or `'custom'` |
662
+ | `path` | `string` | `'./checkpoints'` | Directory for `'file'` backend |
663
+ | `custom` | `CheckpointBackend` | — | Custom implementation for `'custom'` backend |
664
+
665
+ ---
666
+
667
+ ## XDC-Specific Notes
668
+
669
+ - **Non-standard ABI encoding:** Some XDC system contracts (including XDCValidator at `0x...0088`) encode all event parameters in the `data` field with only `topic[0]` (the event signature). Standard decoders like `ethers.js` fail on these. The library includes an automatic fallback decoder that handles this transparently.
670
+
671
+ - **Block range limit:** XDC RPC limits `eth_getLogs` to 100 blocks per request. The `LogPoller` and `BlockScanner` automatically chunk requests. Don't set `maxBlockRange` above 100 for XDC.
672
+
673
+ - **Address prefix:** XDC uses the `xdc` prefix instead of `0x`. All library functions accept both formats. Internally, everything normalizes to lowercase `0x`.
674
+
675
+ - **Tracing:** `debug_traceTransaction` requires an archive node for historical transactions. For monitoring current blocks in real-time, a standard full node works fine.
676
+
677
+ ---
678
+
679
+ ## Architecture
680
+
681
+ ```
682
+ ┌──────────────────────────────────────────────────────────┐
683
+ │ xdc-interaction-detector │
684
+ │ │
685
+ │ ┌───────────────────────────────────────────────────┐ │
686
+ │ │ ContractWatcher (real-time monitoring) │ │
687
+ │ │ ├── WsManager (WebSocket subscription) │ │
688
+ │ │ ├── LogPoller (eth_getLogs fallback) │ │
689
+ │ │ ├── ExplorerClient (txlist + txlistinternal)│ │
690
+ │ │ ├── EventDecoder (ABI + XDC fallback) │ │
691
+ │ │ └── Checkpoint (pluggable persistence) │ │
692
+ │ └───────────────────────────────────────────────────┘ │
693
+ │ │
694
+ │ ┌────────────────────────────────────────────────────┐ │
695
+ │ │ BlockScanner (historical queries) │ │
696
+ │ │ └── ExplorerClient (API adapter) │ │
697
+ │ └────────────────────────────────────────────────────┘ │
698
+ │ │
699
+ │ ┌────────────────────────────────────────────────────┐ │
700
+ │ │ TransactionTracer (debug_traceTransaction) │ │
701
+ │ │ ├── CallTreeParser (callTracer output) │ │
702
+ │ │ └── StateDiffParser (prestateTracer output) │ │
703
+ │ └────────────────────────────────────────────────────┘ │
704
+ │ │
705
+ │ ┌────────────────────┐ ┌───────────────────────────┐ │
706
+ │ │ RPC Client │ │ Utilities │ │
707
+ │ │ (retry, timeout, │ │ (address normalize, │ │
708
+ │ │ fallback, WS) │ │ format, logger, cache) │ │
709
+ │ └────────────────────┘ └───────────────────────────┘ │
710
+ └──────────────────────────────────────────────────────────┘
711
+ ```
712
+
713
+ **Detection pipeline:**
714
+
715
+ ```
716
+ Events (eth_subscribe + eth_getLogs)
717
+ ↓ collect tx hashes
718
+ XDCScan (txlist + txlistinternal)
719
+ ↓ merge + deduplicate
720
+ ContractInteraction records
721
+ ↓ optionally
722
+ debug_traceTransaction (per tx hash)
723
+ ↓ parse call tree + state diffs
724
+ Full execution story
725
+ ```
726
+
727
+ ---
728
+
729
+ ## Examples
730
+
731
+ See the `examples/` directory for runnable scripts:
732
+
733
+ | Example | What it demonstrates |
734
+ | ------------------------------ | ------------------------------------------ |
735
+ | `basic-watcher.ts` | Real-time event monitoring with checkpoint |
736
+ | `historical-scan.ts` | Scanning a block range for events |
737
+ | `trace-transaction.ts` | Deep-diving a single transaction |
738
+ | `full-interaction-detector.ts` | All 3 methods combined |
739
+
740
+ Run any example with:
741
+
742
+ ```bash
743
+ npx tsx examples/basic-watcher.ts
744
+ npx tsx examples/trace-transaction.ts 0xYourTxHash
745
+ ```
746
+
747
+ ---
748
+
749
+ ## Development
750
+
751
+ ```bash
752
+ # Install dependencies
753
+ npm install
754
+
755
+ # Compile TypeScript
756
+ npm run build
757
+
758
+ # Run unit tests (23 tests)
759
+ npm test
760
+
761
+ # Watch mode (auto-recompile on save)
762
+ npm run dev
763
+ ```
764
+
765
+ ## License
766
+
767
+ MIT