@valve-tech/tx-tracker 0.6.0 → 0.7.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.
package/dist/reorg.js ADDED
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Reorg detector — pure function over a recent-block ring.
3
+ *
4
+ * Per `docs/tx-tracker-spec.md` §12. The tracker keeps a small ring
5
+ * of recently-observed canonical blocks (`BlockSample` below). On
6
+ * every new tip, it walks the ring backward up to `reorgDepthBlocks`
7
+ * (default 12) and asks: "is the canonical chain at any of those
8
+ * heights different from what we recorded?" Any divergence becomes
9
+ * a `vanished-from-block` candidate the tracker translates per
10
+ * affected hash.
11
+ *
12
+ * Bounded depth (§12.2): a fixed 12-block window keeps the per-tick
13
+ * work O(depth) regardless of how anomalous the upstream's history
14
+ * is. Deeper reorgs are vanishingly rare on chains the package
15
+ * targets; "find the common ancestor" would let one bad reorg make
16
+ * a tick arbitrarily long.
17
+ *
18
+ * Pure: no I/O, no clock, no mutation of input. The tracker (in
19
+ * `tracker.ts`) calls `appendBlock` to fold each newly-observed
20
+ * canonical block into the ring, and `detectDivergences` against
21
+ * a candidate canonical sequence (typically the source's most
22
+ * recent emit + the on-demand block fetches the tracker did to
23
+ * walk the chain backward).
24
+ */
25
+ /**
26
+ * Append a freshly-observed canonical block into a bounded ring
27
+ * keyed by block number, capped at `capacityBlocks` entries. Returns
28
+ * a new ring (input is not mutated). The newest entry overwrites
29
+ * any previous entry at the same number — a same-height reorg
30
+ * replaces the stale block with the new canonical one.
31
+ */
32
+ export const appendBlock = (ring, block, capacityBlocks) => {
33
+ const next = ring.filter((b) => b.number !== block.number);
34
+ next.push(block);
35
+ // Sort ascending by block number. The ring is small (~depth + a
36
+ // few) so the cost is negligible. The same-number case is
37
+ // unreachable here — `filter` above strips any prior entry at
38
+ // `block.number` before we push, so the comparator never sees
39
+ // equal keys; we return -1 in that arm only to satisfy bigint
40
+ // sort's contract.
41
+ /* c8 ignore next */
42
+ next.sort((a, b) => (a.number < b.number ? -1 : 1));
43
+ if (next.length > capacityBlocks) {
44
+ next.splice(0, next.length - capacityBlocks);
45
+ }
46
+ return next;
47
+ };
48
+ /**
49
+ * Compare the tracker's `ring` (already-recorded canonical blocks)
50
+ * against a freshly-observed `canonical` sequence (the tracker's
51
+ * latest observation, plus any walk-back probes it performed). At
52
+ * each height present in BOTH sides, if the hashes disagree the
53
+ * detector returns a `BlockDivergence`.
54
+ *
55
+ * **Heights present only in `ring` are skipped** — the detector
56
+ * treats "no canonical entry at this height" as "no information,"
57
+ * not "vanished." A real same-height reorg requires the caller to
58
+ * explicitly pass the new canonical block at that height; gapping
59
+ * (e.g. caller skipped a height) is not a divergence signal.
60
+ *
61
+ * If you need to detect "ring's tip is no longer in canonical" you
62
+ * pass the canonical chain that explicitly covers that height; with
63
+ * a partial canonical sequence the detector stays conservative.
64
+ *
65
+ * `depthBlocks` caps how far back the comparison runs. The tracker
66
+ * rarely cares about divergences beyond `reorgDepthBlocks` because
67
+ * any tracked tx that deep would already be considered finalized by
68
+ * downstream consumer policy.
69
+ *
70
+ * Returns an empty array on a clean chain extension (no divergences).
71
+ */
72
+ export const detectDivergences = (input) => {
73
+ const { ring, canonical, depthBlocks } = input;
74
+ if (ring.length === 0 || canonical.length === 0)
75
+ return [];
76
+ // Index the canonical sequence by number for O(1) lookup. The
77
+ // canonical sequence is small (≤ depthBlocks), so map construction
78
+ // is cheap.
79
+ const canonicalByNumber = new Map();
80
+ for (const block of canonical) {
81
+ canonicalByNumber.set(block.number, block);
82
+ }
83
+ // The "tip" is the highest-numbered block in either side. Compare
84
+ // back from there for `depthBlocks` heights (inclusive of the tip).
85
+ // The early `length === 0` guard above means `ring[length-1]` and
86
+ // `canonical[length-1]` are always defined here — the `!` reflects
87
+ // that invariant rather than papering over a real nullable.
88
+ const ringTip = ring[ring.length - 1].number;
89
+ const canonicalTip = canonical[canonical.length - 1].number;
90
+ const tip = ringTip > canonicalTip ? ringTip : canonicalTip;
91
+ const lowestComparedNumber = tip - BigInt(depthBlocks - 1);
92
+ const divergences = [];
93
+ for (const sampled of ring) {
94
+ if (sampled.number < lowestComparedNumber)
95
+ continue;
96
+ const canonicalAtHeight = canonicalByNumber.get(sampled.number) ?? null;
97
+ if (!canonicalAtHeight)
98
+ continue; // no canonical info at this height
99
+ if (canonicalAtHeight.hash === sampled.hash)
100
+ continue; // unchanged
101
+ divergences.push({
102
+ blockNumber: sampled.number,
103
+ previousBlockHash: sampled.hash,
104
+ canonicalBlockHash: canonicalAtHeight.hash,
105
+ vanishedTransactionHashes: sampled.transactionHashes,
106
+ });
107
+ }
108
+ // Sort ascending by number so the tracker emits vanished events
109
+ // in chain order — easier on consumers piping to a single sink.
110
+ // Divergences are unique by `blockNumber` (one entry per ring
111
+ // height), so the comparator never sees equal keys; we return -1
112
+ // in that arm only to satisfy the sort contract.
113
+ /* c8 ignore next */
114
+ divergences.sort((a, b) => (a.blockNumber < b.blockNumber ? -1 : 1));
115
+ return divergences;
116
+ };
117
+ /**
118
+ * Default reorg-detection depth in blocks. Conservative — even
119
+ * Ethereum's worst recent reorgs are under 7 blocks, and unbounded
120
+ * walks would let a single anomalous reorg make the tick arbitrarily
121
+ * long (§12.2). Tunable per-tracker via
122
+ * `CreateTxTrackerOptions.reorgDepthBlocks`.
123
+ */
124
+ export const defaultReorgDepthBlocks = 12;
125
+ //# sourceMappingURL=reorg.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reorg.js","sourceRoot":"","sources":["../src/reorg.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Bulk-subscription matchers — pure functions over `RawTx`.
3
+ *
4
+ * Per `docs/tx-tracker-spec.md` §11. Indexers want "all txs from
5
+ * these senders" or "all txs touching this contract"; the tracker
6
+ * supports `from` / `to` / `predicate` selectors. The matchers
7
+ * themselves are pure (no I/O, no cached state) — `tracker.ts`
8
+ * iterates the source's mempool snapshots and the canonical block's
9
+ * `transactions` array per tick and asks each registered selector
10
+ * whether each tx matches.
11
+ *
12
+ * `from` and `to` selectors normalize addresses to lowercase ASCII
13
+ * before comparison because upstream RPCs are inconsistent about
14
+ * checksum vs lowercase. Mempool snapshots are already lowercased
15
+ * by `chain-source`'s `normalizeMempool`, but the same matcher is
16
+ * called against block-tx records (where the upstream may emit
17
+ * checksum form), so the selector normalizes its own
18
+ * pre-stored target once and compares lowercase-vs-lowercase.
19
+ *
20
+ * Predicate selectors are caller-defined. The tracker calls them
21
+ * O(N) per tick per matched-tx; spec §11.3 documents that slow
22
+ * predicates degrade the tick.
23
+ */
24
+ import type { RawTx } from '@valve-tech/chain-source';
25
+ import type { Hash } from './events.js';
26
+ import type { BulkSelector } from './store.js';
27
+ /**
28
+ * One match the bulk subscription is about to emit. The tracker
29
+ * builds the surrounding `TxMatchEvent` envelope (`source`,
30
+ * `at`, etc.) before pushing to the consumer; this shape is just
31
+ * the matcher's payload.
32
+ */
33
+ export interface BulkMatchPayload {
34
+ hash: Hash;
35
+ matchedBy: 'from' | 'to' | 'predicate';
36
+ selector: BulkSelector;
37
+ tx: RawTx;
38
+ }
39
+ /**
40
+ * One compiled selector — `selector` is the original consumer-facing
41
+ * shape, `match` is the cached pure function the tracker calls per
42
+ * tx. The tracker compiles each registered selector once at
43
+ * registration time so the per-tick fanout pays only the function
44
+ * call, not a dispatch on `selector.kind`.
45
+ */
46
+ export interface CompiledSelector {
47
+ selector: BulkSelector;
48
+ match: (tx: RawTx) => boolean;
49
+ }
50
+ /**
51
+ * Compile a `BulkSelector` into its pure matcher. `from` / `to`
52
+ * cache the lowercased address once; `predicate` returns the
53
+ * caller's function as-is.
54
+ *
55
+ * Throws on a malformed selector (`from` / `to` without an address,
56
+ * `predicate` without a function) — caught at registration time
57
+ * rather than at the per-tick callsite, where the failure mode would
58
+ * be silent zero matches.
59
+ */
60
+ export declare const compileSelector: (selector: BulkSelector) => CompiledSelector;
61
+ /**
62
+ * Iterate `txs` against every compiled selector and yield one
63
+ * `BulkMatchPayload` per (tx, selector) match. Used by the tracker's
64
+ * per-tick fan-out over both `block.transactions` (after a new tip
65
+ * lands) and the source's mempool snapshot deltas.
66
+ *
67
+ * The tracker, not this helper, decides whether to auto-track each
68
+ * matched hash — `BulkTrackOptions.autoTrackMatched` (default true)
69
+ * lives in `tracker.ts`.
70
+ */
71
+ export declare const matchAll: (txs: ReadonlyArray<RawTx>, compiled: ReadonlyArray<CompiledSelector>) => BulkMatchPayload[];
72
+ /**
73
+ * Default per-tracker bulk-subscription cap (§11.3). Higher fan-out
74
+ * is technically allowed but indicates the consumer should be
75
+ * running an indexer-shaped store rather than the in-memory default.
76
+ */
77
+ export declare const defaultMaxBulkSubscriptions = 16;
78
+ //# sourceMappingURL=selectors.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selectors.d.ts","sourceRoot":"","sources":["../src/selectors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAA;AAErD,OAAO,KAAK,EAAW,IAAI,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,IAAI,CAAA;IACV,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,CAAA;IACtC,QAAQ,EAAE,YAAY,CAAA;IACtB,EAAE,EAAE,KAAK,CAAA;CACV;AAED;;;;;;GAMG;AACH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,YAAY,CAAA;IACtB,KAAK,EAAE,CAAC,EAAE,EAAE,KAAK,KAAK,OAAO,CAAA;CAC9B;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe,GAAI,UAAU,YAAY,KAAG,gBAwCxD,CAAA;AAeD;;;;;;;;;GASG;AACH,eAAO,MAAM,QAAQ,GACnB,KAAK,aAAa,CAAC,KAAK,CAAC,EACzB,UAAU,aAAa,CAAC,gBAAgB,CAAC,KACxC,gBAAgB,EAelB,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,2BAA2B,KAAK,CAAA"}
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Bulk-subscription matchers — pure functions over `RawTx`.
3
+ *
4
+ * Per `docs/tx-tracker-spec.md` §11. Indexers want "all txs from
5
+ * these senders" or "all txs touching this contract"; the tracker
6
+ * supports `from` / `to` / `predicate` selectors. The matchers
7
+ * themselves are pure (no I/O, no cached state) — `tracker.ts`
8
+ * iterates the source's mempool snapshots and the canonical block's
9
+ * `transactions` array per tick and asks each registered selector
10
+ * whether each tx matches.
11
+ *
12
+ * `from` and `to` selectors normalize addresses to lowercase ASCII
13
+ * before comparison because upstream RPCs are inconsistent about
14
+ * checksum vs lowercase. Mempool snapshots are already lowercased
15
+ * by `chain-source`'s `normalizeMempool`, but the same matcher is
16
+ * called against block-tx records (where the upstream may emit
17
+ * checksum form), so the selector normalizes its own
18
+ * pre-stored target once and compares lowercase-vs-lowercase.
19
+ *
20
+ * Predicate selectors are caller-defined. The tracker calls them
21
+ * O(N) per tick per matched-tx; spec §11.3 documents that slow
22
+ * predicates degrade the tick.
23
+ */
24
+ /**
25
+ * Compile a `BulkSelector` into its pure matcher. `from` / `to`
26
+ * cache the lowercased address once; `predicate` returns the
27
+ * caller's function as-is.
28
+ *
29
+ * Throws on a malformed selector (`from` / `to` without an address,
30
+ * `predicate` without a function) — caught at registration time
31
+ * rather than at the per-tick callsite, where the failure mode would
32
+ * be silent zero matches.
33
+ */
34
+ export const compileSelector = (selector) => {
35
+ switch (selector.kind) {
36
+ case 'from': {
37
+ if (!selector.address) {
38
+ throw new Error('compileSelector: "from" selector requires an address');
39
+ }
40
+ const target = selector.address.toLowerCase();
41
+ return {
42
+ selector,
43
+ match: (tx) => (tx.from ? tx.from.toLowerCase() === target : false),
44
+ };
45
+ }
46
+ case 'to': {
47
+ if (!selector.address) {
48
+ throw new Error('compileSelector: "to" selector requires an address');
49
+ }
50
+ const target = selector.address.toLowerCase();
51
+ return {
52
+ selector,
53
+ match: (tx) =>
54
+ // RawTx in chain-source's wire shape doesn't carry `to`
55
+ // explicitly (it's loose for portability across
56
+ // mempool/block payloads); the matcher reads the field
57
+ // off the structurally-typed object so block-side txs
58
+ // (which DO carry `to`) match correctly. Mempool snapshots
59
+ // also carry `to`; both are read off the same field name
60
+ // upstream clients use.
61
+ extractTo(tx) ? extractTo(tx)?.toLowerCase() === target : false,
62
+ };
63
+ }
64
+ case 'predicate': {
65
+ if (typeof selector.match !== 'function') {
66
+ throw new Error('compileSelector: "predicate" selector requires a match function');
67
+ }
68
+ const fn = selector.match;
69
+ return { selector, match: (tx) => fn(tx) };
70
+ }
71
+ }
72
+ };
73
+ /**
74
+ * Read `tx.to` off a RawTx without losing type-safety. The
75
+ * `chain-source` `RawTx` is intentionally loose (no `to` field) —
76
+ * mempool / block payloads carry `to` even though it's not in the
77
+ * narrow type, and `to`-selectors must compare against it. Safe
78
+ * cast through `Record<string, unknown>` keeps lint clean and
79
+ * avoids `any`.
80
+ */
81
+ const extractTo = (tx) => {
82
+ const value = tx.to;
83
+ return typeof value === 'string' ? value : undefined;
84
+ };
85
+ /**
86
+ * Iterate `txs` against every compiled selector and yield one
87
+ * `BulkMatchPayload` per (tx, selector) match. Used by the tracker's
88
+ * per-tick fan-out over both `block.transactions` (after a new tip
89
+ * lands) and the source's mempool snapshot deltas.
90
+ *
91
+ * The tracker, not this helper, decides whether to auto-track each
92
+ * matched hash — `BulkTrackOptions.autoTrackMatched` (default true)
93
+ * lives in `tracker.ts`.
94
+ */
95
+ export const matchAll = (txs, compiled) => {
96
+ const matches = [];
97
+ for (const tx of txs) {
98
+ if (!tx.hash)
99
+ continue; // can't bulk-track an unhashed tx
100
+ for (const cs of compiled) {
101
+ if (!cs.match(tx))
102
+ continue;
103
+ matches.push({
104
+ hash: tx.hash,
105
+ matchedBy: cs.selector.kind,
106
+ selector: cs.selector,
107
+ tx,
108
+ });
109
+ }
110
+ }
111
+ return matches;
112
+ };
113
+ /**
114
+ * Default per-tracker bulk-subscription cap (§11.3). Higher fan-out
115
+ * is technically allowed but indicates the consumer should be
116
+ * running an indexer-shaped store rather than the in-memory default.
117
+ */
118
+ export const defaultMaxBulkSubscriptions = 16;
119
+ //# sourceMappingURL=selectors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"selectors.js","sourceRoot":"","sources":["../src/selectors.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,166 @@
1
+ /**
2
+ * `TxTrackerStore` — persistence surface for the tracker, plus the
3
+ * default `createInMemoryStore` implementation.
4
+ *
5
+ * Per `docs/tx-tracker-spec.md` §9 + §10. Indexers and relays cannot
6
+ * lose tracked hashes across a process restart; wallets are fine
7
+ * in-memory. The store interface lets either case plug in.
8
+ *
9
+ * Two responsibilities:
10
+ *
11
+ * - **Record store** — `put` / `get` / `delete` / `listDurable`
12
+ * for `TrackedTxRecord` (one per `(chainId, hash)`).
13
+ * - **Per-hash audit log** — `appendEvent` / `readEventLog?` over
14
+ * `TxEvent[]`. Default implementation is a bounded ring keyed
15
+ * by `(chainId, hash)`.
16
+ *
17
+ * The interface is **async-shaped** so durable stores (Redis, SQLite,
18
+ * JSON-on-disk) can implement it directly. The in-memory default
19
+ * resolves synchronously under the hood — `Promise.resolve` wrappers
20
+ * are cheap.
21
+ *
22
+ * **Wire format** (§9.1, §2.5): `TrackedTxRecord` carries `bigint`
23
+ * fields (`firstSeenBlockNumber`, `lastObservedBlockNumber`,
24
+ * `retentionExpiresAtBlockNumber`) and `TxStatus` carries `bigint`
25
+ * inside its nested `at` / `lastSeenIn*` shapes. The in-memory store
26
+ * keeps them as `bigint` end-to-end. Durable store implementers MUST
27
+ * hex-encode (`'0x' + n.toString(16)`) on write and decode on read;
28
+ * the package never calls `JSON.stringify` on a record itself.
29
+ */
30
+ import type { TxEvent, TxStatus, Hash, Address } from './events.js';
31
+ /**
32
+ * One persisted record per tracked `(chainId, hash)`. `subscriptions`
33
+ * carries every persisted (durable) subscription that referenced this
34
+ * hash so `listDurable` can rehydrate them after restart.
35
+ */
36
+ export interface TrackedTxRecord {
37
+ chainId: number;
38
+ hash: Hash;
39
+ /** Cached current status, kept in sync with every emit. */
40
+ status: TxStatus;
41
+ /** Block number of the very first observation. */
42
+ firstSeenBlockNumber: bigint;
43
+ /** Block number of the most recent observation (any kind). */
44
+ lastObservedBlockNumber: bigint;
45
+ /**
46
+ * Block number after which the record is GC-eligible. Set when the
47
+ * record reaches a terminal observation (`seen-in-block` reaching
48
+ * the consumer's confirmation threshold, `unseen-for-N-blocks`,
49
+ * `replaced-by`, etc.) plus `retentionBlocks`.
50
+ */
51
+ retentionExpiresAtBlockNumber: bigint;
52
+ /** Persisted subscriptions referencing this hash. */
53
+ subscriptions: PersistedSubscription[];
54
+ }
55
+ /**
56
+ * Per-hash and per-bulk subscription metadata. Predicate selectors
57
+ * are non-serializable (closures cannot survive a process boundary)
58
+ * — the tracker silently demotes them to non-durable and surfaces a
59
+ * warning at registration. Per spec §13.2 only `'from'` / `'to'`
60
+ * bulk selectors and per-hash selectors persist meaningfully.
61
+ */
62
+ export interface PersistedSubscription {
63
+ /** Stable identifier. The tracker assigns this on registration. */
64
+ id: string;
65
+ /** Whether the consumer asked for durable persistence. */
66
+ durable: boolean;
67
+ selector: HashSelector | BulkSelector;
68
+ }
69
+ /**
70
+ * Per-hash selector — one tracked hash, ergonomic for wallet UIs.
71
+ */
72
+ export interface HashSelector {
73
+ kind: 'hash';
74
+ hash: Hash;
75
+ }
76
+ /**
77
+ * Bulk selector — `'from' | 'to' | 'predicate'`. `predicate`
78
+ * carries a non-serializable function reference; durable persistence
79
+ * records `kind: 'predicate'` without the function and the tracker
80
+ * cannot rehydrate it on restart.
81
+ */
82
+ export interface BulkSelector {
83
+ kind: 'from' | 'to' | 'predicate';
84
+ /** For 'from' / 'to'. */
85
+ address?: Address;
86
+ /** For 'predicate'. Function references do not persist meaningfully. */
87
+ match?: (tx: import('@valve-tech/chain-source').RawTx) => boolean;
88
+ }
89
+ /**
90
+ * Persistence surface. Indexers and relays cannot lose tracked hashes
91
+ * across restart; wallets are fine in-memory. `readEventLog` is
92
+ * optional — durable stores that don't keep a log can omit it.
93
+ */
94
+ export interface TxTrackerStore {
95
+ /**
96
+ * Persist (or update) a tracked-tx record. Idempotent on
97
+ * `(chainId, hash)`.
98
+ */
99
+ put(record: TrackedTxRecord): Promise<void>;
100
+ /** Read the latest record for a hash. Returns null if absent. */
101
+ get(chainId: number, hash: Hash): Promise<TrackedTxRecord | null>;
102
+ /** Remove a hash. Called when the retention window expires. */
103
+ delete(chainId: number, hash: Hash): Promise<void>;
104
+ /**
105
+ * List records that carry at least one durable subscription. Called
106
+ * once at `tracker.start()` so durable per-hash subscriptions can
107
+ * be re-registered against the source after a restart.
108
+ */
109
+ listDurable(chainId: number): Promise<TrackedTxRecord[]>;
110
+ /**
111
+ * Append an event to the per-hash audit log. Indexers replay this
112
+ * log on restart; wallets can wrap a no-op store implementation.
113
+ * Failures here are routed through the tracker's `onError` and
114
+ * never block live emit (per spec Appendix A).
115
+ */
116
+ appendEvent(chainId: number, hash: Hash, event: TxEvent): Promise<void>;
117
+ /**
118
+ * Read the per-hash audit log, optionally constrained to events
119
+ * with `at.blockNumber >= since`. Optional — implementations
120
+ * without a log return `undefined` on the type and get pruned
121
+ * from the catch-up code path.
122
+ */
123
+ readEventLog?(chainId: number, hash: Hash, since?: bigint): Promise<TxEvent[]>;
124
+ }
125
+ /**
126
+ * Tunable knobs for the default in-memory store.
127
+ *
128
+ * - `retentionBlocks` (default 64): how many blocks past the last
129
+ * terminal observation a record stays in the store. After this
130
+ * window passes, the record is GC'd. Block-units, not seconds —
131
+ * reorg safety is a block-depth invariant, not a time invariant
132
+ * (spec §10.1).
133
+ *
134
+ * - `eventLogCapacity` (default 256): bounded ring buffer cap on
135
+ * the per-hash audit log. Older events are dropped when the cap
136
+ * is exceeded; the latest status is always retained.
137
+ */
138
+ export interface InMemoryStoreOptions {
139
+ retentionBlocks?: number;
140
+ eventLogCapacity?: number;
141
+ }
142
+ /**
143
+ * Default in-memory store. Synchronous internals wrapped in
144
+ * `Promise.resolve`; safe for any consumer that doesn't need
145
+ * cross-process durability.
146
+ *
147
+ * @example
148
+ * import { createInMemoryStore } from '@valve-tech/tx-tracker'
149
+ *
150
+ * const store = createInMemoryStore({ retentionBlocks: 32 })
151
+ * const tracker = createTxTracker({ source, chainId: 1, store })
152
+ */
153
+ export declare const createInMemoryStore: (options?: InMemoryStoreOptions) => TxTrackerStore;
154
+ /**
155
+ * Compute the retention-expiry block for a terminal observation.
156
+ * Pure helper exposed so the tracker (which decides when to call
157
+ * `store.put` with an updated `retentionExpiresAtBlockNumber`) and
158
+ * the store implementations stay in sync on the calculation.
159
+ */
160
+ export declare const computeRetentionExpiry: (terminalBlockNumber: bigint, retentionBlocks?: number) => bigint;
161
+ /**
162
+ * Default retention window in blocks — exposed so the tracker can
163
+ * use the same default if no store-level override is present.
164
+ */
165
+ export declare const defaultRetentionBlocks = 64;
166
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,aAAa,CAAA;AAEnE;;;;GAIG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,IAAI,CAAA;IACV,2DAA2D;IAC3D,MAAM,EAAE,QAAQ,CAAA;IAChB,kDAAkD;IAClD,oBAAoB,EAAE,MAAM,CAAA;IAC5B,8DAA8D;IAC9D,uBAAuB,EAAE,MAAM,CAAA;IAC/B;;;;;OAKG;IACH,6BAA6B,EAAE,MAAM,CAAA;IACrC,qDAAqD;IACrD,aAAa,EAAE,qBAAqB,EAAE,CAAA;CACvC;AAED;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACpC,mEAAmE;IACnE,EAAE,EAAE,MAAM,CAAA;IACV,0DAA0D;IAC1D,OAAO,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,YAAY,GAAG,YAAY,CAAA;CACtC;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,IAAI,CAAA;CACX;AAED;;;;;GAKG;AACH,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,GAAG,IAAI,GAAG,WAAW,CAAA;IACjC,yBAAyB;IACzB,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,wEAAwE;IACxE,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,OAAO,0BAA0B,EAAE,KAAK,KAAK,OAAO,CAAA;CAClE;AAED;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC7B;;;OAGG;IACH,GAAG,CAAC,MAAM,EAAE,eAAe,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAE3C,iEAAiE;IACjE,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAAA;IAEjE,+DAA+D;IAC/D,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAElD;;;;OAIG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC,CAAA;IAExD;;;;;OAKG;IACH,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAEvE;;;;;OAKG;IACH,YAAY,CAAC,CACX,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,IAAI,EACV,KAAK,CAAC,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,EAAE,CAAC,CAAA;CACtB;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,WAAW,oBAAoB;IACnC,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAcD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,GAC9B,UAAS,oBAAyB,KACjC,cAsDF,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,GACjC,qBAAqB,MAAM,EAC3B,kBAAiB,MAAiC,KACjD,MAAuD,CAAA;AAE1D;;;GAGG;AACH,eAAO,MAAM,sBAAsB,KAA2B,CAAA"}
package/dist/store.js ADDED
@@ -0,0 +1,110 @@
1
+ /**
2
+ * `TxTrackerStore` — persistence surface for the tracker, plus the
3
+ * default `createInMemoryStore` implementation.
4
+ *
5
+ * Per `docs/tx-tracker-spec.md` §9 + §10. Indexers and relays cannot
6
+ * lose tracked hashes across a process restart; wallets are fine
7
+ * in-memory. The store interface lets either case plug in.
8
+ *
9
+ * Two responsibilities:
10
+ *
11
+ * - **Record store** — `put` / `get` / `delete` / `listDurable`
12
+ * for `TrackedTxRecord` (one per `(chainId, hash)`).
13
+ * - **Per-hash audit log** — `appendEvent` / `readEventLog?` over
14
+ * `TxEvent[]`. Default implementation is a bounded ring keyed
15
+ * by `(chainId, hash)`.
16
+ *
17
+ * The interface is **async-shaped** so durable stores (Redis, SQLite,
18
+ * JSON-on-disk) can implement it directly. The in-memory default
19
+ * resolves synchronously under the hood — `Promise.resolve` wrappers
20
+ * are cheap.
21
+ *
22
+ * **Wire format** (§9.1, §2.5): `TrackedTxRecord` carries `bigint`
23
+ * fields (`firstSeenBlockNumber`, `lastObservedBlockNumber`,
24
+ * `retentionExpiresAtBlockNumber`) and `TxStatus` carries `bigint`
25
+ * inside its nested `at` / `lastSeenIn*` shapes. The in-memory store
26
+ * keeps them as `bigint` end-to-end. Durable store implementers MUST
27
+ * hex-encode (`'0x' + n.toString(16)`) on write and decode on read;
28
+ * the package never calls `JSON.stringify` on a record itself.
29
+ */
30
+ const DEFAULT_RETENTION_BLOCKS = 64;
31
+ const DEFAULT_EVENT_LOG_CAPACITY = 256;
32
+ /**
33
+ * Composite key for the in-memory map. Keying by `(chainId, hash)`
34
+ * lets a single store back multi-chain trackers if a future caller
35
+ * wants that — today the tracker is single-chain, but the store
36
+ * type is already chain-aware so no migration is needed later.
37
+ */
38
+ const recordKey = (chainId, hash) => `${chainId}:${hash}`;
39
+ /**
40
+ * Default in-memory store. Synchronous internals wrapped in
41
+ * `Promise.resolve`; safe for any consumer that doesn't need
42
+ * cross-process durability.
43
+ *
44
+ * @example
45
+ * import { createInMemoryStore } from '@valve-tech/tx-tracker'
46
+ *
47
+ * const store = createInMemoryStore({ retentionBlocks: 32 })
48
+ * const tracker = createTxTracker({ source, chainId: 1, store })
49
+ */
50
+ export const createInMemoryStore = (options = {}) => {
51
+ const eventLogCapacity = options.eventLogCapacity ?? DEFAULT_EVENT_LOG_CAPACITY;
52
+ const records = new Map();
53
+ const eventLogs = new Map();
54
+ return {
55
+ put: (record) => {
56
+ records.set(recordKey(record.chainId, record.hash), record);
57
+ return Promise.resolve();
58
+ },
59
+ get: (chainId, hash) => Promise.resolve(records.get(recordKey(chainId, hash)) ?? null),
60
+ delete: (chainId, hash) => {
61
+ const key = recordKey(chainId, hash);
62
+ records.delete(key);
63
+ eventLogs.delete(key);
64
+ return Promise.resolve();
65
+ },
66
+ listDurable: (chainId) => {
67
+ const result = [];
68
+ for (const record of records.values()) {
69
+ if (record.chainId !== chainId)
70
+ continue;
71
+ if (record.subscriptions.some((sub) => sub.durable)) {
72
+ result.push(record);
73
+ }
74
+ }
75
+ return Promise.resolve(result);
76
+ },
77
+ appendEvent: (chainId, hash, event) => {
78
+ const key = recordKey(chainId, hash);
79
+ const log = eventLogs.get(key) ?? [];
80
+ log.push(event);
81
+ // Drop oldest when capacity exceeded. Latest entries are the
82
+ // ones consumers care about for catch-up; keeping a strict
83
+ // ring rather than unbounded growth caps memory.
84
+ if (log.length > eventLogCapacity) {
85
+ log.splice(0, log.length - eventLogCapacity);
86
+ }
87
+ eventLogs.set(key, log);
88
+ return Promise.resolve();
89
+ },
90
+ readEventLog: (chainId, hash, since) => {
91
+ const log = eventLogs.get(recordKey(chainId, hash)) ?? [];
92
+ if (since === undefined)
93
+ return Promise.resolve([...log]);
94
+ return Promise.resolve(log.filter((e) => e.at.blockNumber >= since));
95
+ },
96
+ };
97
+ };
98
+ /**
99
+ * Compute the retention-expiry block for a terminal observation.
100
+ * Pure helper exposed so the tracker (which decides when to call
101
+ * `store.put` with an updated `retentionExpiresAtBlockNumber`) and
102
+ * the store implementations stay in sync on the calculation.
103
+ */
104
+ export const computeRetentionExpiry = (terminalBlockNumber, retentionBlocks = DEFAULT_RETENTION_BLOCKS) => terminalBlockNumber + BigInt(retentionBlocks);
105
+ /**
106
+ * Default retention window in blocks — exposed so the tracker can
107
+ * use the same default if no store-level override is present.
108
+ */
109
+ export const defaultRetentionBlocks = DEFAULT_RETENTION_BLOCKS;
110
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}