@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,198 @@
1
+ ---
2
+ name: tx-tracker-integration
3
+ description: Integrate `@valve-tech/tx-tracker` into a wallet UI, indexer, or relay. Use when the user wants to "track this transaction," "watch tx hash," "know when my tx confirms," "detect stuck transactions," "watch for replaced txs," "follow this address's txs," or asks about reorg / replacement / dropped-tx detection on EVM. Also use when seeing imports from `@valve-tech/tx-tracker` and the user asks for help with `createTxTracker`, `track`, `subscribe`, `getTxStatus`, `trackFromAddress` / `trackToAddress` / `trackPredicate`, `TxEvent`, `TxTrackerStore`, `createInMemoryStore`, `lostSignalPolicy`, or capability disclosure (`subscription` / `block-poll` / `mempool-snapshot` / `receipt-poll` event sources). Also applies when the user asks how to compose tx-tracker with `@valve-tech/gas-oracle` via a shared `ChainSource`.
4
+ ---
5
+
6
+ # Integrating `@valve-tech/tx-tracker`
7
+
8
+ Per-tx state machine for EVM chains. Emits **neutral observations**
9
+ (`seen-in-mempool`, `seen-in-block`, `replaced-by`,
10
+ `vanished-from-block`, `unseen-for-N-blocks`, `signal-degraded`,
11
+ `signal-recovered`, `stopped`) so the consumer writes the
12
+ `'confirmed'` / `'stuck'` / `'dropped'` policy in their own UX voice.
13
+
14
+ This skill is for AI agents working in a project that imports the
15
+ package — it grounds you in the right consumption shape for the user's
16
+ codebase and the right configuration for their use case.
17
+
18
+ ## Decision tree: which consumption shape to use
19
+
20
+ Three consumption shapes. All three back onto one push-based core, so
21
+ they see consistent state — pick by ergonomics, not by capability.
22
+
23
+ ```
24
+ Is the user writing new async code (top-to-bottom flow with await)?
25
+ ├── Yes — use `for await (const event of tracker.track(hash)) { ... }`.
26
+ │ Recommended for new code. Break on terminal conditions inline.
27
+ └── No — does the user have existing event-handler / callback code that
28
+ already manages subscription handles?
29
+ ├── Yes — use `tracker.subscribe(hash, cb)`. Returns an
30
+ │ unsubscribe handle. Matches the shape of viem's
31
+ │ watchBlockNumber / watchEvent.
32
+ └── No — they want the cached snapshot for an imperative read?
33
+ Use `tracker.getTxStatus(hash)`. Returns null if the
34
+ hash isn't currently tracked. Sub-millisecond; do NOT
35
+ call in a render loop, subscribe instead.
36
+ ```
37
+
38
+ ## Decision tree: which selector for bulk subscription
39
+
40
+ ```
41
+ The user wants to watch every tx from / to / matching some criterion:
42
+ ├── Single sender (treasury, relayer, factory)
43
+ │ → tracker.trackFromAddress(addr)
44
+ ├── Single recipient (contract, EOA)
45
+ │ → tracker.trackToAddress(addr)
46
+ └── Arbitrary predicate (gas-price band, calldata pattern, value range)
47
+ → tracker.trackPredicate((tx) => /* boolean */)
48
+ NOTE: predicate runs O(N) per tx per tick. Keep it fast.
49
+ NOTE: predicate selectors are non-durable (closures don't
50
+ serialize). `from` / `to` selectors ARE durable.
51
+ ```
52
+
53
+ `autoTrackMatched: true` (default) creates an implicit per-hash
54
+ subscription on every matched hash, so the consumer can use
55
+ `sub.subscribe(cb)` to get the per-hash event stream too. Set
56
+ `false` if the consumer only wants the raw `matched` stream.
57
+
58
+ ## Composing with gas-oracle (one upstream RPC stream)
59
+
60
+ When the user has BOTH gas-oracle and tx-tracker, they should share
61
+ ONE `ChainSource`. One upstream poll cycle, two derived views:
62
+
63
+ ```ts
64
+ import { createChainSource } from '@valve-tech/chain-source'
65
+ import { createGasOracle } from '@valve-tech/gas-oracle'
66
+ import { createTxTracker } from '@valve-tech/tx-tracker'
67
+
68
+ const source = createChainSource({ client })
69
+ const oracle = createGasOracle({ source, chainId: 1 })
70
+ const tracker = createTxTracker({ source, chainId: 1 })
71
+
72
+ source.start(); oracle.start(); tracker.start()
73
+ ```
74
+
75
+ Each surface owns its own lifecycle. `oracle.stop()` does NOT stop the
76
+ source or the tracker. `source.stop()` halts the upstream loop;
77
+ attached consumers stop receiving events but their subscriptions stay
78
+ registered (a later `source.start()` resumes them).
79
+
80
+ ## Per-chain config (always required)
81
+
82
+ | Setting | Default | Tune up for | Tune down for |
83
+ |---|---|---|---|
84
+ | `reorgDepthBlocks` | 12 | Chains with weaker finality (PoW, small validator sets) | High-finality chains where you only care about shallow reorgs |
85
+ | `unseenThresholdBlocks` (per-sub or tracker default) | 30 | Slow chains (Ethereum mainnet — ~6 min) | Fast L2s where 30 blocks ≪ 1 minute |
86
+ | `lostSignalPolicy` | `'emit-uncertain'` | (default — loud is correct) | `'silent'` for wallet UIs that don't want capability-churn UI flicker |
87
+ | Store retention (`createInMemoryStore({ retentionBlocks })`) | 64 | Indexers replaying long windows | Wallet UIs where in-flight is what matters |
88
+
89
+ `reorgDepthBlocks` and retention are in **block-units, not seconds** —
90
+ reorg safety is a depth invariant. Spec §10.1 has the rationale.
91
+
92
+ ## Anti-patterns to flag
93
+
94
+ 1. **Constructing a fresh `ChainSource` per tracker per hash.** One
95
+ source per chain, shared across every consumer. Constructing a
96
+ second source for the same chain doubles the upstream RPC traffic.
97
+
98
+ 2. **Treating `seen-in-block` as "confirmed."** It's the inclusion
99
+ observation, not the policy. Consumer should check
100
+ `event.confirmations >= N` with N from their own UX rules. The
101
+ tracker deliberately does not emit a `confirmed` event.
102
+
103
+ 3. **Calling `getTxStatus(hash)` in a render loop.** Sub-ms but still
104
+ wasteful. Subscribe via `tracker.subscribe(hash, cb)` and store
105
+ the latest event in a state hook / module variable.
106
+
107
+ 4. **Ignoring `signal-degraded` events** when the consumer's UX
108
+ depends on hard inclusion guarantees (relays, settlement). The
109
+ default policy emits these for a reason — when WS drops mid-track,
110
+ the receipt-poll fallback is informational only and cannot detect
111
+ reorgs.
112
+
113
+ 5. **`durable: true` on a `predicate` selector.** Closures don't
114
+ serialize; the tracker silently demotes to non-durable and logs
115
+ via `onError`. Use `from` / `to` selectors when durability matters.
116
+
117
+ 6. **Polling `getTxStatus` to detect changes.** If you find yourself
118
+ in a `setInterval` reading the snapshot, you wanted `subscribe`
119
+ from the start.
120
+
121
+ 7. **Stopping the tracker without unsubscribing per-hash callbacks
122
+ first.** `tracker.stop()` emits a final `stopped` event to every
123
+ per-hash subscriber, then drops the records. That's the intended
124
+ shape — but consumers expecting their `subscribe` callback to
125
+ never fire after their own `unsub()` should call `unsub()` first
126
+ (it's idempotent and emits its own `stopped` with reason
127
+ `'unsubscribed'`).
128
+
129
+ 8. **Reading `event.at.timestamp === 0n` and treating it as "now."**
130
+ `0n` means "no canonical block has been observed yet" (the
131
+ subscription's synthetic `started` event fires before any block
132
+ tick). Wait for a real event before reading `timestamp`.
133
+
134
+ ## Capability disclosure — the no-silent-downgrade rule
135
+
136
+ Every event carries a `source` field. When upstream RPC capability
137
+ changes (WS drops, `txpool_content` newly gated), the tracker emits
138
+ `signal-degraded` with `capabilityLost` and `fallbackSource`. Consumers
139
+ that need hard guarantees filter to `event.source === 'subscription'`.
140
+
141
+ `tracker.capabilities()` returns the source's current snapshot. Use
142
+ this on subscribe to decide your fallback posture upfront rather than
143
+ reacting to the first `signal-degraded`.
144
+
145
+ ## How to recognize this package in the user's code
146
+
147
+ ```ts
148
+ import { createTxTracker } from '@valve-tech/tx-tracker'
149
+ import { createInMemoryStore } from '@valve-tech/tx-tracker'
150
+ import type { TxEvent, TxStatus } from '@valve-tech/tx-tracker'
151
+
152
+ // Composing with gas-oracle:
153
+ import { createChainSource } from '@valve-tech/chain-source'
154
+ import { createGasOracle } from '@valve-tech/gas-oracle'
155
+ ```
156
+
157
+ `package.json` will show `"@valve-tech/tx-tracker": "^0.x.y"` in
158
+ dependencies, and almost always `"@valve-tech/chain-source"` alongside
159
+ it (the tracker requires a source).
160
+
161
+ ## Speed-up workflow (cross-package)
162
+
163
+ For callers tracking a tx via `@valve-tech/tx-tracker` who want to bump
164
+ it when it stalls or drops, pair with `@valve-tech/gas-oracle`'s
165
+ `recommendBumpTier` + `bumpForReplacement` helpers:
166
+
167
+ ```ts
168
+ import { recommendBumpTier, bumpForReplacement } from '@valve-tech/gas-oracle'
169
+
170
+ // tx-tracker tells you the tx is stuck:
171
+ tracker.on('stuck', (stuck) => {
172
+ const tier = recommendBumpTier(
173
+ gasOracleState,
174
+ { priorityTip: stuck.maxPriorityFeePerGas, identifier: { hash: stuck.hash } },
175
+ )
176
+ if (tier === null) return // Already paying above top tier — caller's call
177
+
178
+ const target = gasOracleState.tiers[tier]
179
+ const gas = bumpForReplacement(
180
+ { maxFeePerGas: stuck.maxFeePerGas, maxPriorityFeePerGas: stuck.maxPriorityFeePerGas },
181
+ { maxFeePerGas: target.maxFeePerGas, maxPriorityFeePerGas: target.maxPriorityFeePerGas },
182
+ )
183
+ walletClient.sendTransaction({ ...stuck, ...gas })
184
+ })
185
+ ```
186
+
187
+ Outpace correction (passing `identifier`) reads `gasOracleState.mempoolSamples`
188
+ to compute the tip needed to outpace the stuck tx in the live
189
+ distribution, on top of the EIP-1559 +10% protocol floor.
190
+
191
+ ## Where to find more
192
+
193
+ - Full API + types: `node_modules/@valve-tech/tx-tracker/AGENTS.md`
194
+ - Runnable examples: `node_modules/@valve-tech/gas-oracle/examples/07-tx-tracker.ts` etc.
195
+ - Design contract (the source of truth): `docs/tx-tracker-spec.md` in the
196
+ `valve-tech/evm-toolkit` repo
197
+ - Source (when types alone aren't enough):
198
+ `node_modules/@valve-tech/tx-tracker/dist/` (compiled JS + .d.ts)