@valve-tech/tx-tracker 0.6.0 → 0.8.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 (57) hide show
  1. package/AGENTS.md +237 -0
  2. package/CHANGELOG.md +140 -0
  3. package/README.md +13 -6
  4. package/dist/events.d.ts +309 -0
  5. package/dist/events.d.ts.map +1 -0
  6. package/dist/events.js +132 -0
  7. package/dist/events.js.map +1 -0
  8. package/dist/group-events.d.ts +82 -0
  9. package/dist/group-events.d.ts.map +1 -0
  10. package/dist/group-events.js +47 -0
  11. package/dist/group-events.js.map +1 -0
  12. package/dist/group.d.ts +31 -0
  13. package/dist/group.d.ts.map +1 -0
  14. package/dist/group.js +196 -0
  15. package/dist/group.js.map +1 -0
  16. package/dist/index.d.ts +57 -10
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +52 -1
  19. package/dist/index.js.map +1 -1
  20. package/dist/observations.d.ts +169 -0
  21. package/dist/observations.d.ts.map +1 -0
  22. package/dist/observations.js +287 -0
  23. package/dist/observations.js.map +1 -0
  24. package/dist/reorg.d.ts +108 -0
  25. package/dist/reorg.d.ts.map +1 -0
  26. package/dist/reorg.js +125 -0
  27. package/dist/reorg.js.map +1 -0
  28. package/dist/replace-transaction.d.ts +46 -0
  29. package/dist/replace-transaction.d.ts.map +1 -0
  30. package/dist/replace-transaction.js +47 -0
  31. package/dist/replace-transaction.js.map +1 -0
  32. package/dist/selectors.d.ts +78 -0
  33. package/dist/selectors.d.ts.map +1 -0
  34. package/dist/selectors.js +119 -0
  35. package/dist/selectors.js.map +1 -0
  36. package/dist/store.d.ts +166 -0
  37. package/dist/store.d.ts.map +1 -0
  38. package/dist/store.js +110 -0
  39. package/dist/store.js.map +1 -0
  40. package/dist/tracker.d.ts +211 -0
  41. package/dist/tracker.d.ts.map +1 -0
  42. package/dist/tracker.js +1004 -0
  43. package/dist/tracker.js.map +1 -0
  44. package/dist/wait-for-pending.d.ts +41 -0
  45. package/dist/wait-for-pending.d.ts.map +1 -0
  46. package/dist/wait-for-pending.js +71 -0
  47. package/dist/wait-for-pending.js.map +1 -0
  48. package/dist/wait-for-transaction.d.ts +55 -0
  49. package/dist/wait-for-transaction.d.ts.map +1 -0
  50. package/dist/wait-for-transaction.js +72 -0
  51. package/dist/wait-for-transaction.js.map +1 -0
  52. package/dist/watch-transaction.d.ts +57 -0
  53. package/dist/watch-transaction.d.ts.map +1 -0
  54. package/dist/watch-transaction.js +76 -0
  55. package/dist/watch-transaction.js.map +1 -0
  56. package/package.json +6 -1
  57. package/skills/tx-tracker-integration/SKILL.md +198 -0
@@ -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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"}