@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
package/dist/group.js ADDED
@@ -0,0 +1,196 @@
1
+ /**
2
+ * `createTxGroup` — synthesis layer on top of per-hash tracker
3
+ * subscriptions. Spec §18.1, v0.8.0 design F3.
4
+ *
5
+ * Listens to each member hash via the tracker's existing subscribe API,
6
+ * tracks confirmed/failed counters, and emits group-progress /
7
+ * group-complete / group-failed once per terminal transition. Member
8
+ * unsubscribes are torn down on stop().
9
+ *
10
+ * Replacement does NOT auto-promote — if a member is replaced via
11
+ * same-nonce bump, the group emits group-failed with reason
12
+ * 'replaced'. The consumer constructs a new group with the new hash
13
+ * if they want the replacement to count.
14
+ *
15
+ * Edge case: an empty hashes array immediately emits group-complete
16
+ * with total=0 (vacuously all confirmed) and marks the group terminal.
17
+ * This is a safe, predictable default and matches the mathematical
18
+ * sense of "all zero members confirmed."
19
+ */
20
+ import { Subscriptions } from '@valve-tech/chain-source';
21
+ import { buildGroupComplete, buildGroupFailed, buildGroupProgress, buildGroupStopped, } from './group-events.js';
22
+ /**
23
+ * Construct a group subscription that synthesises per-member `TxEvent`
24
+ * streams into group-level events.
25
+ *
26
+ * @param tracker - The tracker instance to call `subscribe` on.
27
+ * @param hashes - Member transaction hashes. Order is not significant.
28
+ * @param options - Optional group-level and member-level overrides.
29
+ */
30
+ export const createTxGroup = (tracker, hashes, options = {}) => {
31
+ const groupId = options.groupId ??
32
+ `grp-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
33
+ const memberOptions = options.memberOptions ?? {};
34
+ const subs = new Subscriptions();
35
+ const total = hashes.length;
36
+ const confirmedSet = new Set();
37
+ let terminal = false;
38
+ const memberUnsubs = [];
39
+ const emit = (event) => subs.emit(event);
40
+ // Empty group — vacuously all confirmed. Emit complete immediately
41
+ // (synchronously; no member subscriptions to set up).
42
+ if (total === 0) {
43
+ terminal = true;
44
+ const zeroAt = { blockNumber: 0n, timestamp: 0n };
45
+ emit(buildGroupComplete({ groupId, at: zeroAt, total: 0 }));
46
+ }
47
+ const handleMemberEvent = (memberHash, event) => {
48
+ if (terminal)
49
+ return;
50
+ const at = event.at;
51
+ if (event.kind === 'seen-in-block' && event.confirmations >= 1) {
52
+ // Idempotency guard: same hash can emit multiple seen-in-block
53
+ // events (confirmations 1, 2, 3 …) — only count it once.
54
+ if (confirmedSet.has(memberHash))
55
+ return;
56
+ confirmedSet.add(memberHash);
57
+ if (confirmedSet.size < total) {
58
+ emit(buildGroupProgress({
59
+ groupId,
60
+ at,
61
+ confirmed: confirmedSet.size,
62
+ total,
63
+ lastHash: memberHash,
64
+ }));
65
+ }
66
+ else {
67
+ terminal = true;
68
+ emit(buildGroupComplete({ groupId, at, total }));
69
+ }
70
+ return;
71
+ }
72
+ if (event.kind === 'unseen-for-N-blocks') {
73
+ terminal = true;
74
+ emit(buildGroupFailed({
75
+ groupId,
76
+ at,
77
+ failedHash: memberHash,
78
+ reason: 'dropped',
79
+ }));
80
+ return;
81
+ }
82
+ if (event.kind === 'replaced-by') {
83
+ terminal = true;
84
+ emit(buildGroupFailed({
85
+ groupId,
86
+ at,
87
+ failedHash: memberHash,
88
+ reason: 'replaced',
89
+ }));
90
+ return;
91
+ }
92
+ };
93
+ for (const hash of hashes) {
94
+ const unsub = tracker.subscribe(hash, (event) => handleMemberEvent(hash, event), { ...memberOptions, emitInitial: false });
95
+ memberUnsubs.push(unsub);
96
+ }
97
+ let stopped = false;
98
+ const stop = () => {
99
+ if (stopped)
100
+ return;
101
+ stopped = true;
102
+ for (const unsub of memberUnsubs) {
103
+ try {
104
+ unsub();
105
+ }
106
+ catch {
107
+ // Swallow per-sub teardown errors — we always emit group-stopped.
108
+ }
109
+ }
110
+ emit(buildGroupStopped({ groupId, at: { blockNumber: 0n, timestamp: 0n } }));
111
+ };
112
+ const snapshot = () => {
113
+ const out = {};
114
+ for (const hash of hashes)
115
+ out[hash] = tracker.getTxStatus(hash);
116
+ return out;
117
+ };
118
+ /**
119
+ * Async-iterable surface over the group event stream. Resolves each
120
+ * `next()` call with the next queued event, or parks the caller in a
121
+ * waiter array until one arrives. Terminal events (group-complete /
122
+ * group-failed / group-stopped) flip `done` so subsequent `next()`
123
+ * calls resolve immediately with `done: true`.
124
+ */
125
+ const events = () => ({
126
+ [Symbol.asyncIterator]: () => {
127
+ const queue = [];
128
+ const waiters = [];
129
+ let done = false;
130
+ const unsub = subs.subscribe((event) => {
131
+ if (event.kind === 'group-stopped' ||
132
+ event.kind === 'group-complete' ||
133
+ event.kind === 'group-failed') {
134
+ done = true;
135
+ }
136
+ const waiter = waiters.shift();
137
+ if (waiter) {
138
+ waiter({ value: event, done: false });
139
+ if (done) {
140
+ // Drain remaining waiters with done after a terminal event.
141
+ // Reachable when a consumer parks multiple `next()` calls
142
+ // concurrently before any event arrives — first waiter gets
143
+ // the terminal event, the rest resolve with done:true here.
144
+ while (waiters.length > 0) {
145
+ waiters.shift()({
146
+ value: undefined,
147
+ done: true,
148
+ });
149
+ }
150
+ }
151
+ }
152
+ else {
153
+ queue.push(event);
154
+ }
155
+ });
156
+ return {
157
+ next: () => {
158
+ if (queue.length > 0) {
159
+ const value = queue.shift();
160
+ return Promise.resolve({ value, done: false });
161
+ }
162
+ if (done) {
163
+ return Promise.resolve({
164
+ value: undefined,
165
+ done: true,
166
+ });
167
+ }
168
+ return new Promise((resolve) => {
169
+ waiters.push(resolve);
170
+ });
171
+ },
172
+ return: () => {
173
+ unsub();
174
+ done = true;
175
+ while (waiters.length > 0) {
176
+ waiters.shift()({
177
+ value: undefined,
178
+ done: true,
179
+ });
180
+ }
181
+ return Promise.resolve({
182
+ value: undefined,
183
+ done: true,
184
+ });
185
+ },
186
+ };
187
+ },
188
+ });
189
+ return {
190
+ events,
191
+ subscribe: (cb) => subs.subscribe(cb),
192
+ snapshot,
193
+ stop,
194
+ };
195
+ };
196
+ //# sourceMappingURL=group.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"group.js","sourceRoot":"","sources":["../src/group.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,16 +1,63 @@
1
1
  /**
2
- * @valve-tech/tx-tracker — placeholder for v0.0.1.
2
+ * `@valve-tech/tx-tracker`per-tx state machine for EVM chains.
3
3
  *
4
- * The implementation lands in v0.1.0. This stub claims the npm name
5
- * and documents the planned shape so consumers reading the package
6
- * via `npm view` know what it is.
4
+ * Emits **neutral observations** (`seen-in-mempool`, `seen-in-block`,
5
+ * `replaced-by`, `vanished-from-block`, `unseen-for-N-blocks`,
6
+ * `signal-degraded`, `signal-recovered`, `stopped`) so wallet UIs,
7
+ * indexers, and relays can write their own interpretations on top.
8
+ * The tracker itself never says "confirmed" or "stuck"; it gives
9
+ * you the data to decide.
7
10
  *
8
- * Design contract: see `docs/tx-tracker-spec.md` in the
9
- * `valve-tech/evm-toolkit` repo, which defines the `TxTracker`,
10
- * `TxEvent`, `TxTrackerStore`, and bulk-subscription surfaces this
11
- * package will export.
11
+ * Three consumption shapes (callback, async iterator, snapshot)
12
+ * over one push-based core. Per-method capability detection keeps
13
+ * the "no silent downgrade" invariant — every emitted event carries
14
+ * a `source` discriminator (`'subscription' | 'block-poll' |
15
+ * 'mempool-snapshot' | 'receipt-poll'`).
12
16
  *
13
- * Until v0.1.0 is published, no symbols are exported.
17
+ * Consumes `@valve-tech/chain-source` for upstream signals; sibling
18
+ * to `@valve-tech/gas-oracle` (both consume the same source, neither
19
+ * depends on the other).
20
+ *
21
+ * @example
22
+ * import { createPublicClient, http } from 'viem'
23
+ * import { mainnet } from 'viem/chains'
24
+ * import { createChainSource } from '@valve-tech/chain-source'
25
+ * import { createTxTracker } from '@valve-tech/tx-tracker'
26
+ *
27
+ * const client = createPublicClient({ chain: mainnet, transport: http() })
28
+ * const source = createChainSource({ client })
29
+ * const tracker = createTxTracker({ source, chainId: 1 })
30
+ *
31
+ * source.start()
32
+ * tracker.start()
33
+ *
34
+ * for await (const event of tracker.track('0xabc...')) {
35
+ * console.log(event.kind, event.source, event.at.blockNumber)
36
+ * if (event.kind === 'seen-in-block' && event.confirmations >= 6) break
37
+ * }
38
+ *
39
+ * tracker.stop()
40
+ * source.stop()
14
41
  */
15
- export {};
42
+ export { createTxTracker } from './tracker.js';
43
+ export type { CreateTxTrackerOptions, TxTracker, TrackOptions, BulkTrackOptions, TxMatchEvent, TxSubscription, LostSignalPolicy, GroupOptions, TxGroupSubscription, } from './tracker.js';
44
+ export { buildStarted, buildSeenInMempool, buildLeftMempool, buildSeenInBlock, buildVanishedFromBlock, buildReplacedBy, buildUnseenForNBlocks, buildSignalDegraded, buildSignalRecovered, buildStopped, buildInitialStatus, } from './events.js';
45
+ export type { Address, At, Envelope, Hash, TxEvent, TxEventStarted, TxEventSeenInMempool, TxEventLeftMempool, TxEventSeenInBlock, TxEventVanishedFromBlock, TxEventReplacedBy, TxEventUnseenForNBlocks, TxEventSignalDegraded, TxEventSignalRecovered, TxEventStopped, TxStatus, } from './events.js';
46
+ export { createInMemoryStore, computeRetentionExpiry, defaultRetentionBlocks, } from './store.js';
47
+ export type { BulkSelector, HashSelector, InMemoryStoreOptions, PersistedSubscription, TrackedTxRecord, TxTrackerStore, } from './store.js';
48
+ export { appendBlock, defaultReorgDepthBlocks, detectDivergences, } from './reorg.js';
49
+ export type { BlockDivergence, BlockSample } from './reorg.js';
50
+ export { compileSelector, defaultMaxBulkSubscriptions, matchAll, } from './selectors.js';
51
+ export type { BulkMatchPayload, CompiledSelector } from './selectors.js';
52
+ export { createTxGroup } from './group.js';
53
+ export { watchTransaction } from './watch-transaction.js';
54
+ export type { WatchTransactionOptions } from './watch-transaction.js';
55
+ export { waitForTransaction } from './wait-for-transaction.js';
56
+ export type { WaitForTransactionOptions, WaitForTransactionOutcome, } from './wait-for-transaction.js';
57
+ export { waitForPending, WaitForPendingTimeoutError, } from './wait-for-pending.js';
58
+ export type { WaitForPendingOptions } from './wait-for-pending.js';
59
+ export { buildGroupComplete, buildGroupFailed, buildGroupProgress, buildGroupStopped, } from './group-events.js';
60
+ export type { TxGroupEvent, TxGroupEventComplete, TxGroupEventEnvelope, TxGroupEventFailed, TxGroupEventProgress, TxGroupEventStopped, } from './group-events.js';
61
+ export { replaceTransaction } from './replace-transaction.js';
62
+ export type { ReplaceTransactionNewGas, ReplaceTransactionOptions, ReplaceTransactionOriginal, } from './replace-transaction.js';
16
63
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAC9C,YAAY,EACV,sBAAsB,EACtB,SAAS,EACT,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,cAAc,EACd,gBAAgB,EAChB,YAAY,EACZ,mBAAmB,GACpB,MAAM,cAAc,CAAA;AAErB,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,sBAAsB,EACtB,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,YAAY,EACZ,kBAAkB,GACnB,MAAM,aAAa,CAAA;AACpB,YAAY,EACV,OAAO,EACP,EAAE,EACF,QAAQ,EACR,IAAI,EACJ,OAAO,EACP,cAAc,EACd,oBAAoB,EACpB,kBAAkB,EAClB,kBAAkB,EAClB,wBAAwB,EACxB,iBAAiB,EACjB,uBAAuB,EACvB,qBAAqB,EACrB,sBAAsB,EACtB,cAAc,EACd,QAAQ,GACT,MAAM,aAAa,CAAA;AAEpB,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,YAAY,CAAA;AACnB,YAAY,EACV,YAAY,EACZ,YAAY,EACZ,oBAAoB,EACpB,qBAAqB,EACrB,eAAe,EACf,cAAc,GACf,MAAM,YAAY,CAAA;AAEnB,OAAO,EACL,WAAW,EACX,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,YAAY,CAAA;AACnB,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AAE9D,OAAO,EACL,eAAe,EACf,2BAA2B,EAC3B,QAAQ,GACT,MAAM,gBAAgB,CAAA;AACvB,YAAY,EAAE,gBAAgB,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAExE,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AACzD,YAAY,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAA;AAErE,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAC9D,YAAY,EACV,yBAAyB,EACzB,yBAAyB,GAC1B,MAAM,2BAA2B,CAAA;AAElC,OAAO,EACL,cAAc,EACd,0BAA0B,GAC3B,MAAM,uBAAuB,CAAA;AAC9B,YAAY,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAA;AAElE,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,mBAAmB,CAAA;AAC1B,YAAY,EACV,YAAY,EACZ,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,EAClB,oBAAoB,EACpB,mBAAmB,GACpB,MAAM,mBAAmB,CAAA;AAE1B,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAC7D,YAAY,EACV,wBAAwB,EACxB,yBAAyB,EACzB,0BAA0B,GAC3B,MAAM,0BAA0B,CAAA"}
package/dist/index.js CHANGED
@@ -1,2 +1,53 @@
1
- export {};
1
+ /**
2
+ * `@valve-tech/tx-tracker` — per-tx state machine for EVM chains.
3
+ *
4
+ * Emits **neutral observations** (`seen-in-mempool`, `seen-in-block`,
5
+ * `replaced-by`, `vanished-from-block`, `unseen-for-N-blocks`,
6
+ * `signal-degraded`, `signal-recovered`, `stopped`) so wallet UIs,
7
+ * indexers, and relays can write their own interpretations on top.
8
+ * The tracker itself never says "confirmed" or "stuck"; it gives
9
+ * you the data to decide.
10
+ *
11
+ * Three consumption shapes (callback, async iterator, snapshot)
12
+ * over one push-based core. Per-method capability detection keeps
13
+ * the "no silent downgrade" invariant — every emitted event carries
14
+ * a `source` discriminator (`'subscription' | 'block-poll' |
15
+ * 'mempool-snapshot' | 'receipt-poll'`).
16
+ *
17
+ * Consumes `@valve-tech/chain-source` for upstream signals; sibling
18
+ * to `@valve-tech/gas-oracle` (both consume the same source, neither
19
+ * depends on the other).
20
+ *
21
+ * @example
22
+ * import { createPublicClient, http } from 'viem'
23
+ * import { mainnet } from 'viem/chains'
24
+ * import { createChainSource } from '@valve-tech/chain-source'
25
+ * import { createTxTracker } from '@valve-tech/tx-tracker'
26
+ *
27
+ * const client = createPublicClient({ chain: mainnet, transport: http() })
28
+ * const source = createChainSource({ client })
29
+ * const tracker = createTxTracker({ source, chainId: 1 })
30
+ *
31
+ * source.start()
32
+ * tracker.start()
33
+ *
34
+ * for await (const event of tracker.track('0xabc...')) {
35
+ * console.log(event.kind, event.source, event.at.blockNumber)
36
+ * if (event.kind === 'seen-in-block' && event.confirmations >= 6) break
37
+ * }
38
+ *
39
+ * tracker.stop()
40
+ * source.stop()
41
+ */
42
+ export { createTxTracker } from './tracker.js';
43
+ export { buildStarted, buildSeenInMempool, buildLeftMempool, buildSeenInBlock, buildVanishedFromBlock, buildReplacedBy, buildUnseenForNBlocks, buildSignalDegraded, buildSignalRecovered, buildStopped, buildInitialStatus, } from './events.js';
44
+ export { createInMemoryStore, computeRetentionExpiry, defaultRetentionBlocks, } from './store.js';
45
+ export { appendBlock, defaultReorgDepthBlocks, detectDivergences, } from './reorg.js';
46
+ export { compileSelector, defaultMaxBulkSubscriptions, matchAll, } from './selectors.js';
47
+ export { createTxGroup } from './group.js';
48
+ export { watchTransaction } from './watch-transaction.js';
49
+ export { waitForTransaction } from './wait-for-transaction.js';
50
+ export { waitForPending, WaitForPendingTimeoutError, } from './wait-for-pending.js';
51
+ export { buildGroupComplete, buildGroupFailed, buildGroupProgress, buildGroupStopped, } from './group-events.js';
52
+ export { replaceTransaction } from './replace-transaction.js';
2
53
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAa9C,OAAO,EACL,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,sBAAsB,EACtB,eAAe,EACf,qBAAqB,EACrB,mBAAmB,EACnB,oBAAoB,EACpB,YAAY,EACZ,kBAAkB,GACnB,MAAM,aAAa,CAAA;AAoBpB,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,sBAAsB,GACvB,MAAM,YAAY,CAAA;AAUnB,OAAO,EACL,WAAW,EACX,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,YAAY,CAAA;AAGnB,OAAO,EACL,eAAe,EACf,2BAA2B,EAC3B,QAAQ,GACT,MAAM,gBAAgB,CAAA;AAGvB,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE1C,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAGzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAA;AAM9D,OAAO,EACL,cAAc,EACd,0BAA0B,GAC3B,MAAM,uBAAuB,CAAA;AAG9B,OAAO,EACL,kBAAkB,EAClB,gBAAgB,EAChB,kBAAkB,EAClB,iBAAiB,GAClB,MAAM,mBAAmB,CAAA;AAU1B,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA"}
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Per-record decision functions — the **pure** logic that turns one
3
+ * upstream observation (a block or a mempool snapshot) plus one
4
+ * tracked record's current state into the events that should be
5
+ * emitted and the state patch the orchestrator should apply.
6
+ *
7
+ * These functions are extracted from `tracker.ts`'s `onBlock` /
8
+ * `onMempool` so each branch of the per-record state machine is
9
+ * testable with literal fixture inputs — no stub source, no async
10
+ * orchestration, no shared mutable closure. This is the same
11
+ * primitive-vs-orchestrator split that `oracle.ts` (`reducePollInputs`
12
+ * pure / poll loop stateful) and `chain-source` (math pure / source
13
+ * stateful) already follow.
14
+ *
15
+ * Inputs are immutable; outputs are immutable. The caller in
16
+ * `tracker.ts` applies the returned `statusPatch` and `identityPatch`
17
+ * to its mutable `TrackedRecord`, then emits the returned `events`
18
+ * via its event-bus + store-audit-log machinery.
19
+ */
20
+ import type { EventSource, RawTx, TransactionReceipt } from '@valve-tech/chain-source';
21
+ import { type At, type Hash, type TxEvent, type TxStatus } from './events.js';
22
+ /**
23
+ * Read-only projection of `TrackedRecord` that the decision functions
24
+ * consume. The orchestrator passes its mutable record through this
25
+ * shape so the pure layer cannot accidentally mutate state.
26
+ */
27
+ export interface ReadonlyTrackedRecord {
28
+ hash: Hash;
29
+ status: TxStatus;
30
+ identity: {
31
+ from: string;
32
+ nonce: string;
33
+ } | null;
34
+ inLastMempoolSnapshot: boolean;
35
+ unseenThresholdBlocks: number;
36
+ }
37
+ /**
38
+ * Cached `(from, nonce)` identity of the tracked tx. Used for
39
+ * replacement detection.
40
+ */
41
+ export interface IdentityPatch {
42
+ from: string;
43
+ nonce: string;
44
+ }
45
+ /**
46
+ * Result of a decision function: events to emit and patches to apply
47
+ * to the tracked record. The patch shapes are deliberately narrow —
48
+ * only the fields the function decided to change. The orchestrator
49
+ * merges them into its mutable record.
50
+ */
51
+ export interface ObservationResult {
52
+ events: TxEvent[];
53
+ statusPatch: Partial<TxStatus>;
54
+ identityPatch: IdentityPatch | null;
55
+ /** Set when the per-mempool-tick "in-snapshot?" flag should change. */
56
+ inMempoolPatch: boolean | null;
57
+ }
58
+ /**
59
+ * Inputs to `decideBlockObservation`. The orchestrator builds a
60
+ * `txHashSet` once per block and passes it for O(1) "did this hash
61
+ * appear?" lookups across every tracked record.
62
+ */
63
+ export interface BlockObservationInput {
64
+ record: ReadonlyTrackedRecord;
65
+ blockHash: Hash;
66
+ blockNumber: bigint;
67
+ txHashSet: ReadonlySet<Hash>;
68
+ txs: ReadonlyArray<RawTx>;
69
+ chainId: number;
70
+ eventSource: EventSource;
71
+ envelope: At;
72
+ /**
73
+ * The previous canonical tip's block number, or `null` if this is
74
+ * the first block the tracker has seen. Used to gate the
75
+ * "confirmation bump" path — we don't bump confirmations on the
76
+ * very first block we observe.
77
+ */
78
+ previousTipNumber: bigint | null;
79
+ /**
80
+ * Pre-fetched receipts for hashes whose `withReceipts` flag is set.
81
+ * When a hash appears in this map, the receipt is attached to the
82
+ * `seen-in-block` event. Omitted or empty means no enrichment. This
83
+ * is the sole path for F2 eager receipt attachment (spec §18.2) —
84
+ * the orchestrator fetches and populates this map before calling
85
+ * `decideBlockObservation`, so the first emitted event carries the
86
+ * receipt without a follow-up re-emit.
87
+ */
88
+ prefetchedReceipts?: ReadonlyMap<Hash, TransactionReceipt>;
89
+ }
90
+ /**
91
+ * Per-record decision for one new canonical block. Returns the events
92
+ * to emit and the state patch to apply. Mutually-exclusive paths,
93
+ * evaluated in order:
94
+ *
95
+ * 1. Hash is in this block → fresh inclusion (emit `seen-in-block`
96
+ * with `confirmations: 1`) OR same-block re-observation (no
97
+ * emit; `lastSeenInBlock` is already current).
98
+ * 2. Hash NOT in this block but was previously included → bump
99
+ * `confirmations` on the cached observation, emit a fresh
100
+ * `seen-in-block` carrying the new count.
101
+ * 3. Hash NOT in this block, no prior inclusion, but identity is
102
+ * cached AND a different hash with the same `(from, nonce)` is
103
+ * in this block → emit `replaced-by` with the replacement's
104
+ * block number.
105
+ * 4. Truly unseen → bump the unseen-block streak; emit
106
+ * `unseen-for-N-blocks` when the streak crosses the
107
+ * subscription's threshold. Does NOT emit on the first block
108
+ * after subscription (no `firstObservedAtBlock` yet).
109
+ */
110
+ export declare const decideBlockObservation: (input: BlockObservationInput) => ObservationResult;
111
+ /**
112
+ * Inputs to `decideMempoolObservation`. The orchestrator builds the
113
+ * hash-keyed snapshot index once per mempool tick.
114
+ */
115
+ export interface MempoolObservationInput {
116
+ record: ReadonlyTrackedRecord;
117
+ presence: {
118
+ bucket: 'pending' | 'queued';
119
+ tx: RawTx;
120
+ } | null;
121
+ /**
122
+ * The replacement candidate found in the snapshot for this record's
123
+ * `(from, nonce)` identity, or `null` if none. Computed by the
124
+ * orchestrator once per record so this function stays pure on
125
+ * inputs (no closure over the snapshot).
126
+ */
127
+ replacementInMempool: RawTx | null;
128
+ chainId: number;
129
+ eventSource: EventSource;
130
+ envelope: At;
131
+ /**
132
+ * The current canonical-tip block number the orchestrator is using
133
+ * for `firstObservedAtBlock` / `lastObservedAtBlock` book-keeping.
134
+ * Falls back to `0n` when no tip has been observed yet.
135
+ */
136
+ tipBlockNumber: bigint;
137
+ }
138
+ /**
139
+ * Per-record decision for one mempool snapshot. Three independent
140
+ * outputs that may all fire on the same call:
141
+ *
142
+ * - **Presence transition** — emit `seen-in-mempool` on first
143
+ * observation or bucket change; emit `left-mempool` when a
144
+ * previously-seen hash is absent from this snapshot.
145
+ * - **Replacement** — emit `replaced-by` (with `null` block) when
146
+ * the orchestrator's pre-computed `replacementInMempool` is set
147
+ * AND the record hasn't already recorded a replacement.
148
+ */
149
+ export declare const decideMempoolObservation: (input: MempoolObservationInput) => ObservationResult;
150
+ /**
151
+ * Find a tx in `txs` whose `(from, nonce)` matches `identity` but
152
+ * whose hash differs from `originalHash` — the replacement candidate
153
+ * for the original tracked tx. Compares senders case-insensitively
154
+ * since upstreams disagree on checksum form.
155
+ */
156
+ export declare const findReplacementInBlock: (identity: {
157
+ from: string;
158
+ nonce: string;
159
+ }, originalHash: Hash, txs: ReadonlyArray<RawTx>) => RawTx | null;
160
+ /**
161
+ * Cache the tx's `(from, nonce)` as the record's identity if it's
162
+ * not already cached AND the tx carries both fields. Returns the
163
+ * patch to apply (or `null` when no change).
164
+ */
165
+ export declare const cacheIdentity: (current: {
166
+ from: string;
167
+ nonce: string;
168
+ } | null, tx: RawTx) => IdentityPatch | null;
169
+ //# sourceMappingURL=observations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"observations.d.ts","sourceRoot":"","sources":["../src/observations.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAA;AAEtF,OAAO,EAML,KAAK,EAAE,EACP,KAAK,IAAI,EACT,KAAK,OAAO,EACZ,KAAK,QAAQ,EACd,MAAM,aAAa,CAAA;AAEpB;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,IAAI,EAAE,IAAI,CAAA;IACV,MAAM,EAAE,QAAQ,CAAA;IAChB,QAAQ,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAA;IAChD,qBAAqB,EAAE,OAAO,CAAA;IAC9B,qBAAqB,EAAE,MAAM,CAAA;CAC9B;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,EAAE,MAAM,CAAA;CACd;AAED;;;;;GAKG;AACH,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,OAAO,EAAE,CAAA;IACjB,WAAW,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC9B,aAAa,EAAE,aAAa,GAAG,IAAI,CAAA;IACnC,uEAAuE;IACvE,cAAc,EAAE,OAAO,GAAG,IAAI,CAAA;CAC/B;AAaD;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,qBAAqB,CAAA;IAC7B,SAAS,EAAE,IAAI,CAAA;IACf,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,WAAW,CAAC,IAAI,CAAC,CAAA;IAC5B,GAAG,EAAE,aAAa,CAAC,KAAK,CAAC,CAAA;IACzB,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,WAAW,CAAA;IACxB,QAAQ,EAAE,EAAE,CAAA;IACZ;;;;;OAKG;IACH,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;IAChC;;;;;;;;OAQG;IACH,kBAAkB,CAAC,EAAE,WAAW,CAAC,IAAI,EAAE,kBAAkB,CAAC,CAAA;CAC3D;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,sBAAsB,GACjC,OAAO,qBAAqB,KAC3B,iBAkJF,CAAA;AAMD;;;GAGG;AACH,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,qBAAqB,CAAA;IAC7B,QAAQ,EAAE;QAAE,MAAM,EAAE,SAAS,GAAG,QAAQ,CAAC;QAAC,EAAE,EAAE,KAAK,CAAA;KAAE,GAAG,IAAI,CAAA;IAC5D;;;;;OAKG;IACH,oBAAoB,EAAE,KAAK,GAAG,IAAI,CAAA;IAClC,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,WAAW,CAAA;IACxB,QAAQ,EAAE,EAAE,CAAA;IACZ;;;;OAIG;IACH,cAAc,EAAE,MAAM,CAAA;CACvB;AAED;;;;;;;;;;GAUG;AACH,eAAO,MAAM,wBAAwB,GACnC,OAAO,uBAAuB,KAC7B,iBAqFF,CAAA;AAMD;;;;;GAKG;AACH,eAAO,MAAM,sBAAsB,GACjC,UAAU;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EACzC,cAAc,IAAI,EAClB,KAAK,aAAa,CAAC,KAAK,CAAC,KACxB,KAAK,GAAG,IAUV,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,aAAa,GACxB,SAAS;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,EAC/C,IAAI,KAAK,KACR,aAAa,GAAG,IAIlB,CAAA"}