@valve-tech/chain-source 0.5.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -6,6 +6,99 @@ 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.7.0] — 2026-05-06
10
+
11
+ ### Notes
12
+
13
+ - Synchronized release — no consumer-visible changes to this package.
14
+ Bumped in lockstep with `@valve-tech/tx-tracker@0.7.0`, the
15
+ long-promised v0.3.x track ChainSource was built to feed (per
16
+ `docs/tx-tracker-spec.md` §3.1). chain-source itself stays
17
+ byte-identical with v0.6.0's dist; this release exists so
18
+ consumers running `npm view @valve-tech/chain-source versions`
19
+ see the toolkit-wide line is at v0.7.0.
20
+ - Internal-only: the workspace test suite is now at 100/100/100/100
21
+ coverage on this package (was 97/97/97/98). No new exports, no
22
+ behavior change.
23
+
24
+ ## [0.6.0] — 2026-05-05
25
+
26
+ ### Added
27
+
28
+ - **`createChainSource({ client, pollIntervalMs?, poll?, onError? })`** —
29
+ the canonical `ChainSource` primitive lands. Implements the
30
+ `subscribe / on-demand / capabilities / lifecycle` contract from
31
+ [`docs/tx-tracker-spec.md`](https://github.com/valve-tech/evm-toolkit/blob/main/docs/tx-tracker-spec.md)
32
+ §3.2.
33
+ - **`subscribeBlocks(cb)` / `subscribeMempool(cb)`** — first-class
34
+ multi-subscriber streams. One upstream poll cycle fans out to every
35
+ attached subscriber. Returns an idempotent unsubscribe handle.
36
+ - **On-demand RPCs:** `getBlock(tag)`, `getFeeHistory(blockCount, percentiles)`,
37
+ `getMempoolSnapshot()` (returns the normalized form), `getReceipt(hash)`,
38
+ `getTransaction(hash)`. Each follows the `safeRequest`-returns-null
39
+ posture — never throws across the boundary.
40
+ - **`probeCapabilities(client)`** + cached `capabilities()` accessor.
41
+ The probe runs eagerly at construction; `await source.ready()`
42
+ guarantees the cached snapshot is populated before reading.
43
+ Per-method discrimination (`newHeads` / `newPendingTransactions` /
44
+ `txpoolContent` / `receiptByHash`) honors the "no silent downgrade"
45
+ invariant from §2.2 of the spec.
46
+ - **`Subscriptions<E>`** — hand-rolled typed pub/sub primitive,
47
+ browser/mobile-safe (no Node `events` dependency). Per-subscriber
48
+ throws are swallowed so a single bad consumer cannot affect
49
+ delivery to the others; emit fans out to a snapshot of subscribers
50
+ taken at the start of the call so mid-emit registration changes
51
+ are deferred to the next emit.
52
+ - **Type re-exports** of `BlockResult`, `Capabilities`, `EventSource`,
53
+ `FeeHistoryResult`, `NormalizedMempool`, `PollOptions`, `RawTx`,
54
+ `TransactionReceipt`, `TxPoolContent`. These are the wire-format
55
+ contracts used by `@valve-tech/gas-oracle` and `@valve-tech/tx-tracker`
56
+ in their v0.3.x migrations.
57
+ - **Additive method on the spec'd interface: `pollOnce()`**. Drives one
58
+ cycle out-of-band; useful for serverless / manual-refresh flows and
59
+ for deterministic test setups that don't want to engage fake
60
+ timers.
61
+
62
+ ### Changed
63
+
64
+ - **`subscribeBlocks` now dedups by `block.hash`.** Previously, every
65
+ successful tick fanned out to block subscribers regardless of
66
+ whether the head had advanced; on a static head that meant an emit
67
+ every `pollIntervalMs`. The stream now only emits when the observed
68
+ block hash differs from the last one delivered. Hash-based (not
69
+ number-based) so a same-height reorg surfaces as a fresh
70
+ observation. Dedup state resets on `stop()` — a paused-then-resumed
71
+ source emits a current snapshot to its consumers on first re-tick
72
+ rather than waiting for the next chain block. `subscribeMempool` is
73
+ intentionally not deduped — txs come and go between blocks even on
74
+ a static head, so every successful snapshot remains fresh data.
75
+ - **The poll cycle now head-probe-gates the full block fetch.** Each
76
+ tick runs a cheap `eth_blockNumber` probe in parallel with the
77
+ mempool fetch; only when the probe shows the head has advanced (or
78
+ the probe failed and we're falling through defensively) does the
79
+ cycle issue the expensive `eth_getBlockByNumber('latest', true)`
80
+ (1–5MB on busy chains). On a static head with the default 10 s
81
+ interval, the per-tick RPC weight drops from "full block + mempool
82
+ + probe" to "probe + mempool". Mempool fetch still runs every
83
+ cycle. Closes the efficiency-regression risk for the upcoming
84
+ gas-oracle migration to consume `ChainSource` (the soon-to-be-
85
+ deprecated `gas-oracle.blockGatedPolling` option had this behavior
86
+ in v0.5.0; it now lives at the source layer where every consumer
87
+ benefits).
88
+
89
+ ### Notes
90
+
91
+ - WebSocket push subscriptions (`eth_subscribe('newHeads')` /
92
+ `eth_subscribe('newPendingTransactions')`) are not yet wired in this
93
+ release. The capability probe discloses what the transport
94
+ *structurally* supports, but the source always uses its interval
95
+ poll cycle in this revision. A future release adds the push path
96
+ without changing the consumer-facing surface.
97
+ - `gas-oracle` and `tx-tracker` migrations to consume `ChainSource`
98
+ land in subsequent PRs of the same v0.3.x track. Until those merge,
99
+ this package is consumable directly but its sibling packages keep
100
+ their v0.3.0 standalone behavior.
101
+
9
102
  ## [0.5.0] — 2026-05-05
10
103
 
11
104
  ### Notes
package/README.md CHANGED
@@ -1,10 +1,5 @@
1
1
  # @valve-tech/chain-source
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
- > §3 for the design contract.
7
-
8
3
  Canonical EVM chain-observation primitive. Provides a unified push-or-poll
9
4
  source for new blocks, mempool snapshots, on-demand receipt and tx
10
5
  lookups, and explicit capability disclosure (HTTP / WS / per-method
@@ -12,35 +7,98 @@ gating). Designed to be consumed by multiple downstream views of chain
12
7
  state — `@valve-tech/gas-oracle` and `@valve-tech/tx-tracker` are the
13
8
  first two.
14
9
 
10
+ See [`docs/tx-tracker-spec.md`](https://github.com/valve-tech/evm-toolkit/blob/main/docs/tx-tracker-spec.md)
11
+ §3 for the full design contract.
12
+
13
+ ## Why this exists
14
+
15
+ Both gas-oracle and tx-tracker need the same upstream signals — new
16
+ blocks, mempool snapshots, capability probing. Re-implementing the
17
+ poll loop in each would mean double-polling for consumers who use
18
+ both. Sharing a `ChainSource` instance between them gives one upstream
19
+ RPC stream feeding multiple derived views.
20
+
21
+ ## Install
22
+
23
+ ```bash
24
+ yarn add @valve-tech/chain-source viem
25
+ ```
26
+
27
+ ## Quick start
28
+
15
29
  ```ts
16
- // v0.1.0+ shape (not yet implemented):
17
- import { createChainSource } from '@valve-tech/chain-source'
18
30
  import { createPublicClient, http } from 'viem'
19
31
  import { mainnet } from 'viem/chains'
32
+ import { createChainSource } from '@valve-tech/chain-source'
20
33
 
21
34
  const client = createPublicClient({ chain: mainnet, transport: http() })
22
35
  const source = createChainSource({ client })
36
+
37
+ source.subscribeBlocks((block) => {
38
+ console.log('new block', block.number)
39
+ })
40
+
41
+ source.subscribeMempool((snapshot) => {
42
+ console.log('pending senders', Object.keys(snapshot.pending).length)
43
+ })
44
+
23
45
  source.start()
24
46
 
25
- source.subscribeBlocks((block) => { /* ... */ })
26
- source.subscribeMempool((snapshot) => { /* ... */ })
47
+ // On-demand RPCs (don't go through the poll cycle):
27
48
  const receipt = await source.getReceipt('0xabc...')
49
+ const tx = await source.getTransaction('0xabc...')
50
+
51
+ // Stop when done — preserves the subscriber registry across restarts.
52
+ source.stop()
28
53
  ```
29
54
 
30
- ## Why this exists
55
+ ## API surface
31
56
 
32
- Both gas-oracle and tx-tracker need the same upstream signals — new
33
- blocks, mempool snapshots, capability probing. Re-implementing the
34
- poll loop in each would mean double-polling for consumers who use
35
- both. Sharing a `ChainSource` instance between them gives one upstream
36
- RPC stream feeding multiple derived views.
57
+ ```ts
58
+ interface ChainSource {
59
+ start(): void
60
+ stop(): void
61
+ pollOnce(): Promise<void>
62
+ ready(): Promise<void>
37
63
 
38
- ## Install
64
+ subscribeBlocks(cb: (block: BlockResult) => void): () => void
65
+ subscribeMempool(cb: (snapshot: NormalizedMempool) => void): () => void
39
66
 
40
- ```bash
41
- yarn add @valve-tech/chain-source viem
67
+ getBlock(tag: 'latest' | bigint): Promise<BlockResult | null>
68
+ getFeeHistory(blockCount: number, percentiles: number[]): Promise<FeeHistoryResult | null>
69
+ getMempoolSnapshot(): Promise<NormalizedMempool | null>
70
+ getReceipt(hash: string): Promise<TransactionReceipt | null>
71
+ getTransaction(hash: string): Promise<RawTx | null>
72
+
73
+ capabilities(): Capabilities
74
+ }
75
+ ```
76
+
77
+ The capability probe runs eagerly at construction. `capabilities()`
78
+ returns a conservative default (everything `unavailable` / `gated`)
79
+ for the brief window before the probe lands; `await source.ready()`
80
+ guarantees the real values are cached.
81
+
82
+ ## Multi-subscriber semantics
83
+
84
+ `subscribeBlocks` and `subscribeMempool` are first-class
85
+ multi-subscriber streams. One upstream RPC poll cycle, regardless of
86
+ how many derived views attach:
87
+
88
+ ```ts
89
+ const source = createChainSource({ client })
90
+ const oracle = createGasOracle({ source, chainId: 1 }) // v0.3.x+ shape
91
+ const tracker = createTxTracker({ source, chainId: 1 }) // v0.3.x+ shape
92
+
93
+ source.start()
94
+ oracle.start()
95
+ tracker.start()
96
+ // ↑ one shared poll cycle, two derived views, no double-polling.
42
97
  ```
43
98
 
99
+ Stopping a derived view does not stop the source — the consumer that
100
+ constructed the source is the one who calls `source.stop()`.
101
+
44
102
  ## License
45
103
 
46
104
  MIT
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Per-method capability probe. Run once at `source.start()`, exposed
3
+ * via `source.capabilities()`, and re-run on transport reconnect when
4
+ * the underlying transport supports reconnection signalling. The
5
+ * probe is the load-bearing place where the toolkit's "no silent
6
+ * downgrade" rule (spec §2.2) is honored — every event the source
7
+ * emits carries a `source` discriminator chosen against this matrix.
8
+ *
9
+ * The probe uses the existing `safeRequest`-returns-null pattern: a
10
+ * thrown error is treated as "method gated/unavailable", a `null`
11
+ * return is treated the same. The distinction between the four states
12
+ * (`subscription` / `poll-only` / `available` / `gated` /
13
+ * `unavailable`) lives in the per-method spec table (§7).
14
+ *
15
+ * **What this probe deliberately does NOT do:** issue a real
16
+ * `eth_subscribe` against the upstream. The viem transport's
17
+ * `subscribe` API surface varies across versions and engaging it just
18
+ * to immediately tear it down is wasteful + can leak handles on
19
+ * misbehaving providers. We detect WS-subscribe capability
20
+ * structurally (`typeof transport.subscribe === 'function'`) and let
21
+ * the actual subscribe attempt happen lazily when a consumer calls
22
+ * `source.subscribeBlocks` / `subscribeMempool`. If the runtime
23
+ * subscribe fails, the source emits a `signal-degraded`-shaped fact
24
+ * (or downstream consumers do — chain-source itself doesn't author
25
+ * editorial events). This is the v0.3.x posture; future revisions
26
+ * may upgrade to live-probing if a provider is found that lies
27
+ * structurally.
28
+ */
29
+ import type { PublicClient } from 'viem';
30
+ import type { Capabilities } from './types.js';
31
+ interface ProbeOptions {
32
+ /** Same role as on `createChainSource` — sub-RPC failure sink. */
33
+ onError?: (method: string, err: unknown) => void;
34
+ }
35
+ /**
36
+ * Probe the upstream client's per-method capability. The result is a
37
+ * stable snapshot the source caches and exposes via
38
+ * `source.capabilities()`.
39
+ */
40
+ export declare const probeCapabilities: (client: PublicClient, options?: ProbeOptions) => Promise<Capabilities>;
41
+ export {};
42
+ //# sourceMappingURL=capabilities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAA;AAGxC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAE9C,UAAU,YAAY;IACpB,kEAAkE;IAClE,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;CACjD;AAsBD;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,GAC5B,QAAQ,YAAY,EACpB,UAAS,YAAiB,KACzB,OAAO,CAAC,YAAY,CAoDtB,CAAA"}
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Per-method capability probe. Run once at `source.start()`, exposed
3
+ * via `source.capabilities()`, and re-run on transport reconnect when
4
+ * the underlying transport supports reconnection signalling. The
5
+ * probe is the load-bearing place where the toolkit's "no silent
6
+ * downgrade" rule (spec §2.2) is honored — every event the source
7
+ * emits carries a `source` discriminator chosen against this matrix.
8
+ *
9
+ * The probe uses the existing `safeRequest`-returns-null pattern: a
10
+ * thrown error is treated as "method gated/unavailable", a `null`
11
+ * return is treated the same. The distinction between the four states
12
+ * (`subscription` / `poll-only` / `available` / `gated` /
13
+ * `unavailable`) lives in the per-method spec table (§7).
14
+ *
15
+ * **What this probe deliberately does NOT do:** issue a real
16
+ * `eth_subscribe` against the upstream. The viem transport's
17
+ * `subscribe` API surface varies across versions and engaging it just
18
+ * to immediately tear it down is wasteful + can leak handles on
19
+ * misbehaving providers. We detect WS-subscribe capability
20
+ * structurally (`typeof transport.subscribe === 'function'`) and let
21
+ * the actual subscribe attempt happen lazily when a consumer calls
22
+ * `source.subscribeBlocks` / `subscribeMempool`. If the runtime
23
+ * subscribe fails, the source emits a `signal-degraded`-shaped fact
24
+ * (or downstream consumers do — chain-source itself doesn't author
25
+ * editorial events). This is the v0.3.x posture; future revisions
26
+ * may upgrade to live-probing if a provider is found that lies
27
+ * structurally.
28
+ */
29
+ import { safeRequest, zeroHash } from './transport.js';
30
+ /**
31
+ * Inspect the client's transport surface to decide whether push
32
+ * subscriptions are possible. The capability is structural — the
33
+ * subscribe attempt itself is deferred to the actual subscribeBlocks /
34
+ * subscribeMempool call so we don't pay an early eth_subscribe just
35
+ * to learn the answer.
36
+ *
37
+ * Returns `'subscription'` when the transport exposes a `subscribe`
38
+ * function (canonical viem WS shape), `'unavailable'` otherwise.
39
+ * `'poll-only'` is reserved for the future live-probe variant where
40
+ * we can distinguish "transport supports subscribe but upstream
41
+ * rejected this method" from "transport doesn't subscribe at all."
42
+ */
43
+ const probeSubscribeShape = (client) => {
44
+ const transport = client.transport;
45
+ return typeof transport.subscribe === 'function' ? 'subscription' : 'unavailable';
46
+ };
47
+ /**
48
+ * Probe the upstream client's per-method capability. The result is a
49
+ * stable snapshot the source caches and exposes via
50
+ * `source.capabilities()`.
51
+ */
52
+ export const probeCapabilities = async (client, options = {}) => {
53
+ const subscribeShape = probeSubscribeShape(client);
54
+ // 1. txpool_content — distinguishes 'available' / 'gated'. Many
55
+ // public RPCs return 405 / method-not-found for this method.
56
+ const txpoolResult = await safeRequest(client, 'txpool_content', [], options.onError ? (err) => options.onError('txpool_content', err) : undefined);
57
+ const txpoolContent = txpoolResult === null ? 'gated' : 'available';
58
+ // 2. eth_getTransactionReceipt against the zero hash. Per spec §7.2,
59
+ // most providers return null for the zero hash but should not throw.
60
+ // A throw is taken as "method unavailable." Use direct request rather
61
+ // than safeRequest so the throw path is observable.
62
+ let receiptByHash = 'available';
63
+ try {
64
+ await client.request({
65
+ method: 'eth_getTransactionReceipt',
66
+ params: [zeroHash],
67
+ });
68
+ }
69
+ catch (err) {
70
+ receiptByHash = 'unavailable';
71
+ if (options.onError)
72
+ options.onError('eth_getTransactionReceipt', err);
73
+ }
74
+ // 3. WS-subscribe capability is the same answer for both newHeads
75
+ // and newPendingTransactions — the discriminator there is whether
76
+ // the upstream supports the *channel*, not the method. Mid-session
77
+ // method-specific failure surfaces via `signal-degraded` events on
78
+ // downstream consumers (the source itself doesn't author editorial
79
+ // events; see §3.2 of the spec).
80
+ return {
81
+ newHeads: subscribeShape,
82
+ newPendingTransactions:
83
+ // If subscribe is unavailable, fall back to whether txpool_content
84
+ // is available (poll fallback for mempool watch). If both are
85
+ // unavailable, mempool watch isn't possible at all.
86
+ subscribeShape === 'subscription'
87
+ ? 'subscription'
88
+ : txpoolContent === 'available'
89
+ ? 'poll-only'
90
+ : 'unavailable',
91
+ txpoolContent,
92
+ receiptByHash,
93
+ // WS transports are the ones that reconnect; HTTP has no persistent
94
+ // connection so reprobe-on-reconnect is meaningless.
95
+ reprobeOnReconnect: subscribeShape === 'subscription',
96
+ };
97
+ };
98
+ //# sourceMappingURL=capabilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAIH,OAAO,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAQtD;;;;;;;;;;;;GAYG;AACH,MAAM,mBAAmB,GAAG,CAC1B,MAAoB,EACY,EAAE;IAClC,MAAM,SAAS,GAAG,MAAM,CAAC,SAAoC,CAAA;IAC7D,OAAO,OAAO,SAAS,CAAC,SAAS,KAAK,UAAU,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,aAAa,CAAA;AACnF,CAAC,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,EACpC,MAAoB,EACpB,UAAwB,EAAE,EACH,EAAE;IACzB,MAAM,cAAc,GAAG,mBAAmB,CAAC,MAAM,CAAC,CAAA;IAElD,gEAAgE;IAChE,6DAA6D;IAC7D,MAAM,YAAY,GAAG,MAAM,WAAW,CACpC,MAAM,EACN,gBAAgB,EAChB,EAAE,EACF,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAY,EAAE,EAAE,CAAC,OAAO,CAAC,OAAQ,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CACxF,CAAA;IACD,MAAM,aAAa,GACjB,YAAY,KAAK,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,WAAW,CAAA;IAE/C,qEAAqE;IACrE,qEAAqE;IACrE,sEAAsE;IACtE,oDAAoD;IACpD,IAAI,aAAa,GAAgC,WAAW,CAAA;IAC5D,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,OAAO,CAAC;YACnB,MAAM,EAAE,2BAA2B;YACnC,MAAM,EAAE,CAAC,QAAQ,CAAC;SACV,CAAC,CAAA;IACb,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,aAAa,GAAG,aAAa,CAAA;QAC7B,IAAI,OAAO,CAAC,OAAO;YAAE,OAAO,CAAC,OAAO,CAAC,2BAA2B,EAAE,GAAG,CAAC,CAAA;IACxE,CAAC;IAED,kEAAkE;IAClE,kEAAkE;IAClE,mEAAmE;IACnE,mEAAmE;IACnE,mEAAmE;IACnE,iCAAiC;IACjC,OAAO;QACL,QAAQ,EAAE,cAAc;QACxB,sBAAsB;QACpB,mEAAmE;QACnE,8DAA8D;QAC9D,oDAAoD;QACpD,cAAc,KAAK,cAAc;YAC/B,CAAC,CAAC,cAAc;YAChB,CAAC,CAAC,aAAa,KAAK,WAAW;gBAC7B,CAAC,CAAC,WAAW;gBACb,CAAC,CAAC,aAAa;QACrB,aAAa;QACb,aAAa;QACb,oEAAoE;QACpE,qDAAqD;QACrD,kBAAkB,EAAE,cAAc,KAAK,cAAc;KACtD,CAAA;AACH,CAAC,CAAA"}
package/dist/index.d.ts CHANGED
@@ -1,15 +1,33 @@
1
1
  /**
2
- * @valve-tech/chain-source — placeholder for v0.0.1.
2
+ * `@valve-tech/chain-source`canonical EVM chain-observation primitive
3
+ * for the `valve-tech/evm-toolkit` synchronized release line.
3
4
  *
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.
5
+ * The package is the *foundation* layer for every derived view of
6
+ * chain state in the toolkit. Both `@valve-tech/gas-oracle` (gas-tier
7
+ * reducer) and `@valve-tech/tx-tracker` (per-tx state machine) consume
8
+ * a `ChainSource` rather than re-implementing their own poll cycles.
9
+ * One upstream RPC stream feeds every consumer that attaches.
7
10
  *
8
- * Design contract: see `docs/tx-tracker-spec.md` §3 in the
9
- * `valve-tech/evm-toolkit` repo, which defines the `ChainSource`
10
- * interface this package will export.
11
+ * See `docs/tx-tracker-spec.md` §3 for the full design contract.
11
12
  *
12
- * Until v0.1.0 is published, no symbols are exported.
13
+ * @example
14
+ * import { createPublicClient, http } from 'viem'
15
+ * import { mainnet } from 'viem/chains'
16
+ * import { createChainSource } from '@valve-tech/chain-source'
17
+ *
18
+ * const client = createPublicClient({ chain: mainnet, transport: http() })
19
+ * const source = createChainSource({ client })
20
+ *
21
+ * source.subscribeBlocks((block) => {
22
+ * console.log('block', block.number)
23
+ * })
24
+ * source.start()
13
25
  */
14
- export {};
26
+ export { createChainSource } from './source.js';
27
+ export type { ChainSource, CreateChainSourceOptions } from './source.js';
28
+ export { Subscriptions } from './subscriptions.js';
29
+ export { normalizeMempool } from './mempool.js';
30
+ export { probeCapabilities } from './capabilities.js';
31
+ export { safeRequest, fetchBlock, fetchHeadBlockNumber, fetchFeeHistory, fetchTxPool, fetchReceipt, fetchTransaction, zeroHash, } from './transport.js';
32
+ export type { BlockResult, Capabilities, EventSource, FeeHistoryResult, NormalizedMempool, PollOptions, RawTx, TransactionReceipt, TxPoolContent, } from './types.js';
15
33
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAE,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAC/C,YAAY,EAAE,WAAW,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAA;AAExE,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAElD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAE/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAErD,OAAO,EACL,WAAW,EACX,UAAU,EACV,oBAAoB,EACpB,eAAe,EACf,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,QAAQ,GACT,MAAM,gBAAgB,CAAA;AAEvB,YAAY,EACV,WAAW,EACX,YAAY,EACZ,WAAW,EACX,gBAAgB,EAChB,iBAAiB,EACjB,WAAW,EACX,KAAK,EACL,kBAAkB,EAClB,aAAa,GACd,MAAM,YAAY,CAAA"}
package/dist/index.js CHANGED
@@ -1,2 +1,31 @@
1
- export {};
1
+ /**
2
+ * `@valve-tech/chain-source` — canonical EVM chain-observation primitive
3
+ * for the `valve-tech/evm-toolkit` synchronized release line.
4
+ *
5
+ * The package is the *foundation* layer for every derived view of
6
+ * chain state in the toolkit. Both `@valve-tech/gas-oracle` (gas-tier
7
+ * reducer) and `@valve-tech/tx-tracker` (per-tx state machine) consume
8
+ * a `ChainSource` rather than re-implementing their own poll cycles.
9
+ * One upstream RPC stream feeds every consumer that attaches.
10
+ *
11
+ * See `docs/tx-tracker-spec.md` §3 for the full design contract.
12
+ *
13
+ * @example
14
+ * import { createPublicClient, http } from 'viem'
15
+ * import { mainnet } from 'viem/chains'
16
+ * import { createChainSource } from '@valve-tech/chain-source'
17
+ *
18
+ * const client = createPublicClient({ chain: mainnet, transport: http() })
19
+ * const source = createChainSource({ client })
20
+ *
21
+ * source.subscribeBlocks((block) => {
22
+ * console.log('block', block.number)
23
+ * })
24
+ * source.start()
25
+ */
26
+ export { createChainSource } from './source.js';
27
+ export { Subscriptions } from './subscriptions.js';
28
+ export { normalizeMempool } from './mempool.js';
29
+ export { probeCapabilities } from './capabilities.js';
30
+ export { safeRequest, fetchBlock, fetchHeadBlockNumber, fetchFeeHistory, fetchTxPool, fetchReceipt, fetchTransaction, zeroHash, } from './transport.js';
2
31
  //# 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;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAG/C,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAElD,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAA;AAE/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAErD,OAAO,EACL,WAAW,EACX,UAAU,EACV,oBAAoB,EACpB,eAAe,EACf,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,QAAQ,GACT,MAAM,gBAAgB,CAAA"}
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Mempool normalization. The single transformation pass that takes a
3
+ * `txpool_content` response into a canonical `NormalizedMempool` whose
4
+ * outer keys (sender address, nonce) are predictable.
5
+ *
6
+ * Why normalize once at ingest, not on every lookup?
7
+ *
8
+ * - Upstream clients are inconsistent about address case (geth /
9
+ * reth use EIP-55 mixed case, others lowercase, some checksum
10
+ * only some hex chars).
11
+ * - Nonce is also inconsistent — some clients hex-encode (`'0x5'`),
12
+ * some decimal-encode (`'5'`).
13
+ * - A consumer doing direct `pool.pending[address][nonce]` lookups
14
+ * would need a case-fold + format-fold fallback walk on every
15
+ * access. That's both slow and a class of latent bugs (forget
16
+ * the fallback, miss a hit).
17
+ *
18
+ * Normalize once at the source's ingest point, then every lookup is
19
+ * an O(1) two-key access against keys whose form is known. The
20
+ * `NormalizedMempool` type alias signals the invariant.
21
+ */
22
+ import type { NormalizedMempool, TxPoolContent } from './types.js';
23
+ /**
24
+ * Run the upstream `txpool_content` payload through one normalization
25
+ * pass: every sender address key is lowercased, every nonce key is
26
+ * coerced to its canonical decimal form. The inner `RawTx` values
27
+ * are passed through by reference unchanged — pool normalization is
28
+ * for the OUTER keys only; downstream tx-field comparisons (hash,
29
+ * from, nonce) do their own case-folding.
30
+ *
31
+ * Idempotent — re-normalizing a `NormalizedMempool` returns an
32
+ * equivalent `NormalizedMempool`. Pass `null` / `undefined` to get
33
+ * empty subpools back; this lets callers store the result without
34
+ * `null`-checking on every lookup.
35
+ */
36
+ export declare const normalizeMempool: (pool: TxPoolContent | null | undefined) => NormalizedMempool;
37
+ //# sourceMappingURL=mempool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mempool.d.ts","sourceRoot":"","sources":["../src/mempool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAS,aAAa,EAAE,MAAM,YAAY,CAAA;AAmBzE;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,gBAAgB,GAC3B,MAAM,aAAa,GAAG,IAAI,GAAG,SAAS,KACrC,iBAGD,CAAA"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Mempool normalization. The single transformation pass that takes a
3
+ * `txpool_content` response into a canonical `NormalizedMempool` whose
4
+ * outer keys (sender address, nonce) are predictable.
5
+ *
6
+ * Why normalize once at ingest, not on every lookup?
7
+ *
8
+ * - Upstream clients are inconsistent about address case (geth /
9
+ * reth use EIP-55 mixed case, others lowercase, some checksum
10
+ * only some hex chars).
11
+ * - Nonce is also inconsistent — some clients hex-encode (`'0x5'`),
12
+ * some decimal-encode (`'5'`).
13
+ * - A consumer doing direct `pool.pending[address][nonce]` lookups
14
+ * would need a case-fold + format-fold fallback walk on every
15
+ * access. That's both slow and a class of latent bugs (forget
16
+ * the fallback, miss a hit).
17
+ *
18
+ * Normalize once at the source's ingest point, then every lookup is
19
+ * an O(1) two-key access against keys whose form is known. The
20
+ * `NormalizedMempool` type alias signals the invariant.
21
+ */
22
+ const normalizeSubpool = (sub) => {
23
+ if (!sub)
24
+ return {};
25
+ const out = {};
26
+ for (const [address, byNonce] of Object.entries(sub)) {
27
+ const normalizedByNonce = {};
28
+ for (const [nonce, tx] of Object.entries(byNonce)) {
29
+ // BigInt() accepts both '5' and '0x5'; .toString() gives the
30
+ // canonical decimal form. Idempotent on already-decimal keys.
31
+ normalizedByNonce[BigInt(nonce).toString()] = tx;
32
+ }
33
+ out[address.toLowerCase()] = normalizedByNonce;
34
+ }
35
+ return out;
36
+ };
37
+ /**
38
+ * Run the upstream `txpool_content` payload through one normalization
39
+ * pass: every sender address key is lowercased, every nonce key is
40
+ * coerced to its canonical decimal form. The inner `RawTx` values
41
+ * are passed through by reference unchanged — pool normalization is
42
+ * for the OUTER keys only; downstream tx-field comparisons (hash,
43
+ * from, nonce) do their own case-folding.
44
+ *
45
+ * Idempotent — re-normalizing a `NormalizedMempool` returns an
46
+ * equivalent `NormalizedMempool`. Pass `null` / `undefined` to get
47
+ * empty subpools back; this lets callers store the result without
48
+ * `null`-checking on every lookup.
49
+ */
50
+ export const normalizeMempool = (pool) => ({
51
+ pending: normalizeSubpool(pool?.pending),
52
+ queued: normalizeSubpool(pool?.queued),
53
+ });
54
+ //# sourceMappingURL=mempool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mempool.js","sourceRoot":"","sources":["../src/mempool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAIH,MAAM,gBAAgB,GAAG,CACvB,GAAsD,EACf,EAAE;IACzC,IAAI,CAAC,GAAG;QAAE,OAAO,EAAE,CAAA;IACnB,MAAM,GAAG,GAA0C,EAAE,CAAA;IACrD,KAAK,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACrD,MAAM,iBAAiB,GAA0B,EAAE,CAAA;QACnD,KAAK,MAAM,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YAClD,6DAA6D;YAC7D,8DAA8D;YAC9D,iBAAiB,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE,CAAC,GAAG,EAAE,CAAA;QAClD,CAAC;QACD,GAAG,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,GAAG,iBAAiB,CAAA;IAChD,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC,CAAA;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAC9B,IAAsC,EACnB,EAAE,CAAC,CAAC;IACvB,OAAO,EAAE,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC;IACxC,MAAM,EAAE,gBAAgB,CAAC,IAAI,EAAE,MAAM,CAAC;CACvC,CAAC,CAAA"}