@shroud-fi/scanning 0.1.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 (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -0
  3. package/dist/cjs/backend.d.ts +2 -0
  4. package/dist/cjs/backend.d.ts.map +1 -0
  5. package/dist/cjs/backend.js +6 -0
  6. package/dist/cjs/backend.js.map +1 -0
  7. package/dist/cjs/constants.d.ts +14 -0
  8. package/dist/cjs/constants.d.ts.map +1 -0
  9. package/dist/cjs/constants.js +17 -0
  10. package/dist/cjs/constants.js.map +1 -0
  11. package/dist/cjs/cursor.d.ts +8 -0
  12. package/dist/cjs/cursor.d.ts.map +1 -0
  13. package/dist/cjs/cursor.js +28 -0
  14. package/dist/cjs/cursor.js.map +1 -0
  15. package/dist/cjs/dedup.d.ts +10 -0
  16. package/dist/cjs/dedup.d.ts.map +1 -0
  17. package/dist/cjs/dedup.js +48 -0
  18. package/dist/cjs/dedup.js.map +1 -0
  19. package/dist/cjs/detector.d.ts +9 -0
  20. package/dist/cjs/detector.d.ts.map +1 -0
  21. package/dist/cjs/detector.js +425 -0
  22. package/dist/cjs/detector.js.map +1 -0
  23. package/dist/cjs/errors.d.ts +21 -0
  24. package/dist/cjs/errors.d.ts.map +1 -0
  25. package/dist/cjs/errors.js +49 -0
  26. package/dist/cjs/errors.js.map +1 -0
  27. package/dist/cjs/index.d.ts +9 -0
  28. package/dist/cjs/index.d.ts.map +1 -0
  29. package/dist/cjs/index.js +33 -0
  30. package/dist/cjs/index.js.map +1 -0
  31. package/dist/cjs/package.json +1 -0
  32. package/dist/cjs/types.d.ts +90 -0
  33. package/dist/cjs/types.d.ts.map +1 -0
  34. package/dist/cjs/types.js +17 -0
  35. package/dist/cjs/types.js.map +1 -0
  36. package/dist/cjs/viem-backend.d.ts +14 -0
  37. package/dist/cjs/viem-backend.d.ts.map +1 -0
  38. package/dist/cjs/viem-backend.js +166 -0
  39. package/dist/cjs/viem-backend.js.map +1 -0
  40. package/dist/esm/backend.d.ts +2 -0
  41. package/dist/esm/backend.d.ts.map +1 -0
  42. package/dist/esm/backend.js +5 -0
  43. package/dist/esm/backend.js.map +1 -0
  44. package/dist/esm/constants.d.ts +14 -0
  45. package/dist/esm/constants.d.ts.map +1 -0
  46. package/dist/esm/constants.js +14 -0
  47. package/dist/esm/constants.js.map +1 -0
  48. package/dist/esm/cursor.d.ts +8 -0
  49. package/dist/esm/cursor.d.ts.map +1 -0
  50. package/dist/esm/cursor.js +24 -0
  51. package/dist/esm/cursor.js.map +1 -0
  52. package/dist/esm/dedup.d.ts +10 -0
  53. package/dist/esm/dedup.d.ts.map +1 -0
  54. package/dist/esm/dedup.js +44 -0
  55. package/dist/esm/dedup.js.map +1 -0
  56. package/dist/esm/detector.d.ts +9 -0
  57. package/dist/esm/detector.d.ts.map +1 -0
  58. package/dist/esm/detector.js +422 -0
  59. package/dist/esm/detector.js.map +1 -0
  60. package/dist/esm/errors.d.ts +21 -0
  61. package/dist/esm/errors.d.ts.map +1 -0
  62. package/dist/esm/errors.js +41 -0
  63. package/dist/esm/errors.js.map +1 -0
  64. package/dist/esm/index.d.ts +9 -0
  65. package/dist/esm/index.d.ts.map +1 -0
  66. package/dist/esm/index.js +15 -0
  67. package/dist/esm/index.js.map +1 -0
  68. package/dist/esm/types.d.ts +90 -0
  69. package/dist/esm/types.d.ts.map +1 -0
  70. package/dist/esm/types.js +16 -0
  71. package/dist/esm/types.js.map +1 -0
  72. package/dist/esm/viem-backend.d.ts +14 -0
  73. package/dist/esm/viem-backend.d.ts.map +1 -0
  74. package/dist/esm/viem-backend.js +162 -0
  75. package/dist/esm/viem-backend.js.map +1 -0
  76. package/dist/tsconfig.cjs.tsbuildinfo +1 -0
  77. package/dist/tsconfig.esm.tsbuildinfo +1 -0
  78. package/dist/tsconfig.tsbuildinfo +1 -0
  79. package/package.json +61 -0
@@ -0,0 +1,90 @@
1
+ import type { Address, Hex, PublicClient, Chain, Transport } from 'viem';
2
+ import type { ScanningKey, SpendingKey } from '@shroud-fi/core';
3
+ /** Finality threshold an event must satisfy before it is yielded. */
4
+ export type FinalityLevel = 'unsafe' | 'safe' | 'finalized';
5
+ /** Optional handle for the caller-supplied viem PublicClient. */
6
+ export interface ScannerTransportLike {
7
+ readonly publicClient: PublicClient<Transport, Chain>;
8
+ }
9
+ /** Stealth address detection configuration. */
10
+ export interface ScannerConfig {
11
+ /** A ShroudFi transport (or any object exposing a viem PublicClient). */
12
+ readonly transport: ScannerTransportLike;
13
+ /** Recipient's scanning private key (branded). */
14
+ readonly scanningKey: ScanningKey;
15
+ /** Recipient's spending private key (branded). Required for ECDH derivation. */
16
+ readonly spendingKey: SpendingKey;
17
+ /** Scheme ID filter. Defaults to 1 (secp256k1 + view tag). */
18
+ readonly schemeId?: bigint;
19
+ /**
20
+ * ERC-5564 Announcer contract to watch. Defaults to the canonical address
21
+ * exported from @shroud-fi/transport (`ERC5564_ANNOUNCER`).
22
+ */
23
+ readonly contractAddress?: Address;
24
+ /** First block scanned. Detection state resets to this on restart. */
25
+ readonly startBlock: bigint;
26
+ /** Finality threshold for yielding. Defaults to `'safe'`. */
27
+ readonly finality?: FinalityLevel;
28
+ /** Reconciliation tick interval in ms. Defaults to 300_000 (5 minutes). */
29
+ readonly reconcileIntervalMs?: number;
30
+ /** LRU dedup capacity. Defaults to 10_000. */
31
+ readonly dedupCapacity?: number;
32
+ /** Max blocks per getLogs call (auto-chunking). Defaults to 10_000n. */
33
+ readonly getLogsChunkSize?: bigint;
34
+ /** Optional override of the backend implementation (for tests / Envio). */
35
+ readonly backend?: ScannerBackend;
36
+ }
37
+ /** Raw on-chain announcement before view-tag filtering or ECDH verification. */
38
+ export interface RawAnnouncement {
39
+ readonly schemeId: bigint;
40
+ readonly stealthAddress: Address;
41
+ readonly caller: Address;
42
+ readonly ephemeralPubKey: Uint8Array;
43
+ readonly metadata: Uint8Array;
44
+ readonly blockNumber: bigint;
45
+ readonly blockHash: Hex;
46
+ readonly txHash: Hex;
47
+ readonly logIndex: number;
48
+ }
49
+ /** Successfully detected stealth payment destined for this recipient. */
50
+ export interface DetectedPayment {
51
+ readonly stealthAddress: Address;
52
+ readonly ephemeralPubKey: Hex;
53
+ readonly stealthPrivateKey: Hex;
54
+ readonly blockNumber: bigint;
55
+ readonly blockHash: Hex;
56
+ readonly txHash: Hex;
57
+ readonly logIndex: number;
58
+ readonly finality: 'safe' | 'finalized';
59
+ }
60
+ /** Callback signature passed to backend.watchAnnouncements. */
61
+ export type AnnouncementHandler = (announcement: RawAnnouncement) => void | Promise<void>;
62
+ /** Unsubscribe handle returned by backend.watchAnnouncements. */
63
+ export type UnsubscribeFn = () => void;
64
+ /** Backend options shared between watch + getAnnouncementsInRange. */
65
+ export interface BackendWatchOptions {
66
+ readonly contractAddress: Address;
67
+ readonly schemeId?: bigint;
68
+ }
69
+ /**
70
+ * Indexer-agnostic announcement source. ViemScannerBackend is the only impl
71
+ * shipping in P3. A future EnvioScannerBackend lands behind this interface.
72
+ */
73
+ export interface ScannerBackend {
74
+ /** Subscribe to live announcements. Returns unsubscribe handle. */
75
+ watchAnnouncements(handler: AnnouncementHandler, options: BackendWatchOptions): UnsubscribeFn;
76
+ /** Historical range query (auto-chunked). */
77
+ getAnnouncementsInRange(fromBlock: bigint, toBlock: bigint, options: BackendWatchOptions): Promise<readonly RawAnnouncement[]>;
78
+ /** Latest block number for a finality level. */
79
+ getLatestBlock(level: FinalityLevel): Promise<bigint>;
80
+ }
81
+ /** Scanner runtime returned by createScanner. */
82
+ export interface Scanner {
83
+ /** Live async iterator over detected payments. Cancellable via AbortSignal. */
84
+ watch(signal?: AbortSignal): AsyncIterable<DetectedPayment>;
85
+ /** Backfill a historical block range, yielding any detected payments. */
86
+ scanRange(fromBlock: bigint, toBlock: bigint, signal?: AbortSignal): AsyncIterable<DetectedPayment>;
87
+ /** Stop watching, clear timers, release the backend subscription. */
88
+ close(): void;
89
+ }
90
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ // Public surface types for @shroud-fi/scanning (Phase 3 — detection only).
3
+ //
4
+ // Privacy contract:
5
+ // - ScannerConfig accepts branded ScanningKey + SpendingKey directly. These
6
+ // remain in-process; the scanner never logs them, serializes them, or
7
+ // surfaces them in errors. Spending key is required because ECDH derivation
8
+ // of the per-payment stealth private key needs it; see compute-key.ts.
9
+ // - DetectedPayment.stealthPrivateKey is the recovered private key for the
10
+ // one-time stealth address. It NEVER touches a relayer or logger. Caller
11
+ // drives the sweep (via @shroud-fi/payments sweepETH/sweepERC20) and is
12
+ // responsible for not leaking it.
13
+ // - Detection is idempotent. Cursor is in-memory only and resets to
14
+ // `startBlock` on restart. The dedup LRU prevents double-yield within a
15
+ // single Scanner lifetime.
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":";AAAA,2EAA2E;AAC3E,EAAE;AACF,oBAAoB;AACpB,8EAA8E;AAC9E,0EAA0E;AAC1E,gFAAgF;AAChF,2EAA2E;AAC3E,6EAA6E;AAC7E,6EAA6E;AAC7E,4EAA4E;AAC5E,sCAAsC;AACtC,sEAAsE;AACtE,4EAA4E;AAC5E,+BAA+B"}
@@ -0,0 +1,14 @@
1
+ import { type PublicClient } from 'viem';
2
+ import type { AnnouncementHandler, BackendWatchOptions, FinalityLevel, RawAnnouncement, ScannerBackend, UnsubscribeFn } from './types.js';
3
+ interface ViemBackendOptions {
4
+ readonly getLogsChunkSize?: bigint;
5
+ }
6
+ export declare class ViemScannerBackend implements ScannerBackend {
7
+ #private;
8
+ constructor(publicClient: PublicClient, options?: ViemBackendOptions);
9
+ watchAnnouncements(handler: AnnouncementHandler, options: BackendWatchOptions): UnsubscribeFn;
10
+ getAnnouncementsInRange(fromBlock: bigint, toBlock: bigint, options: BackendWatchOptions): Promise<readonly RawAnnouncement[]>;
11
+ getLatestBlock(level: FinalityLevel): Promise<bigint>;
12
+ }
13
+ export {};
14
+ //# sourceMappingURL=viem-backend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"viem-backend.d.ts","sourceRoot":"","sources":["../../src/viem-backend.ts"],"names":[],"mappings":"AAYA,OAAO,EAAoC,KAAK,YAAY,EAAE,MAAM,MAAM,CAAC;AAO3E,OAAO,KAAK,EACV,mBAAmB,EACnB,mBAAmB,EACnB,aAAa,EACb,eAAe,EACf,cAAc,EACd,aAAa,EACd,MAAM,YAAY,CAAC;AAOpB,UAAU,kBAAkB;IAC1B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACpC;AAkBD,qBAAa,kBAAmB,YAAW,cAAc;;gBAI3C,YAAY,EAAE,YAAY,EAAE,OAAO,CAAC,EAAE,kBAAkB;IAKpE,kBAAkB,CAChB,OAAO,EAAE,mBAAmB,EAC5B,OAAO,EAAE,mBAAmB,GAC3B,aAAa;IA+BV,uBAAuB,CAC3B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,EACf,OAAO,EAAE,mBAAmB,GAC3B,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC;IA8ChC,cAAc,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC;CA2B5D"}
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+ // viem-backed ScannerBackend. The only backend that ships in Phase 3.
3
+ //
4
+ // Privacy contract (binding):
5
+ // - No console.* anywhere in this file.
6
+ // - Error messages NEVER embed hex strings, key material, amounts, or raw RPC
7
+ // payloads. Short reason tags only ("getLogs failed", "watch failed",
8
+ // "getBlockNumber failed").
9
+ // - No JSON.stringify of args or logs.
10
+ // - Pending / malformed logs (missing blockNumber, blockHash, transactionHash,
11
+ // or logIndex) are silently skipped — they aren't actionable for detection
12
+ // and we don't want to leak their existence via thrown errors.
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.ViemScannerBackend = void 0;
15
+ const viem_1 = require("viem");
16
+ const transport_1 = require("@shroud-fi/transport");
17
+ const constants_js_1 = require("./constants.js");
18
+ const errors_js_1 = require("./errors.js");
19
+ const ANNOUNCEMENT_EVENT = (0, viem_1.getAbiItem)({
20
+ abi: transport_1.ERC5564AnnouncerAbi,
21
+ name: 'Announcement',
22
+ });
23
+ class ViemScannerBackend {
24
+ #publicClient;
25
+ #chunkSize;
26
+ constructor(publicClient, options) {
27
+ this.#publicClient = publicClient;
28
+ this.#chunkSize = options?.getLogsChunkSize ?? constants_js_1.DEFAULT_GETLOGS_CHUNK_SIZE;
29
+ }
30
+ watchAnnouncements(handler, options) {
31
+ const watchArgs = {
32
+ address: options.contractAddress,
33
+ abi: transport_1.ERC5564AnnouncerAbi,
34
+ eventName: 'Announcement',
35
+ onLogs: (logs) => {
36
+ for (const log of logs) {
37
+ const decoded = decodeAnnouncementLog(log);
38
+ if (decoded === null)
39
+ continue;
40
+ // Fire-and-forget. Handler may return a promise; we intentionally do
41
+ // not await — backend semantics are "deliver, don't gate".
42
+ void handler(decoded);
43
+ }
44
+ },
45
+ onError: (_err) => {
46
+ // Privacy: do not surface the underlying RPC error payload. Tag only.
47
+ throw new errors_js_1.BackendUnreachableError('watch failed');
48
+ },
49
+ };
50
+ const unsubscribe = options.schemeId !== undefined
51
+ ? this.#publicClient.watchContractEvent({
52
+ ...watchArgs,
53
+ args: { schemeId: options.schemeId },
54
+ })
55
+ : this.#publicClient.watchContractEvent(watchArgs);
56
+ return unsubscribe;
57
+ }
58
+ async getAnnouncementsInRange(fromBlock, toBlock, options) {
59
+ if (toBlock < fromBlock)
60
+ return [];
61
+ const collected = [];
62
+ let cursor = fromBlock;
63
+ while (cursor <= toBlock) {
64
+ const chunkEnd = cursor + this.#chunkSize - 1n > toBlock
65
+ ? toBlock
66
+ : cursor + this.#chunkSize - 1n;
67
+ let logs;
68
+ try {
69
+ const getLogsArgs = options.schemeId !== undefined
70
+ ? {
71
+ address: options.contractAddress,
72
+ event: ANNOUNCEMENT_EVENT,
73
+ args: { schemeId: options.schemeId },
74
+ fromBlock: cursor,
75
+ toBlock: chunkEnd,
76
+ }
77
+ : {
78
+ address: options.contractAddress,
79
+ event: ANNOUNCEMENT_EVENT,
80
+ fromBlock: cursor,
81
+ toBlock: chunkEnd,
82
+ };
83
+ logs = await this.#publicClient.getLogs(getLogsArgs);
84
+ }
85
+ catch {
86
+ // Privacy: short tag only. No original error payload.
87
+ throw new errors_js_1.BackendUnreachableError('getLogs failed');
88
+ }
89
+ for (const log of logs) {
90
+ const decoded = decodeAnnouncementLog(log);
91
+ if (decoded === null)
92
+ continue;
93
+ collected.push(decoded);
94
+ }
95
+ cursor = chunkEnd + 1n;
96
+ }
97
+ return collected;
98
+ }
99
+ async getLatestBlock(level) {
100
+ // viem.getBlockNumber() always returns the *latest* head; for 'safe' and
101
+ // 'finalized' we must go through getBlock({ blockTag }) and extract .number.
102
+ if (level === 'unsafe') {
103
+ try {
104
+ return await this.#publicClient.getBlockNumber();
105
+ }
106
+ catch {
107
+ throw new errors_js_1.BackendUnreachableError('getBlockNumber failed');
108
+ }
109
+ }
110
+ if (level !== 'safe' && level !== 'finalized') {
111
+ throw new errors_js_1.InvalidFinalityLevelError(String(level));
112
+ }
113
+ try {
114
+ const block = await this.#publicClient.getBlock({ blockTag: level });
115
+ const num = block.number;
116
+ if (num === null) {
117
+ throw new errors_js_1.BackendUnreachableError('getBlockNumber failed');
118
+ }
119
+ return num;
120
+ }
121
+ catch (err) {
122
+ if (err instanceof errors_js_1.BackendUnreachableError)
123
+ throw err;
124
+ throw new errors_js_1.BackendUnreachableError('getBlockNumber failed');
125
+ }
126
+ }
127
+ }
128
+ exports.ViemScannerBackend = ViemScannerBackend;
129
+ /**
130
+ * Convert a viem decoded log into a RawAnnouncement. Returns null for any
131
+ * pending / malformed log (missing block fields, missing args). Never throws.
132
+ */
133
+ function decodeAnnouncementLog(log) {
134
+ const args = log.args;
135
+ if (args === undefined)
136
+ return null;
137
+ if (args.schemeId === undefined ||
138
+ args.stealthAddress === undefined ||
139
+ args.caller === undefined ||
140
+ args.ephemeralPubKey === undefined ||
141
+ args.metadata === undefined) {
142
+ return null;
143
+ }
144
+ if (log.blockNumber === undefined ||
145
+ log.blockNumber === null ||
146
+ log.blockHash === undefined ||
147
+ log.blockHash === null ||
148
+ log.transactionHash === undefined ||
149
+ log.transactionHash === null ||
150
+ log.logIndex === undefined ||
151
+ log.logIndex === null) {
152
+ return null;
153
+ }
154
+ return {
155
+ schemeId: args.schemeId,
156
+ stealthAddress: args.stealthAddress,
157
+ caller: args.caller,
158
+ ephemeralPubKey: (0, viem_1.hexToBytes)(args.ephemeralPubKey),
159
+ metadata: (0, viem_1.hexToBytes)(args.metadata),
160
+ blockNumber: log.blockNumber,
161
+ blockHash: log.blockHash,
162
+ txHash: log.transactionHash,
163
+ logIndex: log.logIndex,
164
+ };
165
+ }
166
+ //# sourceMappingURL=viem-backend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"viem-backend.js","sourceRoot":"","sources":["../../src/viem-backend.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export type { ScannerBackend, RawAnnouncement, AnnouncementHandler, UnsubscribeFn, BackendWatchOptions, FinalityLevel, } from './types.js';
2
+ //# sourceMappingURL=backend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../../src/backend.ts"],"names":[],"mappings":"AAIA,YAAY,EACV,cAAc,EACd,eAAe,EACf,mBAAmB,EACnB,aAAa,EACb,mBAAmB,EACnB,aAAa,GACd,MAAM,YAAY,CAAC"}
@@ -0,0 +1,5 @@
1
+ // Thin re-export module that exposes the ScannerBackend interface without
2
+ // pulling the whole index.ts barrel. Lets backend implementations import the
3
+ // minimum surface they need.
4
+ export {};
5
+ //# sourceMappingURL=backend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"backend.js","sourceRoot":"","sources":["../../src/backend.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,6EAA6E;AAC7E,6BAA6B"}
@@ -0,0 +1,14 @@
1
+ import type { FinalityLevel } from './types.js';
2
+ /** Default ERC-5564 SCHEME_ID — secp256k1 with view tags. */
3
+ export declare const DEFAULT_SCHEME_ID = 1n;
4
+ /** Reconciliation tick interval — 5 minutes. */
5
+ export declare const DEFAULT_RECONCILE_INTERVAL_MS = 300000;
6
+ /** LRU dedup capacity — chosen to fit ~30 minutes of Base block production. */
7
+ export declare const DEFAULT_DEDUP_CAPACITY = 10000;
8
+ /** Max blocks per getLogs call — Alchemy/QuickNode default-safe. */
9
+ export declare const DEFAULT_GETLOGS_CHUNK_SIZE = 10000n;
10
+ /** Default finality threshold an event must satisfy before yielding. */
11
+ export declare const DEFAULT_FINALITY: FinalityLevel;
12
+ /** Minimum metadata length before a view tag can even be read (byte 0). */
13
+ export declare const MIN_METADATA_LENGTH = 1;
14
+ //# sourceMappingURL=constants.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD,6DAA6D;AAC7D,eAAO,MAAM,iBAAiB,KAAK,CAAC;AAEpC,gDAAgD;AAChD,eAAO,MAAM,6BAA6B,SAAU,CAAC;AAErD,+EAA+E;AAC/E,eAAO,MAAM,sBAAsB,QAAS,CAAC;AAE7C,oEAAoE;AACpE,eAAO,MAAM,0BAA0B,SAAU,CAAC;AAElD,wEAAwE;AACxE,eAAO,MAAM,gBAAgB,EAAE,aAAsB,CAAC;AAEtD,2EAA2E;AAC3E,eAAO,MAAM,mBAAmB,IAAI,CAAC"}
@@ -0,0 +1,14 @@
1
+ // Defaults for @shroud-fi/scanning. All values are tunable via ScannerConfig.
2
+ /** Default ERC-5564 SCHEME_ID — secp256k1 with view tags. */
3
+ export const DEFAULT_SCHEME_ID = 1n;
4
+ /** Reconciliation tick interval — 5 minutes. */
5
+ export const DEFAULT_RECONCILE_INTERVAL_MS = 300_000;
6
+ /** LRU dedup capacity — chosen to fit ~30 minutes of Base block production. */
7
+ export const DEFAULT_DEDUP_CAPACITY = 10_000;
8
+ /** Max blocks per getLogs call — Alchemy/QuickNode default-safe. */
9
+ export const DEFAULT_GETLOGS_CHUNK_SIZE = 10000n;
10
+ /** Default finality threshold an event must satisfy before yielding. */
11
+ export const DEFAULT_FINALITY = 'safe';
12
+ /** Minimum metadata length before a view tag can even be read (byte 0). */
13
+ export const MIN_METADATA_LENGTH = 1;
14
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/constants.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAI9E,6DAA6D;AAC7D,MAAM,CAAC,MAAM,iBAAiB,GAAG,EAAE,CAAC;AAEpC,gDAAgD;AAChD,MAAM,CAAC,MAAM,6BAA6B,GAAG,OAAO,CAAC;AAErD,+EAA+E;AAC/E,MAAM,CAAC,MAAM,sBAAsB,GAAG,MAAM,CAAC;AAE7C,oEAAoE;AACpE,MAAM,CAAC,MAAM,0BAA0B,GAAG,MAAO,CAAC;AAElD,wEAAwE;AACxE,MAAM,CAAC,MAAM,gBAAgB,GAAkB,MAAM,CAAC;AAEtD,2EAA2E;AAC3E,MAAM,CAAC,MAAM,mBAAmB,GAAG,CAAC,CAAC"}
@@ -0,0 +1,8 @@
1
+ export declare class Cursor {
2
+ #private;
3
+ constructor(startBlock: bigint);
4
+ get last(): bigint;
5
+ /** Advance to `block` if it is strictly greater than the current last. */
6
+ advanceTo(block: bigint): void;
7
+ }
8
+ //# sourceMappingURL=cursor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor.d.ts","sourceRoot":"","sources":["../../src/cursor.ts"],"names":[],"mappings":"AAOA,qBAAa,MAAM;;gBAGL,UAAU,EAAE,MAAM;IAO9B,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,0EAA0E;IAC1E,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI;CAK/B"}
@@ -0,0 +1,24 @@
1
+ // Monotonic block cursor used by the detector's reconciliation loop.
2
+ //
3
+ // In-memory only — by design (per types.ts contract). On restart the scanner
4
+ // resets to `startBlock`. Dedup + ECDH verification prevent double-yield.
5
+ import { ScanningError } from './errors.js';
6
+ export class Cursor {
7
+ #last;
8
+ constructor(startBlock) {
9
+ if (startBlock < 0n) {
10
+ throw new ScanningError('startBlock must be non-negative');
11
+ }
12
+ this.#last = startBlock;
13
+ }
14
+ get last() {
15
+ return this.#last;
16
+ }
17
+ /** Advance to `block` if it is strictly greater than the current last. */
18
+ advanceTo(block) {
19
+ if (block > this.#last) {
20
+ this.#last = block;
21
+ }
22
+ }
23
+ }
24
+ //# sourceMappingURL=cursor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/cursor.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,EAAE;AACF,6EAA6E;AAC7E,0EAA0E;AAE1E,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,OAAO,MAAM;IACjB,KAAK,CAAS;IAEd,YAAY,UAAkB;QAC5B,IAAI,UAAU,GAAG,EAAE,EAAE,CAAC;YACpB,MAAM,IAAI,aAAa,CAAC,iCAAiC,CAAC,CAAC;QAC7D,CAAC;QACD,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC;IAC1B,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,KAAK,CAAC;IACpB,CAAC;IAED,0EAA0E;IAC1E,SAAS,CAAC,KAAa;QACrB,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;YACvB,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACrB,CAAC;IACH,CAAC;CACF"}
@@ -0,0 +1,10 @@
1
+ export declare class LRUDeduper {
2
+ #private;
3
+ constructor(capacity: number);
4
+ has(key: string): boolean;
5
+ /** Add a key. Returns true if newly added, false if already present. */
6
+ add(key: string): boolean;
7
+ clear(): void;
8
+ get size(): number;
9
+ }
10
+ //# sourceMappingURL=dedup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedup.d.ts","sourceRoot":"","sources":["../../src/dedup.ts"],"names":[],"mappings":"AAWA,qBAAa,UAAU;;gBAIT,QAAQ,EAAE,MAAM;IAQ5B,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAIzB,wEAAwE;IACxE,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO;IAazB,KAAK,IAAI,IAAI;IAIb,IAAI,IAAI,IAAI,MAAM,CAEjB;CACF"}
@@ -0,0 +1,44 @@
1
+ // FIFO LRU deduper for announcement keys (`${txHash}:${logIndex}`).
2
+ //
3
+ // Privacy contract:
4
+ // - Keys passed in are public on-chain identifiers (txHash + logIndex). The
5
+ // deduper never sees private key material; nothing here is sensitive on its
6
+ // own. Still, no console.* logging by policy.
7
+ // - JS Map iteration order is insertion order, which gives us FIFO eviction
8
+ // for free without an explicit doubly-linked list.
9
+ import { ScanningError } from './errors.js';
10
+ export class LRUDeduper {
11
+ #capacity;
12
+ #map;
13
+ constructor(capacity) {
14
+ if (!Number.isInteger(capacity) || capacity < 1) {
15
+ throw new ScanningError('LRU capacity must be a positive integer');
16
+ }
17
+ this.#capacity = capacity;
18
+ this.#map = new Map();
19
+ }
20
+ has(key) {
21
+ return this.#map.has(key);
22
+ }
23
+ /** Add a key. Returns true if newly added, false if already present. */
24
+ add(key) {
25
+ if (this.#map.has(key))
26
+ return false;
27
+ this.#map.set(key, true);
28
+ if (this.#map.size > this.#capacity) {
29
+ // FIFO eviction: drop the oldest entry (first in insertion order).
30
+ for (const oldest of this.#map.keys()) {
31
+ this.#map.delete(oldest);
32
+ break;
33
+ }
34
+ }
35
+ return true;
36
+ }
37
+ clear() {
38
+ this.#map.clear();
39
+ }
40
+ get size() {
41
+ return this.#map.size;
42
+ }
43
+ }
44
+ //# sourceMappingURL=dedup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dedup.js","sourceRoot":"","sources":["../../src/dedup.ts"],"names":[],"mappings":"AAAA,oEAAoE;AACpE,EAAE;AACF,oBAAoB;AACpB,8EAA8E;AAC9E,gFAAgF;AAChF,kDAAkD;AAClD,8EAA8E;AAC9E,uDAAuD;AAEvD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,MAAM,OAAO,UAAU;IACZ,SAAS,CAAS;IAClB,IAAI,CAAoB;IAEjC,YAAY,QAAgB;QAC1B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,QAAQ,GAAG,CAAC,EAAE,CAAC;YAChD,MAAM,IAAI,aAAa,CAAC,yCAAyC,CAAC,CAAC;QACrE,CAAC;QACD,IAAI,CAAC,SAAS,GAAG,QAAQ,CAAC;QAC1B,IAAI,CAAC,IAAI,GAAG,IAAI,GAAG,EAAgB,CAAC;IACtC,CAAC;IAED,GAAG,CAAC,GAAW;QACb,OAAO,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC5B,CAAC;IAED,wEAAwE;IACxE,GAAG,CAAC,GAAW;QACb,IAAI,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAO,KAAK,CAAC;QACrC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;QACzB,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;YACpC,mEAAmE;YACnE,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;gBACtC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBACzB,MAAM;YACR,CAAC;QACH,CAAC;QACD,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK;QACH,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;IACpB,CAAC;IAED,IAAI,IAAI;QACN,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;IACxB,CAAC;CACF"}
@@ -0,0 +1,9 @@
1
+ import type { Scanner, ScannerConfig } from './types.js';
2
+ /**
3
+ * Build a Scanner instance.
4
+ *
5
+ * Construction validates config + resolves defaults. The Scanner is inert until
6
+ * the caller starts iterating watch() or scanRange(). close() is idempotent.
7
+ */
8
+ export declare function createScanner(config: ScannerConfig): Scanner;
9
+ //# sourceMappingURL=detector.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detector.d.ts","sourceRoot":"","sources":["../../src/detector.ts"],"names":[],"mappings":"AAuCA,OAAO,KAAK,EAGV,OAAO,EAEP,aAAa,EAEd,MAAM,YAAY,CAAC;AAEpB;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CA0b5D"}