@xdc.org/interaction-detector 1.0.0 → 1.0.6
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 +202 -52
- package/dist/checkpoint/checkpoint.d.ts.map +1 -1
- package/dist/checkpoint/checkpoint.js +6 -2
- package/dist/checkpoint/checkpoint.js.map +1 -1
- package/dist/explorer/explorer-client.d.ts.map +1 -1
- package/dist/explorer/explorer-client.js +22 -13
- package/dist/explorer/explorer-client.js.map +1 -1
- package/dist/explorer/rate-limiter.d.ts +15 -3
- package/dist/explorer/rate-limiter.d.ts.map +1 -1
- package/dist/explorer/rate-limiter.js +15 -3
- package/dist/explorer/rate-limiter.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/rpc/rpc-client.d.ts.map +1 -1
- package/dist/rpc/rpc-client.js +13 -9
- package/dist/rpc/rpc-client.js.map +1 -1
- package/dist/rpc/ws-manager.d.ts +23 -0
- package/dist/rpc/ws-manager.d.ts.map +1 -1
- package/dist/rpc/ws-manager.js +40 -4
- package/dist/rpc/ws-manager.js.map +1 -1
- package/dist/scanner/block-scanner.d.ts +14 -4
- package/dist/scanner/block-scanner.d.ts.map +1 -1
- package/dist/scanner/block-scanner.js +33 -12
- package/dist/scanner/block-scanner.js.map +1 -1
- package/dist/tracer/call-tree-parser.d.ts +2 -1
- package/dist/tracer/call-tree-parser.d.ts.map +1 -1
- package/dist/tracer/call-tree-parser.js +9 -5
- package/dist/tracer/call-tree-parser.js.map +1 -1
- package/dist/utils/address.js +1 -1
- package/dist/utils/address.js.map +1 -1
- package/dist/utils/format.d.ts +4 -2
- package/dist/utils/format.d.ts.map +1 -1
- package/dist/utils/format.js +12 -4
- package/dist/utils/format.js.map +1 -1
- package/dist/watcher/contract-watcher.d.ts +1 -0
- package/dist/watcher/contract-watcher.d.ts.map +1 -1
- package/dist/watcher/contract-watcher.js +20 -7
- package/dist/watcher/contract-watcher.js.map +1 -1
- package/dist/watcher/log-poller.d.ts +16 -0
- package/dist/watcher/log-poller.d.ts.map +1 -1
- package/dist/watcher/log-poller.js +20 -13
- package/dist/watcher/log-poller.js.map +1 -1
- package/package.json +9 -2
package/README.md
CHANGED
|
@@ -17,9 +17,12 @@ Combines: **events** + **direct calls** + **internal calls** + **transaction tra
|
|
|
17
17
|
- [1. ContractWatcher — Real-Time Monitoring](#1-contractwatcher--real-time-monitoring)
|
|
18
18
|
- [2. BlockScanner — Historical Queries](#2-blockscanner--historical-queries)
|
|
19
19
|
- [3. TransactionTracer — Deep Transaction Analysis](#3-transactiontracer--deep-transaction-analysis)
|
|
20
|
+
- [LogPoller — Standalone Log Fetching](#logpoller--standalone-log-fetching)
|
|
20
21
|
- [Explorer API Client](#explorer-api-client)
|
|
21
22
|
- [Event Decoder \& ABI Registry](#event-decoder--abi-registry)
|
|
22
23
|
- [Checkpoint Persistence](#checkpoint-persistence)
|
|
24
|
+
- [Built-in Backends](#built-in-backends)
|
|
25
|
+
- [Custom Backends](#custom-backends)
|
|
23
26
|
- [Utility Functions](#utility-functions)
|
|
24
27
|
- [Configuration Reference](#configuration-reference)
|
|
25
28
|
- [InteractionDetectorConfig (ContractWatcher)](#interactiondetectorconfig-contractwatcher)
|
|
@@ -51,17 +54,17 @@ Combines: **events** + **direct calls** + **internal calls** + **transaction tra
|
|
|
51
54
|
## Installation
|
|
52
55
|
|
|
53
56
|
```bash
|
|
54
|
-
npm install xdc
|
|
57
|
+
npm install @xdc.org/interaction-detector
|
|
55
58
|
```
|
|
56
59
|
|
|
57
|
-
**Dependencies:** `ethers` v6, `
|
|
60
|
+
**Dependencies:** `ethers` v6, `ws` — all installed automatically. HTTP calls use Node.js native `fetch` (requires Node ≥ 18).
|
|
58
61
|
|
|
59
62
|
---
|
|
60
63
|
|
|
61
64
|
## Quick Start
|
|
62
65
|
|
|
63
66
|
```typescript
|
|
64
|
-
import { ContractWatcher } from 'xdc
|
|
67
|
+
import { ContractWatcher } from '@xdc.org/interaction-detector';
|
|
65
68
|
|
|
66
69
|
const watcher = new ContractWatcher({
|
|
67
70
|
// RPC endpoints
|
|
@@ -80,8 +83,9 @@ const watcher = new ContractWatcher({
|
|
|
80
83
|
|
|
81
84
|
// Explorer API — enables direct & internal call detection
|
|
82
85
|
explorer: {
|
|
83
|
-
apiUrl: 'https://
|
|
84
|
-
apiKey: '
|
|
86
|
+
apiUrl: 'https://api.etherscan.io/v2/api',
|
|
87
|
+
apiKey: 'YOUR_ETHERSCAN_API_KEY', // optional — higher rate limits
|
|
88
|
+
chainId: 50, // XDC Mainnet
|
|
85
89
|
rateLimitPerSec: 5,
|
|
86
90
|
},
|
|
87
91
|
|
|
@@ -120,7 +124,7 @@ Watches one or more contract addresses for all interactions in real-time using t
|
|
|
120
124
|
**Creating a watcher:**
|
|
121
125
|
|
|
122
126
|
```typescript
|
|
123
|
-
import { ContractWatcher } from 'xdc
|
|
127
|
+
import { ContractWatcher } from '@xdc.org/interaction-detector';
|
|
124
128
|
|
|
125
129
|
const watcher = new ContractWatcher({
|
|
126
130
|
// ─── Required ───────────────────────────────────────────
|
|
@@ -141,9 +145,9 @@ const watcher = new ContractWatcher({
|
|
|
141
145
|
|
|
142
146
|
// ─── Optional: Explorer API (direct + internal calls) ───
|
|
143
147
|
explorer: {
|
|
144
|
-
apiUrl: 'https://
|
|
145
|
-
apiKey: '
|
|
146
|
-
chainId: 50, // required for Etherscan v2
|
|
148
|
+
apiUrl: 'https://api.etherscan.io/v2/api', // Etherscan v2 supports XDC
|
|
149
|
+
apiKey: 'YOUR_ETHERSCAN_API_KEY', // optional — get higher rate limits
|
|
150
|
+
chainId: 50, // XDC Mainnet (required for Etherscan v2)
|
|
147
151
|
rateLimitPerSec: 5, // default: 5 req/s
|
|
148
152
|
pollIntervalMs: 60_000, // how often to check explorer (default: 60s)
|
|
149
153
|
},
|
|
@@ -254,7 +258,7 @@ Scans a block range for all events and interactions involving a contract. Automa
|
|
|
254
258
|
**Scanning for events:**
|
|
255
259
|
|
|
256
260
|
```typescript
|
|
257
|
-
import { BlockScanner } from 'xdc
|
|
261
|
+
import { BlockScanner } from '@xdc.org/interaction-detector';
|
|
258
262
|
|
|
259
263
|
const scanner = new BlockScanner({
|
|
260
264
|
rpcUrl: 'https://rpc.xinfin.network',
|
|
@@ -264,8 +268,9 @@ const scanner = new BlockScanner({
|
|
|
264
268
|
|
|
265
269
|
// Optional: explorer for direct + internal tx enrichment
|
|
266
270
|
explorer: {
|
|
267
|
-
apiUrl: 'https://
|
|
268
|
-
apiKey: '
|
|
271
|
+
apiUrl: 'https://api.etherscan.io/v2/api',
|
|
272
|
+
apiKey: 'YOUR_ETHERSCAN_API_KEY',
|
|
273
|
+
chainId: 50,
|
|
269
274
|
},
|
|
270
275
|
});
|
|
271
276
|
|
|
@@ -324,7 +329,7 @@ Traces a single transaction to extract the full execution story: call tree, stat
|
|
|
324
329
|
**Full trace:**
|
|
325
330
|
|
|
326
331
|
```typescript
|
|
327
|
-
import { TransactionTracer } from 'xdc
|
|
332
|
+
import { TransactionTracer } from '@xdc.org/interaction-detector';
|
|
328
333
|
|
|
329
334
|
const tracer = new TransactionTracer({
|
|
330
335
|
rpcUrl: 'https://archive-rpc.xinfin.network', // must support debug_traceTransaction
|
|
@@ -398,7 +403,7 @@ const { stateDiffs, balanceChanges } = await tracer.traceStateDiffs('0xTxHash');
|
|
|
398
403
|
**Call tree utilities:**
|
|
399
404
|
|
|
400
405
|
```typescript
|
|
401
|
-
import { flattenCallTree, findCallsTo, extractInvolvedContracts } from 'xdc
|
|
406
|
+
import { flattenCallTree, findCallsTo, extractInvolvedContracts } from '@xdc.org/interaction-detector';
|
|
402
407
|
|
|
403
408
|
// Flatten the nested tree into a linear array
|
|
404
409
|
const allCalls = flattenCallTree(result.callTree);
|
|
@@ -413,19 +418,67 @@ const addresses = extractInvolvedContracts(result.callTree);
|
|
|
413
418
|
|
|
414
419
|
---
|
|
415
420
|
|
|
421
|
+
## LogPoller — Standalone Log Fetching
|
|
422
|
+
|
|
423
|
+
The `LogPoller` is used internally by `ContractWatcher` and `BlockScanner`, but is also exported for standalone use when you need direct control over `eth_getLogs` fetching with automatic chunking and concurrency.
|
|
424
|
+
|
|
425
|
+
```typescript
|
|
426
|
+
import { LogPoller, RpcClient } from '@xdc.org/interaction-detector';
|
|
427
|
+
import type { FetchLogsResult } from '@xdc.org/interaction-detector';
|
|
428
|
+
|
|
429
|
+
const rpc = new RpcClient('https://rpc.xinfin.network');
|
|
430
|
+
const poller = new LogPoller(
|
|
431
|
+
rpc,
|
|
432
|
+
['0x0000000000000000000000000000000000000088'], // addresses to monitor
|
|
433
|
+
{ maxBlockRange: 100, concurrency: 3 }, // optional PollingConfig
|
|
434
|
+
'info', // optional log level
|
|
435
|
+
);
|
|
436
|
+
|
|
437
|
+
// ── Basic usage — returns RawLog[] ──────────────────────────
|
|
438
|
+
const logs = await poller.fetchLogs(75_000_000, 75_001_000);
|
|
439
|
+
console.log(`Fetched ${logs.length} logs`);
|
|
440
|
+
|
|
441
|
+
// ── With failure tracking — returns FetchLogsResult ─────────
|
|
442
|
+
// Tracks which block ranges failed, so you can retry or alert
|
|
443
|
+
const result: FetchLogsResult = await poller.fetchLogs(75_000_000, 75_001_000, { trackFailures: true });
|
|
444
|
+
|
|
445
|
+
console.log(`Fetched ${result.logs.length} logs`);
|
|
446
|
+
if (result.failedRanges.length > 0) {
|
|
447
|
+
console.warn('Some ranges failed:');
|
|
448
|
+
for (const range of result.failedRanges) {
|
|
449
|
+
console.warn(` blocks ${range.from}–${range.to}: ${range.error}`);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
**`FetchLogsResult` type:**
|
|
455
|
+
|
|
456
|
+
```typescript
|
|
457
|
+
interface FetchLogsResult {
|
|
458
|
+
/** Successfully fetched logs */
|
|
459
|
+
logs: RawLog[];
|
|
460
|
+
/** Block ranges that failed to fetch (data may be missing for these ranges) */
|
|
461
|
+
failedRanges: Array<{ from: number; to: number; error: string }>;
|
|
462
|
+
}
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
> **Note:** The default `fetchLogs(from, to)` call (without `{ trackFailures: true }`) returns `RawLog[]` directly for backward compatibility. Failed chunks return empty arrays silently — use the `trackFailures` option when you need visibility into partial failures.
|
|
466
|
+
|
|
467
|
+
---
|
|
468
|
+
|
|
416
469
|
## Explorer API Client
|
|
417
470
|
|
|
418
471
|
Standalone Etherscan-compatible API client. Works with XDCScan, Etherscan v2, BSCScan, PolygonScan, and any Etherscan-compatible explorer.
|
|
419
472
|
|
|
420
473
|
```typescript
|
|
421
|
-
import { ExplorerClient } from 'xdc
|
|
474
|
+
import { ExplorerClient } from '@xdc.org/interaction-detector';
|
|
422
475
|
|
|
423
476
|
const explorer = new ExplorerClient({
|
|
424
|
-
apiUrl: 'https://
|
|
425
|
-
// apiUrl: 'https://
|
|
426
|
-
// apiUrl: 'https://api.bscscan.com/api',
|
|
427
|
-
apiKey: '
|
|
428
|
-
chainId: 50, // required for Etherscan v2
|
|
477
|
+
apiUrl: 'https://api.etherscan.io/v2/api', // Etherscan v2 — supports XDC + 80 chains
|
|
478
|
+
// apiUrl: 'https://xdc.blocksscan.io/api', // XDCScan (alternative)
|
|
479
|
+
// apiUrl: 'https://api.bscscan.com/api', // BSCScan
|
|
480
|
+
apiKey: 'YOUR_ETHERSCAN_API_KEY', // optional — higher rate limits
|
|
481
|
+
chainId: 50, // XDC Mainnet (required for Etherscan v2)
|
|
429
482
|
rateLimitPerSec: 5, // built-in token-bucket rate limiter
|
|
430
483
|
});
|
|
431
484
|
```
|
|
@@ -480,13 +533,13 @@ explorer.destroy(); // Cleans up rate limiter timers
|
|
|
480
533
|
|
|
481
534
|
**Explorer compatibility:**
|
|
482
535
|
|
|
483
|
-
| Explorer
|
|
484
|
-
|
|
|
485
|
-
| **
|
|
486
|
-
| **
|
|
487
|
-
| **BSCScan**
|
|
488
|
-
| **PolygonScan**
|
|
489
|
-
| **Custom**
|
|
536
|
+
| Explorer | Base URL | Chain | Free Rate Limit |
|
|
537
|
+
| ------------------- | --------------------------------- | -------------------------------- | --------------- |
|
|
538
|
+
| **Etherscan v2** ⭐ | `https://api.etherscan.io/v2/api` | XDC (50) + 80 chains via chainId | 5 req/s |
|
|
539
|
+
| **XDCScan** | `https://xdc.blocksscan.io/api` | XDC Mainnet (50) | 5 req/s |
|
|
540
|
+
| **BSCScan** | `https://api.bscscan.com/api` | BSC (56) | 5 req/s |
|
|
541
|
+
| **PolygonScan** | `https://api.polygonscan.com/api` | Polygon (137) | 5 req/s |
|
|
542
|
+
| **Custom** | Any Etherscan-compatible URL | Any EVM chain | Configurable |
|
|
490
543
|
|
|
491
544
|
---
|
|
492
545
|
|
|
@@ -495,7 +548,7 @@ explorer.destroy(); // Cleans up rate limiter timers
|
|
|
495
548
|
The decoder handles both standard Solidity ABI encoding and XDC's non-standard encoding (where all params are packed into the `data` field).
|
|
496
549
|
|
|
497
550
|
```typescript
|
|
498
|
-
import { AbiRegistry, EventDecoder } from 'xdc
|
|
551
|
+
import { AbiRegistry, EventDecoder } from '@xdc.org/interaction-detector';
|
|
499
552
|
|
|
500
553
|
// ── Register ABIs ────────────────────────────────────────────
|
|
501
554
|
const registry = new AbiRegistry();
|
|
@@ -513,8 +566,12 @@ registry.register(
|
|
|
513
566
|
// Register from a JSON ABI array
|
|
514
567
|
registry.register('0xAnotherContract', require('./MyContract.json').abi, 'MyContract');
|
|
515
568
|
|
|
516
|
-
// Auto-fetch from block explorer (verified contracts only)
|
|
517
|
-
const explorer = new ExplorerClient({
|
|
569
|
+
// Auto-fetch from block explorer (verified contracts only, requires API key)
|
|
570
|
+
const explorer = new ExplorerClient({
|
|
571
|
+
apiUrl: 'https://api.etherscan.io/v2/api',
|
|
572
|
+
apiKey: 'YOUR_ETHERSCAN_API_KEY',
|
|
573
|
+
chainId: 50,
|
|
574
|
+
});
|
|
518
575
|
const success = await registry.registerFromExplorer('0xVerifiedContract', explorer, 'VerifiedToken');
|
|
519
576
|
console.log(success ? 'ABI fetched!' : 'Contract not verified');
|
|
520
577
|
|
|
@@ -542,36 +599,112 @@ if (decoded) {
|
|
|
542
599
|
|
|
543
600
|
Checkpoints let the watcher resume from where it left off after a restart.
|
|
544
601
|
|
|
602
|
+
| Backend | Storage | Survives Restart? | Best For |
|
|
603
|
+
| -------- | -------------------------------- | ----------------- | ------------------------------ |
|
|
604
|
+
| `memory` | JavaScript `Map` in RAM | ❌ No | Development, testing |
|
|
605
|
+
| `file` | `checkpoints.json` on disk | ✅ Yes | Simple production deployments |
|
|
606
|
+
| `custom` | Your own (Redis, SQL, InfluxDB…) | ✅ Yes | Distributed / production-grade |
|
|
607
|
+
|
|
608
|
+
### Built-in Backends
|
|
609
|
+
|
|
545
610
|
```typescript
|
|
546
|
-
import { MemoryCheckpoint, FileCheckpoint, createCheckpointBackend } from 'xdc
|
|
611
|
+
import { MemoryCheckpoint, FileCheckpoint, createCheckpointBackend } from '@xdc.org/interaction-detector';
|
|
547
612
|
|
|
548
613
|
// ── Memory (development / testing) ───────────────────────────
|
|
614
|
+
// Stores in RAM only — lost when process stops
|
|
549
615
|
const memCp = new MemoryCheckpoint();
|
|
550
616
|
await memCp.save('watcher-key', 75_000_000);
|
|
551
617
|
await memCp.load('watcher-key'); // 75000000
|
|
552
618
|
|
|
553
619
|
// ── File (simple production) ─────────────────────────────────
|
|
620
|
+
// Auto-creates directory and file on first save
|
|
554
621
|
const fileCp = new FileCheckpoint('./checkpoints');
|
|
555
622
|
await fileCp.save('watcher-key', 75_000_000);
|
|
556
|
-
// Persists to ./checkpoints/checkpoints.json
|
|
623
|
+
// Persists to ./checkpoints/checkpoints.json (relative to process.cwd())
|
|
557
624
|
// Survives process restarts
|
|
558
625
|
|
|
559
|
-
// ──
|
|
560
|
-
const
|
|
561
|
-
|
|
562
|
-
|
|
626
|
+
// ── Factory function ─────────────────────────────────────────
|
|
627
|
+
const cp = createCheckpointBackend({ backend: 'file', path: './data' });
|
|
628
|
+
const cp2 = createCheckpointBackend({ backend: 'memory' });
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
### Custom Backends
|
|
632
|
+
|
|
633
|
+
Implement the `CheckpointBackend` interface — just two methods:
|
|
634
|
+
|
|
635
|
+
```typescript
|
|
636
|
+
interface CheckpointBackend {
|
|
637
|
+
save(key: string, blockNumber: number): Promise<void>;
|
|
638
|
+
load(key: string): Promise<number | null>;
|
|
639
|
+
}
|
|
640
|
+
```
|
|
641
|
+
|
|
642
|
+
**Redis:**
|
|
643
|
+
|
|
644
|
+
```typescript
|
|
645
|
+
checkpoint: {
|
|
646
|
+
backend: 'custom',
|
|
647
|
+
custom: {
|
|
648
|
+
async save(key: string, blockNumber: number) {
|
|
649
|
+
await redis.set(`checkpoint:${key}`, blockNumber);
|
|
650
|
+
},
|
|
651
|
+
async load(key: string) {
|
|
652
|
+
const val = await redis.get(`checkpoint:${key}`);
|
|
653
|
+
return val ? parseInt(val) : null;
|
|
654
|
+
},
|
|
563
655
|
},
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
656
|
+
},
|
|
657
|
+
```
|
|
658
|
+
|
|
659
|
+
**PostgreSQL / MySQL:**
|
|
660
|
+
|
|
661
|
+
```typescript
|
|
662
|
+
checkpoint: {
|
|
663
|
+
backend: 'custom',
|
|
664
|
+
custom: {
|
|
665
|
+
async save(key: string, blockNumber: number) {
|
|
666
|
+
await pool.query(
|
|
667
|
+
`INSERT INTO checkpoints (key, block_number) VALUES ($1, $2)
|
|
668
|
+
ON CONFLICT (key) DO UPDATE SET block_number = $2`,
|
|
669
|
+
[key, blockNumber],
|
|
670
|
+
);
|
|
671
|
+
},
|
|
672
|
+
async load(key: string) {
|
|
673
|
+
const result = await pool.query(
|
|
674
|
+
'SELECT block_number FROM checkpoints WHERE key = $1',
|
|
675
|
+
[key],
|
|
676
|
+
);
|
|
677
|
+
return result.rows[0]?.block_number ?? null;
|
|
678
|
+
},
|
|
567
679
|
},
|
|
568
|
-
}
|
|
680
|
+
},
|
|
681
|
+
```
|
|
569
682
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
683
|
+
**InfluxDB:**
|
|
684
|
+
|
|
685
|
+
```typescript
|
|
686
|
+
checkpoint: {
|
|
687
|
+
backend: 'custom',
|
|
688
|
+
custom: {
|
|
689
|
+
async save(key: string, blockNumber: number) {
|
|
690
|
+
await influx.writePoints([{
|
|
691
|
+
measurement: 'checkpoints',
|
|
692
|
+
tags: { key },
|
|
693
|
+
fields: { block_number: blockNumber },
|
|
694
|
+
}]);
|
|
695
|
+
},
|
|
696
|
+
async load(key: string) {
|
|
697
|
+
const result = await influx.query(
|
|
698
|
+
`SELECT LAST(block_number) FROM checkpoints WHERE key = '${key}'`,
|
|
699
|
+
);
|
|
700
|
+
return result[0]?.block_number ?? null;
|
|
701
|
+
},
|
|
702
|
+
},
|
|
703
|
+
},
|
|
573
704
|
```
|
|
574
705
|
|
|
706
|
+
> **Recommendation:** Use **Redis** or **SQL** for checkpoint storage. InfluxDB is better suited for storing the detected events/interactions as time-series data rather than simple key-value checkpoint state.
|
|
707
|
+
|
|
575
708
|
---
|
|
576
709
|
|
|
577
710
|
## Utility Functions
|
|
@@ -585,9 +718,9 @@ import {
|
|
|
585
718
|
isAddress, // validate hex address (20 bytes)
|
|
586
719
|
addressEqual, // compare addresses (case-insensitive, prefix-aware)
|
|
587
720
|
|
|
588
|
-
// Formatting
|
|
721
|
+
// Formatting (precision-safe for large BigInt values)
|
|
589
722
|
formatXDC, // bigint wei → '1.23M XDC' / '456.78K XDC'
|
|
590
|
-
formatWei, // bigint wei → '1.23M' (generic,
|
|
723
|
+
formatWei, // bigint wei → '1.23M' (generic, configurable decimals)
|
|
591
724
|
parseHexOrDecimal, // '0x100' → 256, '256' → 256
|
|
592
725
|
toHex, // 256 → '0x100'
|
|
593
726
|
shortAddress, // '0x1234...abcd'
|
|
@@ -595,9 +728,21 @@ import {
|
|
|
595
728
|
|
|
596
729
|
// Logger
|
|
597
730
|
Logger, // new Logger('MyModule', 'info')
|
|
598
|
-
} from 'xdc
|
|
731
|
+
} from '@xdc.org/interaction-detector';
|
|
732
|
+
```
|
|
733
|
+
|
|
734
|
+
**Address normalization behavior:**
|
|
735
|
+
|
|
736
|
+
```typescript
|
|
737
|
+
normalizeAddress('0xAbCdEf...'); // → '0xabcdef...' (lowercased)
|
|
738
|
+
normalizeAddress('xdcAbCdEf...'); // → '0xabcdef...' (xdc → 0x)
|
|
739
|
+
normalizeAddress(''); // → '0x0000000000000000000000000000000000000000' (zero address)
|
|
599
740
|
```
|
|
600
741
|
|
|
742
|
+
> **Note:** `normalizeAddress` returns the zero address for empty or falsy inputs, consistent with EVM conventions. This prevents empty strings from propagating as ghost entries in maps and sets.
|
|
743
|
+
|
|
744
|
+
**Formatting precision:** Both `formatXDC` and `formatWei` use bigint arithmetic internally, so large amounts (beyond JavaScript's `Number.MAX_SAFE_INTEGER`) are displayed correctly without loss of precision.
|
|
745
|
+
|
|
601
746
|
---
|
|
602
747
|
|
|
603
748
|
## Configuration Reference
|
|
@@ -670,30 +815,33 @@ import {
|
|
|
670
815
|
|
|
671
816
|
- **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
817
|
|
|
673
|
-
- **Address prefix:** XDC uses the `xdc` prefix instead of `0x`. All library functions accept both formats. Internally, everything normalizes to lowercase `0x`.
|
|
818
|
+
- **Address prefix:** XDC uses the `xdc` prefix instead of `0x`. All library functions accept both formats. Internally, everything normalizes to lowercase `0x`. Empty/falsy inputs normalize to the zero address (`0x000...000`).
|
|
674
819
|
|
|
675
820
|
- **Tracing:** `debug_traceTransaction` requires an archive node for historical transactions. For monitoring current blocks in real-time, a standard full node works fine.
|
|
676
821
|
|
|
822
|
+
- **Atomic checkpoints:** The `FileCheckpoint` backend uses atomic write-then-rename to prevent data corruption on process crash. Checkpoint data is always consistent.
|
|
823
|
+
|
|
677
824
|
---
|
|
678
825
|
|
|
679
826
|
## Architecture
|
|
680
827
|
|
|
681
828
|
```
|
|
682
829
|
┌──────────────────────────────────────────────────────────┐
|
|
683
|
-
│
|
|
830
|
+
│ @xdc.org/interaction-detector │
|
|
684
831
|
│ │
|
|
685
832
|
│ ┌───────────────────────────────────────────────────┐ │
|
|
686
833
|
│ │ ContractWatcher (real-time monitoring) │ │
|
|
687
834
|
│ │ ├── WsManager (WebSocket subscription) │ │
|
|
688
|
-
│ │ ├── LogPoller
|
|
689
|
-
│ │ ├── ExplorerClient
|
|
835
|
+
│ │ ├── LogPoller* (eth_getLogs fallback) │ │
|
|
836
|
+
│ │ ├── ExplorerClient* (txlist + txlistinternal)│ │
|
|
690
837
|
│ │ ├── EventDecoder (ABI + XDC fallback) │ │
|
|
691
838
|
│ │ └── Checkpoint (pluggable persistence) │ │
|
|
692
839
|
│ └───────────────────────────────────────────────────┘ │
|
|
693
840
|
│ │
|
|
694
841
|
│ ┌────────────────────────────────────────────────────┐ │
|
|
695
842
|
│ │ BlockScanner (historical queries) │ │
|
|
696
|
-
│ │
|
|
843
|
+
│ │ ├── LogPoller* (chunked eth_getLogs) │ │
|
|
844
|
+
│ │ └── ExplorerClient* (API adapter) │ │
|
|
697
845
|
│ └────────────────────────────────────────────────────┘ │
|
|
698
846
|
│ │
|
|
699
847
|
│ ┌────────────────────────────────────────────────────┐ │
|
|
@@ -707,6 +855,8 @@ import {
|
|
|
707
855
|
│ │ (retry, timeout, │ │ (address normalize, │ │
|
|
708
856
|
│ │ fallback, WS) │ │ format, logger, cache) │ │
|
|
709
857
|
│ └────────────────────┘ └───────────────────────────┘ │
|
|
858
|
+
│ │
|
|
859
|
+
│ * = also exported as standalone classes │
|
|
710
860
|
└──────────────────────────────────────────────────────────┘
|
|
711
861
|
```
|
|
712
862
|
|
|
@@ -755,7 +905,7 @@ npm install
|
|
|
755
905
|
# Compile TypeScript
|
|
756
906
|
npm run build
|
|
757
907
|
|
|
758
|
-
# Run unit tests (
|
|
908
|
+
# Run unit tests (114 tests across 11 test files)
|
|
759
909
|
npm test
|
|
760
910
|
|
|
761
911
|
# Watch mode (auto-recompile on save)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkpoint.d.ts","sourceRoot":"","sources":["../../src/checkpoint/checkpoint.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D;;GAEG;AACH,qBAAa,gBAAiB,YAAW,iBAAiB;IACxD,OAAO,CAAC,KAAK,CAAkC;IAEzC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrD,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;CAGhD;AAED;;;GAGG;AACH,qBAAa,cAAe,YAAW,iBAAiB;IACtD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAEtB,OAAO,GAAE,MAAwB;IAOvC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"checkpoint.d.ts","sourceRoot":"","sources":["../../src/checkpoint/checkpoint.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D;;GAEG;AACH,qBAAa,gBAAiB,YAAW,iBAAiB;IACxD,OAAO,CAAC,KAAK,CAAkC;IAEzC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAIrD,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;CAGhD;AAED;;;GAGG;AACH,qBAAa,cAAe,YAAW,iBAAiB;IACtD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;gBAEtB,OAAO,GAAE,MAAwB;IAOvC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAQrD,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;YAKjC,QAAQ;CAWvB;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,CAAC,EAAE;IAC/C,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,iBAAiB,CAAC;CAC5B,GAAG,iBAAiB,CAapB"}
|
|
@@ -30,7 +30,9 @@ export class FileCheckpoint {
|
|
|
30
30
|
async save(key, blockNumber) {
|
|
31
31
|
const data = await this.readFile();
|
|
32
32
|
data[key] = blockNumber;
|
|
33
|
-
|
|
33
|
+
const tmpPath = this.filePath + '.tmp';
|
|
34
|
+
fs.writeFileSync(tmpPath, JSON.stringify(data, null, 2));
|
|
35
|
+
fs.renameSync(tmpPath, this.filePath);
|
|
34
36
|
}
|
|
35
37
|
async load(key) {
|
|
36
38
|
const data = await this.readFile();
|
|
@@ -43,7 +45,9 @@ export class FileCheckpoint {
|
|
|
43
45
|
return JSON.parse(content);
|
|
44
46
|
}
|
|
45
47
|
}
|
|
46
|
-
catch {
|
|
48
|
+
catch {
|
|
49
|
+
/* ignore corrupt file */
|
|
50
|
+
}
|
|
47
51
|
return {};
|
|
48
52
|
}
|
|
49
53
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"checkpoint.js","sourceRoot":"","sources":["../../src/checkpoint/checkpoint.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAG7B;;GAEG;AACH,MAAM,OAAO,gBAAgB;IACnB,KAAK,GAAwB,IAAI,GAAG,EAAE,CAAC;IAE/C,KAAK,CAAC,IAAI,CAAC,GAAW,EAAE,WAAmB;QACzC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,GAAW;QACpB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;IACrC,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,OAAO,cAAc;IACR,QAAQ,CAAS;IAElC,YAAY,UAAkB,eAAe;QAC3C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAC5B,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7C,CAAC;QACD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,kBAAkB,CAAC,CAAC;IACzD,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,GAAW,EAAE,WAAmB;QACzC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;QACnC,IAAI,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC;QACxB,
|
|
1
|
+
{"version":3,"file":"checkpoint.js","sourceRoot":"","sources":["../../src/checkpoint/checkpoint.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAG7B;;GAEG;AACH,MAAM,OAAO,gBAAgB;IACnB,KAAK,GAAwB,IAAI,GAAG,EAAE,CAAC;IAE/C,KAAK,CAAC,IAAI,CAAC,GAAW,EAAE,WAAmB;QACzC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,WAAW,CAAC,CAAC;IACnC,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,GAAW;QACpB,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;IACrC,CAAC;CACF;AAED;;;GAGG;AACH,MAAM,OAAO,cAAc;IACR,QAAQ,CAAS;IAElC,YAAY,UAAkB,eAAe;QAC3C,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;YAC5B,EAAE,CAAC,SAAS,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7C,CAAC;QACD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,kBAAkB,CAAC,CAAC;IACzD,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,GAAW,EAAE,WAAmB;QACzC,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;QACnC,IAAI,CAAC,GAAG,CAAC,GAAG,WAAW,CAAC;QACxB,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,GAAG,MAAM,CAAC;QACvC,EAAE,CAAC,aAAa,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QACzD,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;IACxC,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,GAAW;QACpB,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,QAAQ,EAAE,CAAC;QACnC,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;IAC3B,CAAC;IAEO,KAAK,CAAC,QAAQ;QACpB,IAAI,CAAC;YACH,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACjC,MAAM,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;gBACxD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC7B,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,yBAAyB;QAC3B,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;CACF;AAED;;GAEG;AACH,MAAM,UAAU,uBAAuB,CAAC,MAIvC;IACC,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,gBAAgB,EAAE,CAAC;IAE3C,QAAQ,MAAM,CAAC,OAAO,EAAE,CAAC;QACvB,KAAK,MAAM;YACT,OAAO,IAAI,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACzC,KAAK,QAAQ;YACX,IAAI,CAAC,MAAM,CAAC,MAAM;gBAAE,MAAM,IAAI,KAAK,CAAC,wCAAwC,CAAC,CAAC;YAC9E,OAAO,MAAM,CAAC,MAAM,CAAC;QACvB,KAAK,QAAQ,CAAC;QACd;YACE,OAAO,IAAI,gBAAgB,EAAE,CAAC;IAClC,CAAC;AACH,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"explorer-client.d.ts","sourceRoot":"","sources":["../../src/explorer/explorer-client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;
|
|
1
|
+
{"version":3,"file":"explorer-client.d.ts","sourceRoot":"","sources":["../../src/explorer/explorer-client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,mBAAmB,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAEvF,qBAAa,cAAc;IACzB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAc;gBAE9B,MAAM,EAAE,cAAc,EAAE,QAAQ,CAAC,EAAE,QAAQ;IAevD;;OAEG;IACG,eAAe,CACnB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,KAAK,GAAG,MAAM,CAAA;KAAE,GAC1E,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAcjC;;OAEG;IACG,uBAAuB,CAC3B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,KAAK,GAAG,MAAM,CAAA;KAAE,GAC1E,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAcjC;;OAEG;IACG,2BAA2B,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAWjF;;OAEG;IACG,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAgBnH;;OAEG;IACG,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;IAmB5D;;OAEG;IACG,iBAAiB,CACrB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,KAAK,GAAG,MAAM,CAAA;KAAE,GAC1E,OAAO,CAAC,GAAG,EAAE,CAAC;IAajB;;OAEG;IACG,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAUlD;;OAEG;IACH,OAAO,IAAI,IAAI;YAMD,OAAO;CA0CtB"}
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* Works with: XDCScan, Etherscan v2, BSCScan, PolygonScan, and any
|
|
5
5
|
* Etherscan-compatible API by changing the base URL.
|
|
6
6
|
*/
|
|
7
|
-
import axios from 'axios';
|
|
8
7
|
import { Logger } from '../utils/logger.js';
|
|
9
8
|
import { RateLimiter } from './rate-limiter.js';
|
|
10
9
|
export class ExplorerClient {
|
|
@@ -19,6 +18,10 @@ export class ExplorerClient {
|
|
|
19
18
|
this.chainId = config.chainId;
|
|
20
19
|
this.rateLimiter = new RateLimiter(config.rateLimitPerSec ?? 5);
|
|
21
20
|
this.logger = new Logger('ExplorerClient', logLevel ?? 'info');
|
|
21
|
+
// Warn if using Etherscan v2 without an API key (required for all endpoints)
|
|
22
|
+
if (this.baseUrl.includes('etherscan.io') && !this.apiKey) {
|
|
23
|
+
this.logger.warn('Etherscan v2 requires an API key. Requests will fail without one. Get a free key at https://etherscan.io/myapikey');
|
|
24
|
+
}
|
|
22
25
|
}
|
|
23
26
|
/**
|
|
24
27
|
* Get external transactions for an address (txlist).
|
|
@@ -33,7 +36,7 @@ export class ExplorerClient {
|
|
|
33
36
|
sort: options?.sort ?? 'asc',
|
|
34
37
|
};
|
|
35
38
|
const results = await this.request(params);
|
|
36
|
-
return (results ?? []).map(
|
|
39
|
+
return (results ?? []).map(tx => ({ ...tx, txType: 'external' }));
|
|
37
40
|
}
|
|
38
41
|
/**
|
|
39
42
|
* Get internal transactions for an address (txlistinternal).
|
|
@@ -48,7 +51,7 @@ export class ExplorerClient {
|
|
|
48
51
|
sort: options?.sort ?? 'asc',
|
|
49
52
|
};
|
|
50
53
|
const results = await this.request(params);
|
|
51
|
-
return (results ?? []).map(
|
|
54
|
+
return (results ?? []).map(tx => ({ ...tx, txType: 'internal' }));
|
|
52
55
|
}
|
|
53
56
|
/**
|
|
54
57
|
* Get internal transactions within a specific transaction (txlistinternal by txhash).
|
|
@@ -60,7 +63,7 @@ export class ExplorerClient {
|
|
|
60
63
|
txhash: txHash,
|
|
61
64
|
};
|
|
62
65
|
const results = await this.request(params);
|
|
63
|
-
return (results ?? []).map(
|
|
66
|
+
return (results ?? []).map(tx => ({ ...tx, txType: 'internal' }));
|
|
64
67
|
}
|
|
65
68
|
/**
|
|
66
69
|
* Get event logs for an address (getLogs).
|
|
@@ -140,17 +143,23 @@ export class ExplorerClient {
|
|
|
140
143
|
params.chainid = String(this.chainId);
|
|
141
144
|
}
|
|
142
145
|
try {
|
|
143
|
-
const
|
|
144
|
-
|
|
145
|
-
|
|
146
|
+
const queryString = new URLSearchParams(params).toString();
|
|
147
|
+
const url = `${this.baseUrl}?${queryString}`;
|
|
148
|
+
const response = await fetch(url, {
|
|
149
|
+
method: 'GET',
|
|
150
|
+
signal: AbortSignal.timeout(15000),
|
|
146
151
|
});
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
return [];
|
|
152
|
+
if (!response.ok) {
|
|
153
|
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
150
154
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
155
|
+
const data = (await response.json());
|
|
156
|
+
if (data.status === '0') {
|
|
157
|
+
const msg = (data.message || '').toLowerCase();
|
|
158
|
+
if (msg.includes('no') && (msg.includes('found') || msg.includes('record') || msg.includes('transaction'))) {
|
|
159
|
+
return [];
|
|
160
|
+
}
|
|
161
|
+
const errorMsg = data.result || data.message || 'Unknown error';
|
|
162
|
+
this.logger.warn(`Explorer API error: ${errorMsg}`);
|
|
154
163
|
return null;
|
|
155
164
|
}
|
|
156
165
|
return data.result;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"explorer-client.js","sourceRoot":"","sources":["../../src/explorer/explorer-client.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,
|
|
1
|
+
{"version":3,"file":"explorer-client.js","sourceRoot":"","sources":["../../src/explorer/explorer-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -10,22 +10,34 @@ export declare class RateLimiter {
|
|
|
10
10
|
private queue;
|
|
11
11
|
private drainTimer;
|
|
12
12
|
/**
|
|
13
|
+
* Initializes the rate limiter with a given requests-per-second cap.
|
|
13
14
|
* @param maxPerSecond Maximum requests per second (default: 5)
|
|
14
15
|
*/
|
|
15
16
|
constructor(maxPerSecond?: number);
|
|
16
17
|
/**
|
|
17
|
-
*
|
|
18
|
+
* Waits until a request slot is available, then consumes one token.
|
|
19
|
+
* If no tokens are available, the caller is queued and resolved later.
|
|
18
20
|
*/
|
|
19
21
|
acquire(): Promise<void>;
|
|
20
22
|
/**
|
|
21
|
-
*
|
|
23
|
+
* Returns how many requests are currently waiting in the queue.
|
|
22
24
|
*/
|
|
23
25
|
get pending(): number;
|
|
24
26
|
/**
|
|
25
|
-
*
|
|
27
|
+
* Cleans up the rate limiter by clearing the drain timer
|
|
28
|
+
* and resolving all queued requests so nothing is left hanging.
|
|
26
29
|
*/
|
|
27
30
|
destroy(): void;
|
|
31
|
+
/**
|
|
32
|
+
* Refills tokens based on elapsed time since the last refill,
|
|
33
|
+
* capping at the maximum bucket size.
|
|
34
|
+
*/
|
|
28
35
|
private refill;
|
|
36
|
+
/**
|
|
37
|
+
* Starts a periodic timer that refills tokens and resolves
|
|
38
|
+
* queued requests as slots become available. Stops automatically
|
|
39
|
+
* when the queue is empty.
|
|
40
|
+
*/
|
|
29
41
|
private startDrain;
|
|
30
42
|
}
|
|
31
43
|
//# sourceMappingURL=rate-limiter.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/explorer/rate-limiter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,KAAK,CAAsC;IACnD,OAAO,CAAC,UAAU,CAA+B;IAEjD
|
|
1
|
+
{"version":3,"file":"rate-limiter.d.ts","sourceRoot":"","sources":["../../src/explorer/rate-limiter.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAS;IACtC,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,KAAK,CAAsC;IACnD,OAAO,CAAC,UAAU,CAA+B;IAEjD;;;OAGG;gBACS,YAAY,GAAE,MAAU;IAOpC;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IAe9B;;OAEG;IACH,IAAI,OAAO,IAAI,MAAM,CAEpB;IAED;;;OAGG;IACH,OAAO,IAAI,IAAI;IAWf;;;OAGG;IACH,OAAO,CAAC,MAAM;IAQd;;;;OAIG;IACH,OAAO,CAAC,UAAU;CAkBnB"}
|