@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.
- package/AGENTS.md +237 -0
- package/CHANGELOG.md +140 -0
- package/README.md +13 -6
- package/dist/events.d.ts +309 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +132 -0
- package/dist/events.js.map +1 -0
- package/dist/group-events.d.ts +82 -0
- package/dist/group-events.d.ts.map +1 -0
- package/dist/group-events.js +47 -0
- package/dist/group-events.js.map +1 -0
- package/dist/group.d.ts +31 -0
- package/dist/group.d.ts.map +1 -0
- package/dist/group.js +196 -0
- package/dist/group.js.map +1 -0
- package/dist/index.d.ts +57 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +52 -1
- package/dist/index.js.map +1 -1
- package/dist/observations.d.ts +169 -0
- package/dist/observations.d.ts.map +1 -0
- package/dist/observations.js +287 -0
- package/dist/observations.js.map +1 -0
- package/dist/reorg.d.ts +108 -0
- package/dist/reorg.d.ts.map +1 -0
- package/dist/reorg.js +125 -0
- package/dist/reorg.js.map +1 -0
- package/dist/replace-transaction.d.ts +46 -0
- package/dist/replace-transaction.d.ts.map +1 -0
- package/dist/replace-transaction.js +47 -0
- package/dist/replace-transaction.js.map +1 -0
- package/dist/selectors.d.ts +78 -0
- package/dist/selectors.d.ts.map +1 -0
- package/dist/selectors.js +119 -0
- package/dist/selectors.js.map +1 -0
- package/dist/store.d.ts +166 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +110 -0
- package/dist/store.js.map +1 -0
- package/dist/tracker.d.ts +211 -0
- package/dist/tracker.d.ts.map +1 -0
- package/dist/tracker.js +1004 -0
- package/dist/tracker.js.map +1 -0
- package/dist/wait-for-pending.d.ts +41 -0
- package/dist/wait-for-pending.d.ts.map +1 -0
- package/dist/wait-for-pending.js +71 -0
- package/dist/wait-for-pending.js.map +1 -0
- package/dist/wait-for-transaction.d.ts +55 -0
- package/dist/wait-for-transaction.d.ts.map +1 -0
- package/dist/wait-for-transaction.js +72 -0
- package/dist/wait-for-transaction.js.map +1 -0
- package/dist/watch-transaction.d.ts +57 -0
- package/dist/watch-transaction.d.ts.map +1 -0
- package/dist/watch-transaction.js +76 -0
- package/dist/watch-transaction.js.map +1 -0
- package/package.json +6 -1
- package/skills/tx-tracker-integration/SKILL.md +198 -0
package/AGENTS.md
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
Terse reference for AI agents (Claude Code, Cursor, Aider, etc.) integrating
|
|
4
|
+
`@valve-tech/tx-tracker`. The full README is for humans; this file is for
|
|
5
|
+
agents that need to ground their work in the package's actual surface
|
|
6
|
+
quickly.
|
|
7
|
+
|
|
8
|
+
## What this package does
|
|
9
|
+
|
|
10
|
+
Per-tx state machine for EVM chains. Consumes a `ChainSource`'s block +
|
|
11
|
+
mempool stream and emits **neutral observations** (`seen-in-mempool`,
|
|
12
|
+
`seen-in-block`, `replaced-by`, `vanished-from-block`,
|
|
13
|
+
`unseen-for-N-blocks`, `signal-degraded`, `signal-recovered`,
|
|
14
|
+
`stopped`). Three consumption shapes (callback / async iterator /
|
|
15
|
+
snapshot) over one push-based core. Per-method capability disclosure
|
|
16
|
+
keeps the no-silent-downgrade rule.
|
|
17
|
+
|
|
18
|
+
Sibling to `@valve-tech/gas-oracle` — both consume the same
|
|
19
|
+
`ChainSource`, neither depends on the other. One upstream RPC poll
|
|
20
|
+
cycle can feed both.
|
|
21
|
+
|
|
22
|
+
`viem ^2.0.0` is the only external peer. `@valve-tech/chain-source`
|
|
23
|
+
is a runtime dependency.
|
|
24
|
+
|
|
25
|
+
## Public API
|
|
26
|
+
|
|
27
|
+
All exports live under `src/index.ts` (single subpath; no sub-exports).
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import {
|
|
31
|
+
createTxTracker, // primary constructor
|
|
32
|
+
createInMemoryStore, // default TxTrackerStore impl
|
|
33
|
+
computeRetentionExpiry, // pure helper
|
|
34
|
+
defaultRetentionBlocks, // 64
|
|
35
|
+
defaultReorgDepthBlocks, // 12
|
|
36
|
+
defaultMaxBulkSubscriptions, // 16
|
|
37
|
+
// pure detectors / matchers
|
|
38
|
+
appendBlock,
|
|
39
|
+
detectDivergences,
|
|
40
|
+
compileSelector,
|
|
41
|
+
matchAll,
|
|
42
|
+
// event builders (mostly internal — exported for store implementers)
|
|
43
|
+
buildStarted, buildSeenInMempool, buildLeftMempool,
|
|
44
|
+
buildSeenInBlock, buildVanishedFromBlock, buildReplacedBy,
|
|
45
|
+
buildUnseenForNBlocks, buildSignalDegraded, buildSignalRecovered,
|
|
46
|
+
buildStopped, buildInitialStatus,
|
|
47
|
+
// types
|
|
48
|
+
type TxTracker,
|
|
49
|
+
type CreateTxTrackerOptions,
|
|
50
|
+
type TrackOptions,
|
|
51
|
+
type BulkTrackOptions,
|
|
52
|
+
type TxMatchEvent,
|
|
53
|
+
type TxSubscription,
|
|
54
|
+
type LostSignalPolicy,
|
|
55
|
+
type TxEvent,
|
|
56
|
+
type TxEventStarted, type TxEventSeenInMempool, type TxEventLeftMempool,
|
|
57
|
+
type TxEventSeenInBlock, type TxEventVanishedFromBlock,
|
|
58
|
+
type TxEventReplacedBy, type TxEventUnseenForNBlocks,
|
|
59
|
+
type TxEventSignalDegraded, type TxEventSignalRecovered,
|
|
60
|
+
type TxEventStopped,
|
|
61
|
+
type TxStatus,
|
|
62
|
+
type Address, type Hash, type At, type Envelope,
|
|
63
|
+
// store types
|
|
64
|
+
type TxTrackerStore,
|
|
65
|
+
type TrackedTxRecord,
|
|
66
|
+
type PersistedSubscription,
|
|
67
|
+
type HashSelector,
|
|
68
|
+
type BulkSelector,
|
|
69
|
+
type InMemoryStoreOptions,
|
|
70
|
+
// reorg
|
|
71
|
+
type BlockSample,
|
|
72
|
+
type BlockDivergence,
|
|
73
|
+
// selectors
|
|
74
|
+
type CompiledSelector,
|
|
75
|
+
type BulkMatchPayload,
|
|
76
|
+
} from '@valve-tech/tx-tracker'
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Five types you must know
|
|
80
|
+
|
|
81
|
+
| Type | What it is |
|
|
82
|
+
|---|---|
|
|
83
|
+
| `CreateTxTrackerOptions` | Constructor config. Required: `source`, `chainId`. Tuneables: `store`, `lostSignalPolicy`, `reorgDepthBlocks`, `unseenThresholdBlocks`, `maxBulkSubscriptions`, `onError`, `lifecycle`. |
|
|
84
|
+
| `TxEvent` | Discriminated union of 10 variants (see below). Every variant carries `{ hash, chainId, source, at: { blockNumber, timestamp } }`. |
|
|
85
|
+
| `TxStatus` | Cached snapshot returned by `getTxStatus(hash)`. Carries the **last observation** (`lastSeenInBlock`, `lastSeenInMempool`, `replacedBy`, `vanishedAt`) plus housekeeping (`unseenStreak`, `firstObservedAtBlock`, `lastObservedAtBlock`, `capabilities`). |
|
|
86
|
+
| `TxTrackerStore` | Persistence surface. `put` / `get` / `delete` / `listDurable` / `appendEvent` / `readEventLog?`. Default: `createInMemoryStore`. |
|
|
87
|
+
| `BulkSelector` | `{ kind: 'from' \| 'to' \| 'predicate', address?, match? }`. From / to lowercase the address once at compile time. Predicate runs O(N) per tx per tick. |
|
|
88
|
+
|
|
89
|
+
## The discriminated `TxEvent`
|
|
90
|
+
|
|
91
|
+
Every event carries the same envelope; the `kind` field discriminates
|
|
92
|
+
the variant-specific payload.
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
type TxEvent =
|
|
96
|
+
| { kind: 'started'; capabilities: Capabilities }
|
|
97
|
+
| { kind: 'seen-in-mempool'; bucket: 'pending' | 'queued'; tx: RawTx }
|
|
98
|
+
| { kind: 'left-mempool' }
|
|
99
|
+
| { kind: 'seen-in-block'; blockHash; blockNumber; transactionIndex; confirmations }
|
|
100
|
+
| { kind: 'vanished-from-block'; previousBlockHash; canonicalBlockHash; blockNumber }
|
|
101
|
+
| { kind: 'replaced-by'; replacementHash; replacementBlockNumber: bigint | null }
|
|
102
|
+
| { kind: 'unseen-for-N-blocks'; blocks: number }
|
|
103
|
+
| { kind: 'signal-degraded'; capabilityLost: keyof Capabilities; fallbackSource }
|
|
104
|
+
| { kind: 'signal-recovered'; capabilityRestored: keyof Capabilities }
|
|
105
|
+
| { kind: 'stopped'; reason: 'unsubscribed' | 'retention-expired' | 'tracker-stopped' }
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
The envelope on every variant:
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
{
|
|
112
|
+
hash: Hash
|
|
113
|
+
chainId: number
|
|
114
|
+
source: 'subscription' | 'block-poll' | 'mempool-snapshot' | 'receipt-poll'
|
|
115
|
+
at: { blockNumber: bigint; timestamp: bigint }
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Three consumption shapes (consistent across all three)
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
// 1. Snapshot — sub-millisecond, returns null if not tracked
|
|
123
|
+
const status = tracker.getTxStatus(hash)
|
|
124
|
+
|
|
125
|
+
// 2. Callback — returns an unsubscribe handle
|
|
126
|
+
const unsub = tracker.subscribe(hash, (event) => { /* ... */ })
|
|
127
|
+
|
|
128
|
+
// 3. Async iterator — recommended for new code
|
|
129
|
+
for await (const event of tracker.track(hash)) {
|
|
130
|
+
if (event.kind === 'seen-in-block' && event.confirmations >= 6) break
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
All three back onto the same internal `Subscriptions<TxEvent>` per
|
|
135
|
+
hash, so they see consistent state.
|
|
136
|
+
|
|
137
|
+
## Bulk subscriptions (indexer-style)
|
|
138
|
+
|
|
139
|
+
```ts
|
|
140
|
+
const sub = tracker.trackFromAddress(treasuryAddress, { durable: true })
|
|
141
|
+
// raw match stream:
|
|
142
|
+
for await (const m of sub.events()) { /* m: TxMatchEvent */ }
|
|
143
|
+
// per-hash event stream (auto-tracked by default):
|
|
144
|
+
sub.subscribe((event) => { /* TxEvent */ })
|
|
145
|
+
sub.stop() // does NOT stop already-auto-tracked per-hash subs
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
`trackFromAddress` / `trackToAddress` / `trackPredicate`. Capped at
|
|
149
|
+
`maxBulkSubscriptions: 16` by default.
|
|
150
|
+
|
|
151
|
+
## Composing with gas-oracle
|
|
152
|
+
|
|
153
|
+
One `ChainSource` shared across both — one upstream RPC poll cycle:
|
|
154
|
+
|
|
155
|
+
```ts
|
|
156
|
+
import { createChainSource } from '@valve-tech/chain-source'
|
|
157
|
+
import { createGasOracle } from '@valve-tech/gas-oracle'
|
|
158
|
+
import { createTxTracker } from '@valve-tech/tx-tracker'
|
|
159
|
+
|
|
160
|
+
const source = createChainSource({ client })
|
|
161
|
+
const oracle = createGasOracle({ source, chainId: 1 })
|
|
162
|
+
const tracker = createTxTracker({ source, chainId: 1 })
|
|
163
|
+
|
|
164
|
+
source.start(); oracle.start(); tracker.start()
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
Each surface owns its own lifecycle — `oracle.stop()` does not stop
|
|
168
|
+
the source or the tracker.
|
|
169
|
+
|
|
170
|
+
## Configuration patterns
|
|
171
|
+
|
|
172
|
+
| Setting | Default | Tune up for | Tune down for |
|
|
173
|
+
|---|---|---|---|
|
|
174
|
+
| `reorgDepthBlocks` | 12 | Weak-finality chains (PoW, small validator sets) | High-finality chains; only care about shallow reorgs |
|
|
175
|
+
| `unseenThresholdBlocks` | 30 | Slow chains (Ethereum: ~6 min) | Fast L2s |
|
|
176
|
+
| `lostSignalPolicy` | `'emit-uncertain'` | (default — loud is correct) | `'silent'` for wallets that don't want capability-churn UI flicker |
|
|
177
|
+
| `createInMemoryStore({ retentionBlocks })` | 64 | Indexers replaying long windows | Wallet UIs |
|
|
178
|
+
| `createInMemoryStore({ eventLogCapacity })` | 256 | Heavy catch-up on restart | Memory-constrained mobile / edge |
|
|
179
|
+
|
|
180
|
+
`reorgDepthBlocks` and retention are in **block-units, not seconds** —
|
|
181
|
+
reorg safety is a depth invariant. Spec §10.1.
|
|
182
|
+
|
|
183
|
+
## Wire format
|
|
184
|
+
|
|
185
|
+
All numeric fields are `bigint` (block numbers, fees, timestamps).
|
|
186
|
+
`JSON.stringify(event)` will throw without hex-encoding at the wire
|
|
187
|
+
boundary. Durable store implementers MUST hex-encode (`'0x' + n.toString(16)`)
|
|
188
|
+
on write and decode on read. The default in-memory store keeps `bigint`
|
|
189
|
+
end-to-end.
|
|
190
|
+
|
|
191
|
+
## Capability disclosure
|
|
192
|
+
|
|
193
|
+
`tracker.capabilities()` forwards the source's snapshot:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
{
|
|
197
|
+
newHeads: 'subscription' | 'poll-only' | 'unavailable'
|
|
198
|
+
newPendingTransactions: 'subscription' | 'poll-only' | 'unavailable'
|
|
199
|
+
txpoolContent: 'available' | 'gated'
|
|
200
|
+
receiptByHash: 'available' | 'unavailable'
|
|
201
|
+
reprobeOnReconnect: boolean
|
|
202
|
+
}
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
When capabilities change mid-tracking, the tracker emits
|
|
206
|
+
`signal-degraded` / `signal-recovered` per affected key. Consumers that
|
|
207
|
+
need hard inclusion guarantees filter to `event.source === 'subscription'`.
|
|
208
|
+
|
|
209
|
+
## Examples
|
|
210
|
+
|
|
211
|
+
- `examples/07-tx-tracker.ts` — minimal tracker, no oracle (async iterator)
|
|
212
|
+
- `examples/08-tx-tracker-with-oracle.ts` — shared `ChainSource` between gas-oracle + tracker
|
|
213
|
+
- `examples/09-bulk-from-address.ts` — indexer-style bulk subscription
|
|
214
|
+
|
|
215
|
+
(Examples live under `node_modules/@valve-tech/gas-oracle/examples/` —
|
|
216
|
+
the toolkit's examples directory is hosted by gas-oracle.) Run with
|
|
217
|
+
`yarn tsx examples/07-tx-tracker.ts`.
|
|
218
|
+
|
|
219
|
+
## Skills (for AI agents)
|
|
220
|
+
|
|
221
|
+
`skills/` directory ships in the npm tarball. If you're an AI agent
|
|
222
|
+
working in a project that has installed this package, look in
|
|
223
|
+
`node_modules/@valve-tech/tx-tracker/skills/tx-tracker-integration/SKILL.md`
|
|
224
|
+
for trigger conditions and integration recipes that go deeper than this
|
|
225
|
+
file.
|
|
226
|
+
|
|
227
|
+
## Verifying provenance
|
|
228
|
+
|
|
229
|
+
v0.6.0+ ships with SLSA provenance attestation:
|
|
230
|
+
|
|
231
|
+
```bash
|
|
232
|
+
npm view @valve-tech/tx-tracker@latest --json | jq .dist.attestations
|
|
233
|
+
npm audit signatures
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
The attestation links the published tarball to the GitHub Actions
|
|
237
|
+
workflow run that built it.
|
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,146 @@ this file.
|
|
|
6
6
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
7
7
|
and this project adheres to [Semantic Versioning](https://semver.org/).
|
|
8
8
|
|
|
9
|
+
## [0.8.0] — 2026-05-06
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `lostSignalPolicy: { strategy: 'receipt-poll-fallback', pollEveryBlocks: N }` runtime. Closes the type-vs-runtime gap from v0.3.x — when a tracked subscription is in a degraded state, the tracker fetches `getReceipt` every N block ticks and emits `seen-in-block` with `source: 'receipt-poll'` on hit. Capability gate: requires `receiptByHash === 'available'`.
|
|
13
|
+
- `withReceipts: true` opt-in receipt enrichment on `TrackOptions`. When set, the tracker pre-fetches the receipt before the per-record block decision and attaches it to `seen-in-block` events via the new `TxEventSeenInBlock.receipt` field. One emit per inclusion — receipt is on the first event, not a follow-up.
|
|
14
|
+
- `tracker.group(hashes, options?)` — cross-tx correlation (spec §18.1). Emits `group-progress` / `group-complete` / `group-failed` / `group-stopped` derived from per-member event streams. Replacement does NOT auto-promote.
|
|
15
|
+
- `watchTransaction({ client, hash, ... })` — one-shot callback convenience export.
|
|
16
|
+
- `waitForTransaction({ client, hash, ... })` — Promise variant of `watchTransaction`. Resolves with discriminated-union outcome (`mined` / `dropped` / `replaced` / `failed`).
|
|
17
|
+
- `waitForPending({ client, hash, timeoutBlocks })` — Promise that resolves on first `seen-in-mempool`; rejects with typed `WaitForPendingTimeoutError` if the hash isn't observed within `timeoutBlocks`.
|
|
18
|
+
- `replaceTransaction({ original, walletClient, newGas })` — same-nonce replacement primitive. Caller-provides-newGas keeps tx-tracker independent of `@valve-tech/gas-oracle`.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
- `onBlock` is now async to support pre-fetching receipts before the per-record decision. Stale-block guard added against the resulting interleave window so a delayed pre-fetch can't clobber state advanced by a concurrent block tick.
|
|
22
|
+
- `decideBlockObservation` accepts an optional `prefetchedReceipts: ReadonlyMap<Hash, TransactionReceipt>` parameter (backward-compatible — existing callsites omit it).
|
|
23
|
+
|
|
24
|
+
## [0.7.0] — 2026-05-06
|
|
25
|
+
|
|
26
|
+
> **The implementation lands.** This is the first release of
|
|
27
|
+
> `@valve-tech/tx-tracker` with a real public surface. Prior versions
|
|
28
|
+
> (v0.0.1 → v0.6.0) were stubs reserving the npm name. The full
|
|
29
|
+
> design contract is at `docs/tx-tracker-spec.md` in the
|
|
30
|
+
> `valve-tech/evm-toolkit` repo.
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
|
|
34
|
+
- **Per-tx state machine** (`createTxTracker`) consuming a
|
|
35
|
+
`ChainSource` for upstream block + mempool signals. Three
|
|
36
|
+
consumption shapes over one push-based core: `getTxStatus(hash)`
|
|
37
|
+
for the cached snapshot, `subscribe(hash, cb)` for callback-style,
|
|
38
|
+
and `track(hash)` for the async-iterator shape — all three back
|
|
39
|
+
onto the same internal stream so they see consistent state.
|
|
40
|
+
- **`TxEvent` discriminated union** (spec §6) with neutral
|
|
41
|
+
observation kinds: `started`, `seen-in-mempool`, `left-mempool`,
|
|
42
|
+
`seen-in-block`, `vanished-from-block`, `replaced-by`,
|
|
43
|
+
`unseen-for-N-blocks`, `signal-degraded`, `signal-recovered`,
|
|
44
|
+
`stopped`. Every event carries an envelope (`hash`, `chainId`,
|
|
45
|
+
`source`, `at: { blockNumber, timestamp }`) so consumers can
|
|
46
|
+
apply policy (`'confirmed'`, `'stuck'`, etc.) in their own UX
|
|
47
|
+
voice without the tracker prejudging.
|
|
48
|
+
- **`TxTrackerStore` interface + `createInMemoryStore` default**
|
|
49
|
+
(spec §9, §10). Block-unit retention (`retentionBlocks: 64` by
|
|
50
|
+
default — reorg safety is a depth invariant, not a wall-clock
|
|
51
|
+
invariant), bounded per-hash audit log (`eventLogCapacity: 256`
|
|
52
|
+
by default) for catch-up replay.
|
|
53
|
+
- **Reorg detector** (`detectDivergences`, spec §12) — pure function
|
|
54
|
+
over `BlockSample[]` that flags same-height different-hash
|
|
55
|
+
divergences within `reorgDepthBlocks` (default 12). Ring is
|
|
56
|
+
conservative about heights with no canonical entry — a partial
|
|
57
|
+
canonical sequence does not nuke unrelated ring entries.
|
|
58
|
+
- **Bulk subscriptions** (spec §11): `trackFromAddress`,
|
|
59
|
+
`trackToAddress`, `trackPredicate`. Auto-tracks matched hashes
|
|
60
|
+
by default (`autoTrackMatched: true`) so the per-hash event
|
|
61
|
+
stream is available too. Capped at `maxBulkSubscriptions: 16`.
|
|
62
|
+
- **Capability disclosure** — `tracker.capabilities()` forwards the
|
|
63
|
+
source's snapshot. `signal-degraded` / `signal-recovered` events
|
|
64
|
+
fire on every tracked hash when source-level capability
|
|
65
|
+
transitions cross authority boundaries.
|
|
66
|
+
- **Replacement detection** — caches `(from, nonce)` on first
|
|
67
|
+
observation and emits `replaced-by` when a different hash with
|
|
68
|
+
the same identity appears (mempool: `replacementBlockNumber: null`;
|
|
69
|
+
block: filled-in block number).
|
|
70
|
+
- **`subscribeAll(cb)`** — global stream of every event the tracker
|
|
71
|
+
emits, useful for indexers piping to a single sink.
|
|
72
|
+
- **`AGENTS.md`** + **`skills/tx-tracker-integration/SKILL.md`** for AI
|
|
73
|
+
agents working in downstream projects that import the package. Both
|
|
74
|
+
ship in the npm tarball; the SKILL.md trigger phrases catch
|
|
75
|
+
"track this transaction," "watch tx hash," "stuck transaction," and
|
|
76
|
+
composition questions with `@valve-tech/gas-oracle`.
|
|
77
|
+
|
|
78
|
+
### Changed
|
|
79
|
+
|
|
80
|
+
- **Coverage hardening pre-1.0.** Eliminated dead defensive branches
|
|
81
|
+
in `reorg.ts` (sort-comparator equal-key arms unreachable after
|
|
82
|
+
the dedup `filter`; `?? 0n` defaults unreachable after the empty-
|
|
83
|
+
array early return). Tightened `capabilityRank`'s input type from
|
|
84
|
+
`string` to a `CapabilityValue` union literal so the switch is
|
|
85
|
+
exhaustive without a default arm.
|
|
86
|
+
- **Test suite up from 75 → 95 tests** (+20). New coverage:
|
|
87
|
+
`trackToAddress`, per-subscription `lostSignalPolicy` overrides,
|
|
88
|
+
durable-subscription persistence (with stub stores), predicate-
|
|
89
|
+
selector + `durable: true` warning, store.appendEvent / store.put
|
|
90
|
+
failure routing through `onError`, bad-block-number handling,
|
|
91
|
+
async iterator queue-vs-waiter ordering and early-break cleanup,
|
|
92
|
+
bulk async iterator drain via `sub.stop()`, multi-sub-on-same-hash
|
|
93
|
+
cleanup semantics, idempotent `stop` / `unsub` / `sub.stop`,
|
|
94
|
+
reorg handler skipping records without `lastSeenInBlock`,
|
|
95
|
+
`findReplacement` raw-nonce fallback when `BigInt()` throws,
|
|
96
|
+
`lifecycle: 'lazy'` accepts-the-option contract.
|
|
97
|
+
- Coverage went **89.23% / 78.59% / 92.13% / 91.84%** stmts / branches
|
|
98
|
+
/ funcs / lines → **96.13% / 88.93% / 98.87% / 97.61%**, then to
|
|
99
|
+
**97.22% / 92.7% / 98.9% / 98.12%** after the per-record decision
|
|
100
|
+
logic was extracted into pure functions (see "Refactor" below).
|
|
101
|
+
|
|
102
|
+
### Refactor
|
|
103
|
+
|
|
104
|
+
- **Per-record decision logic extracted from `tracker.ts` into a new
|
|
105
|
+
pure module `observations.ts`.** The previous shape — two giant
|
|
106
|
+
closures inside `onBlock` / `onMempool` mutating shared state and
|
|
107
|
+
emitting events as a side effect — was a pile of conditionals that
|
|
108
|
+
could only be tested by spinning up the full state machine through
|
|
109
|
+
a stub source. Now `decideBlockObservation` and
|
|
110
|
+
`decideMempoolObservation` are pure functions: literal inputs in,
|
|
111
|
+
`{ events, statusPatch, identityPatch, inMempoolPatch }` out. The
|
|
112
|
+
orchestrator in `tracker.ts` shrank to "compute envelope, loop
|
|
113
|
+
records, call decision fn, merge patch, emit events." Same shape
|
|
114
|
+
as the rest of the toolkit (`reducePollInputs` pure / poll loop
|
|
115
|
+
stateful in gas-oracle; math pure / source stateful in chain-source).
|
|
116
|
+
- **`observations.ts` lands at 100% statements / 100% branches**
|
|
117
|
+
(67/67 stmts, 55/55 branches) covered by 33 fixture-driven unit
|
|
118
|
+
tests in `observations.test.ts`. Each per-record decision arm
|
|
119
|
+
has a dedicated test with literal inputs — no async, no stubs,
|
|
120
|
+
no shared state.
|
|
121
|
+
- `tracker.ts` shrank from 374 statements → 344 (the extracted
|
|
122
|
+
code is gone) and is now mostly orchestration; its branch
|
|
123
|
+
coverage rose from 86.69% → 88.75%.
|
|
124
|
+
- `findReplacement` (closure-based) replaced with pure
|
|
125
|
+
`findReplacementInMempool(snapshot, identity, originalHash)`.
|
|
126
|
+
`cacheIdentityFromTx` (mutation-based) replaced with pure
|
|
127
|
+
`cacheIdentity(current, tx)` returning a patch.
|
|
128
|
+
- **No behavior change.** All 95 pre-refactor integration tests
|
|
129
|
+
continue to pass unchanged; the refactor is internal-only and
|
|
130
|
+
the public API surface is identical.
|
|
131
|
+
- Tracker test suite: 95 → 133 (+38: 33 from `observations.test.ts`
|
|
132
|
+
plus 5 new tracker integration tests covering reorg height-mismatch
|
|
133
|
+
skip and async-iterator multi-waiter drain paths).
|
|
134
|
+
|
|
135
|
+
### Notes
|
|
136
|
+
|
|
137
|
+
- Implements spec §5–§12 minus the `'receipt-poll-fallback'`
|
|
138
|
+
lostSignalPolicy strategy (the type is accepted; the runtime
|
|
139
|
+
falls back to `'emit-uncertain'` and a follow-up PR adds the
|
|
140
|
+
per-block receipt fetch path).
|
|
141
|
+
- Predicate bulk selectors are silently non-durable per spec §13.2
|
|
142
|
+
(closures don't survive a process boundary). The tracker logs a
|
|
143
|
+
warning via `onError` when a `predicate` selector is registered
|
|
144
|
+
with `durable: true` and persists everything else about the
|
|
145
|
+
selector.
|
|
146
|
+
- `gas-oracle` and `tx-tracker` remain siblings — neither imports
|
|
147
|
+
the other; both consume `@valve-tech/chain-source` directly.
|
|
148
|
+
|
|
9
149
|
## [0.6.0] — 2026-05-05
|
|
10
150
|
|
|
11
151
|
### Notes
|
package/README.md
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
# @valve-tech/tx-tracker
|
|
2
2
|
|
|
3
|
-
> **Status: stub (v0.0.1).** This package is a name reservation. The
|
|
4
|
-
> implementation lands in v0.1.0. See
|
|
5
|
-
> [`docs/tx-tracker-spec.md`](https://github.com/valve-tech/evm-toolkit/blob/main/docs/tx-tracker-spec.md)
|
|
6
|
-
> for the full design contract.
|
|
7
|
-
|
|
8
3
|
Per-tx state machine for EVM chains. Emits **neutral observations** —
|
|
9
4
|
`seen-in-mempool`, `seen-in-block`, `replaced-by`, `vanished-from-block`,
|
|
10
5
|
`unseen-for-N-blocks`, `signal-degraded`, `signal-recovered`, `stopped` —
|
|
@@ -12,8 +7,11 @@ so wallet UIs, indexers, and relays can write their own interpretations
|
|
|
12
7
|
on top. The package itself never says "confirmed" or "stuck"; it gives
|
|
13
8
|
you the data to decide.
|
|
14
9
|
|
|
10
|
+
See
|
|
11
|
+
[`docs/tx-tracker-spec.md`](https://github.com/valve-tech/evm-toolkit/blob/main/docs/tx-tracker-spec.md)
|
|
12
|
+
for the full design contract.
|
|
13
|
+
|
|
15
14
|
```ts
|
|
16
|
-
// v0.1.0+ shape (not yet implemented):
|
|
17
15
|
import { createChainSource } from '@valve-tech/chain-source'
|
|
18
16
|
import { createTxTracker } from '@valve-tech/tx-tracker'
|
|
19
17
|
|
|
@@ -51,6 +49,15 @@ adapters (callback / async iterator / snapshot).
|
|
|
51
49
|
yarn add @valve-tech/tx-tracker @valve-tech/chain-source viem
|
|
52
50
|
```
|
|
53
51
|
|
|
52
|
+
## For AI agents
|
|
53
|
+
|
|
54
|
+
This package ships an [`AGENTS.md`](AGENTS.md) reference and a
|
|
55
|
+
[`skills/`](skills/) directory for Claude Code / Cursor skill files
|
|
56
|
+
shipped in the npm tarball. After install, both are reachable at:
|
|
57
|
+
|
|
58
|
+
- `node_modules/@valve-tech/tx-tracker/AGENTS.md`
|
|
59
|
+
- `node_modules/@valve-tech/tx-tracker/skills/tx-tracker-integration/SKILL.md`
|
|
60
|
+
|
|
54
61
|
## License
|
|
55
62
|
|
|
56
63
|
MIT
|