@valve-tech/chain-source 0.5.0 → 0.6.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.
@@ -0,0 +1,128 @@
1
+ /**
2
+ * `createChainSource` — the canonical chain-observation primitive
3
+ * for `@valve-tech/evm-toolkit`. Owns:
4
+ *
5
+ * - the upstream poll cycle (block + mempool fan-out per tick)
6
+ * - the per-method capability probe (run eagerly at construction)
7
+ * - typed pub/sub for blocks and mempool snapshots, with
8
+ * multiple-subscribers-per-stream as a first-class guarantee
9
+ * - on-demand RPC passthroughs for individual blocks, fee history,
10
+ * receipts, and transactions
11
+ *
12
+ * Sibling features (`@valve-tech/gas-oracle`, `@valve-tech/tx-tracker`)
13
+ * consume `ChainSource` and never re-implement the poll loop. One
14
+ * upstream RPC cycle, regardless of how many derived views attach.
15
+ *
16
+ * Lifecycle (per spec §14.1):
17
+ *
18
+ * - `start()` begins interval-driven polling. Idempotent.
19
+ * - `stop()` halts the interval; subscriber registry is preserved
20
+ * so a `start() → stop() → start()` resume keeps existing
21
+ * subscriptions alive.
22
+ * - The capability probe runs eagerly at *construction*, not at
23
+ * start(). By the time a consumer calls `capabilities()` after
24
+ * any await, the probe has typically landed. For a brief window
25
+ * immediately after `createChainSource()`, `capabilities()` returns
26
+ * a conservative default (everything `unavailable` / `gated`).
27
+ * Callers that need a guaranteed-fresh result can `await
28
+ * source.ready()` before reading `capabilities()`.
29
+ *
30
+ * Push subscriptions (eth_subscribe) are not yet wired in v0.3.x —
31
+ * the `Capabilities.newHeads` / `newPendingTransactions` fields
32
+ * disclose what *would* be available structurally, but the source
33
+ * always falls back to its interval poll cycle in this revision.
34
+ * Future revisions add WS push without changing the consumer-facing
35
+ * `subscribeBlocks` / `subscribeMempool` shape.
36
+ */
37
+ import type { PublicClient } from 'viem';
38
+ import type { BlockResult, Capabilities, FeeHistoryResult, NormalizedMempool, PollOptions, RawTx, TransactionReceipt } from './types.js';
39
+ export interface CreateChainSourceOptions {
40
+ /** viem PublicClient pointed at the upstream RPC. */
41
+ client: PublicClient;
42
+ /**
43
+ * Polling interval in ms when push subscriptions aren't available
44
+ * (or aren't preferred). Default 10_000.
45
+ */
46
+ pollIntervalMs?: number;
47
+ /**
48
+ * Producer-side toggles: which RPCs the source's tick fans out.
49
+ * Disabling `mempool` here disables `subscribeMempool` for every
50
+ * consumer; the source-level toggle is the single source of truth.
51
+ * `feeHistory` is currently informational — the source's tick does
52
+ * not fan out fee history; consumers fetch it on demand via
53
+ * `getFeeHistory`. The toggle is reserved for forward-compatibility.
54
+ */
55
+ poll?: PollOptions;
56
+ /**
57
+ * Optional error sink — called per-method when an RPC fails. Same
58
+ * role as on `createGasOracle`. Failures are otherwise swallowed
59
+ * (the source keeps running on partial data).
60
+ */
61
+ onError?: (method: string, err: unknown) => void;
62
+ }
63
+ export interface ChainSource {
64
+ /** Begin the poll loop. Idempotent. */
65
+ start: () => void;
66
+ /** Halt the poll loop. Subscribers preserved across stop/start. Idempotent. */
67
+ stop: () => void;
68
+ /**
69
+ * Run one poll cycle out-of-band. Useful for tests, manual
70
+ * refreshes, and serverless contexts where the interval timer
71
+ * isn't appropriate. Fans out to subscribers identically to a
72
+ * timer-driven tick.
73
+ *
74
+ * Additive over the design contract — the spec exposes only
75
+ * subscribe + on-demand methods, but pollOnce is a natural
76
+ * symmetric helper that keeps the test surface honest without
77
+ * depending on fake timers.
78
+ */
79
+ pollOnce: () => Promise<void>;
80
+ /**
81
+ * Resolve when the eager capability probe (kicked off at
82
+ * construction) has completed. After this resolves, `capabilities()`
83
+ * returns the real probed values rather than the conservative
84
+ * default.
85
+ */
86
+ ready: () => Promise<void>;
87
+ /** Subscribe to new-block events. Multiple subscribers allowed. */
88
+ subscribeBlocks: (cb: (block: BlockResult) => void) => () => void;
89
+ /** Subscribe to mempool snapshots. Multiple subscribers allowed. */
90
+ subscribeMempool: (cb: (snapshot: NormalizedMempool) => void) => () => void;
91
+ /** On-demand: fetch a single block (full transactions). */
92
+ getBlock: (tag: 'latest' | bigint) => Promise<BlockResult | null>;
93
+ /** On-demand: fee history. */
94
+ getFeeHistory: (blockCount: number, percentiles: number[]) => Promise<FeeHistoryResult | null>;
95
+ /**
96
+ * On-demand: fresh `txpool_content` snapshot, normalized. Returns
97
+ * `null` when the upstream gates the method. For continuous access,
98
+ * prefer `subscribeMempool` — that path reuses the source's poll
99
+ * cycle and avoids a fresh RPC per call.
100
+ */
101
+ getMempoolSnapshot: () => Promise<NormalizedMempool | null>;
102
+ /** On-demand: receipt by tx hash. */
103
+ getReceipt: (hash: string) => Promise<TransactionReceipt | null>;
104
+ /** On-demand: tx by hash (used by tx-tracker for replacement detection). */
105
+ getTransaction: (hash: string) => Promise<RawTx | null>;
106
+ /** Latest probed capability snapshot. */
107
+ capabilities: () => Capabilities;
108
+ }
109
+ /**
110
+ * Build a configured chain-source. The eager capability probe starts
111
+ * immediately; nothing else happens until `start()` is called.
112
+ *
113
+ * @example
114
+ * import { createPublicClient, http } from 'viem'
115
+ * import { mainnet } from 'viem/chains'
116
+ * import { createChainSource } from '@valve-tech/chain-source'
117
+ *
118
+ * const client = createPublicClient({ chain: mainnet, transport: http() })
119
+ * const source = createChainSource({ client })
120
+ *
121
+ * source.subscribeBlocks((block) => console.log('new block', block.number))
122
+ * source.start()
123
+ *
124
+ * // ... later
125
+ * source.stop()
126
+ */
127
+ export declare const createChainSource: (options: CreateChainSourceOptions) => ChainSource;
128
+ //# sourceMappingURL=source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.d.ts","sourceRoot":"","sources":["../src/source.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAA;AAaxC,OAAO,KAAK,EACV,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,iBAAiB,EACjB,WAAW,EACX,KAAK,EACL,kBAAkB,EACnB,MAAM,YAAY,CAAA;AAoBnB,MAAM,WAAW,wBAAwB;IACvC,qDAAqD;IACrD,MAAM,EAAE,YAAY,CAAA;IACpB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAA;IACvB;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,WAAW,CAAA;IAClB;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;CACjD;AAED,MAAM,WAAW,WAAW;IAC1B,uCAAuC;IACvC,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,+EAA+E;IAC/E,IAAI,EAAE,MAAM,IAAI,CAAA;IAChB;;;;;;;;;;OAUG;IACH,QAAQ,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC7B;;;;;OAKG;IACH,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IAC1B,mEAAmE;IACnE,eAAe,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,KAAK,MAAM,IAAI,CAAA;IACjE,oEAAoE;IACpE,gBAAgB,EAAE,CAAC,EAAE,EAAE,CAAC,QAAQ,EAAE,iBAAiB,KAAK,IAAI,KAAK,MAAM,IAAI,CAAA;IAC3E,2DAA2D;IAC3D,QAAQ,EAAE,CAAC,GAAG,EAAE,QAAQ,GAAG,MAAM,KAAK,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAAA;IACjE,8BAA8B;IAC9B,aAAa,EAAE,CACb,UAAU,EAAE,MAAM,EAClB,WAAW,EAAE,MAAM,EAAE,KAClB,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC,CAAA;IACrC;;;;;OAKG;IACH,kBAAkB,EAAE,MAAM,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAAA;IAC3D,qCAAqC;IACrC,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAAC,CAAA;IAChE,4EAA4E;IAC5E,cAAc,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,KAAK,GAAG,IAAI,CAAC,CAAA;IACvD,yCAAyC;IACzC,YAAY,EAAE,MAAM,YAAY,CAAA;CACjC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,eAAO,MAAM,iBAAiB,GAC5B,SAAS,wBAAwB,KAChC,WAsJF,CAAA"}
package/dist/source.js ADDED
@@ -0,0 +1,198 @@
1
+ /**
2
+ * `createChainSource` — the canonical chain-observation primitive
3
+ * for `@valve-tech/evm-toolkit`. Owns:
4
+ *
5
+ * - the upstream poll cycle (block + mempool fan-out per tick)
6
+ * - the per-method capability probe (run eagerly at construction)
7
+ * - typed pub/sub for blocks and mempool snapshots, with
8
+ * multiple-subscribers-per-stream as a first-class guarantee
9
+ * - on-demand RPC passthroughs for individual blocks, fee history,
10
+ * receipts, and transactions
11
+ *
12
+ * Sibling features (`@valve-tech/gas-oracle`, `@valve-tech/tx-tracker`)
13
+ * consume `ChainSource` and never re-implement the poll loop. One
14
+ * upstream RPC cycle, regardless of how many derived views attach.
15
+ *
16
+ * Lifecycle (per spec §14.1):
17
+ *
18
+ * - `start()` begins interval-driven polling. Idempotent.
19
+ * - `stop()` halts the interval; subscriber registry is preserved
20
+ * so a `start() → stop() → start()` resume keeps existing
21
+ * subscriptions alive.
22
+ * - The capability probe runs eagerly at *construction*, not at
23
+ * start(). By the time a consumer calls `capabilities()` after
24
+ * any await, the probe has typically landed. For a brief window
25
+ * immediately after `createChainSource()`, `capabilities()` returns
26
+ * a conservative default (everything `unavailable` / `gated`).
27
+ * Callers that need a guaranteed-fresh result can `await
28
+ * source.ready()` before reading `capabilities()`.
29
+ *
30
+ * Push subscriptions (eth_subscribe) are not yet wired in v0.3.x —
31
+ * the `Capabilities.newHeads` / `newPendingTransactions` fields
32
+ * disclose what *would* be available structurally, but the source
33
+ * always falls back to its interval poll cycle in this revision.
34
+ * Future revisions add WS push without changing the consumer-facing
35
+ * `subscribeBlocks` / `subscribeMempool` shape.
36
+ */
37
+ import { probeCapabilities } from './capabilities.js';
38
+ import { normalizeMempool } from './mempool.js';
39
+ import { Subscriptions } from './subscriptions.js';
40
+ import { fetchBlock, fetchFeeHistory, fetchHeadBlockNumber, fetchReceipt, fetchTransaction, fetchTxPool, } from './transport.js';
41
+ const DEFAULT_POLL_INTERVAL_MS = 10000;
42
+ /**
43
+ * Conservative capability default returned by `capabilities()` before
44
+ * the eager probe completes. Every signal is treated as unavailable
45
+ * until proven otherwise — consumers reading capabilities in this
46
+ * window get the safest answer (no path is available, fall back to
47
+ * the most defensive flow). Once the probe lands, real values
48
+ * overwrite this.
49
+ */
50
+ const PROBING_DEFAULT = {
51
+ newHeads: 'unavailable',
52
+ newPendingTransactions: 'unavailable',
53
+ txpoolContent: 'gated',
54
+ receiptByHash: 'unavailable',
55
+ reprobeOnReconnect: false,
56
+ };
57
+ /**
58
+ * Build a configured chain-source. The eager capability probe starts
59
+ * immediately; nothing else happens until `start()` is called.
60
+ *
61
+ * @example
62
+ * import { createPublicClient, http } from 'viem'
63
+ * import { mainnet } from 'viem/chains'
64
+ * import { createChainSource } from '@valve-tech/chain-source'
65
+ *
66
+ * const client = createPublicClient({ chain: mainnet, transport: http() })
67
+ * const source = createChainSource({ client })
68
+ *
69
+ * source.subscribeBlocks((block) => console.log('new block', block.number))
70
+ * source.start()
71
+ *
72
+ * // ... later
73
+ * source.stop()
74
+ */
75
+ export const createChainSource = (options) => {
76
+ const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
77
+ const fetchMempool = options.poll?.mempool !== false;
78
+ const blockSubs = new Subscriptions();
79
+ const mempoolSubs = new Subscriptions();
80
+ let timer = null;
81
+ let started = false;
82
+ let cachedCapabilities = PROBING_DEFAULT;
83
+ // Dedup key for the block stream — by hash, not number, so that a
84
+ // same-height reorg (different hash, same number) still surfaces as
85
+ // a fresh observation. Reset on stop() so a paused-then-resumed
86
+ // source emits a current snapshot to its consumers rather than
87
+ // waiting for the next chain block. Typed `string | undefined` to
88
+ // match `BlockResult.hash`'s optionality (real upstream blocks
89
+ // always carry a hash; the type stays permissive for test fixtures).
90
+ let lastEmittedBlockHash;
91
+ // Head-probe gate state — last `eth_blockNumber`-confirmed head we
92
+ // actually fetched a full block for. Lets the tick skip
93
+ // `eth_getBlockByNumber('latest', true)` (1–5MB on busy chains)
94
+ // when the head hasn't moved. Same lifecycle as the dedup hash —
95
+ // reset on stop() so a paused-then-resumed source defensively
96
+ // re-fetches even at the same height.
97
+ let lastSeenBlockNumber;
98
+ const errSink = (method) => options.onError ? (err) => options.onError(method, err) : undefined;
99
+ // Eager capability probe. Fire-and-forget; consumers that need a
100
+ // guaranteed-completed probe await source.ready().
101
+ const readyPromise = probeCapabilities(options.client, {
102
+ onError: options.onError,
103
+ }).then((caps) => {
104
+ cachedCapabilities = caps;
105
+ });
106
+ // One poll cycle. Cheap `eth_blockNumber` probe + (optionally)
107
+ // mempool fetch in parallel; if the probe shows the head has
108
+ // advanced (or the probe failed and we're falling through
109
+ // defensively), follow up with the expensive full-block fetch.
110
+ // Mempool emits every successful cycle; blocks emit only when the
111
+ // observed hash changes.
112
+ const tick = async () => {
113
+ const [head, txPool] = await Promise.all([
114
+ fetchHeadBlockNumber(options.client, errSink('eth_blockNumber')),
115
+ fetchMempool
116
+ ? fetchTxPool(options.client, errSink('txpool_content'))
117
+ : Promise.resolve(null),
118
+ ]);
119
+ // Head-probe gate: skip the full-block fetch when the probe says
120
+ // the head hasn't moved since we last observed it. A null probe
121
+ // (RPC method gated, transport error) falls through to fetch —
122
+ // we'd rather pay one extra block fetch than block on a flaky
123
+ // upstream that can't even report `eth_blockNumber`.
124
+ const headChanged = head === null ||
125
+ lastSeenBlockNumber === undefined ||
126
+ head !== lastSeenBlockNumber;
127
+ const block = headChanged
128
+ ? await fetchBlock(options.client, 'latest', errSink('eth_getBlockByNumber'))
129
+ : null;
130
+ if (block) {
131
+ // Update the gate state from the actually-fetched block, not
132
+ // from the probe's number — keeps the gate consistent with
133
+ // what consumers observed.
134
+ try {
135
+ lastSeenBlockNumber = BigInt(block.number);
136
+ }
137
+ catch {
138
+ // Block number didn't decode — leave gate state untouched so
139
+ // we re-fetch on the next tick rather than persist garbage.
140
+ }
141
+ if (block.hash !== lastEmittedBlockHash) {
142
+ lastEmittedBlockHash = block.hash;
143
+ blockSubs.emit(block);
144
+ }
145
+ }
146
+ // Mempool is intentionally not deduped — txs come and go between
147
+ // blocks even on a static head, so every successful snapshot is
148
+ // fresh data. Only the block stream dedups.
149
+ if (txPool && fetchMempool) {
150
+ mempoolSubs.emit(normalizeMempool(txPool));
151
+ }
152
+ };
153
+ return {
154
+ start: () => {
155
+ if (started)
156
+ return;
157
+ started = true;
158
+ // Fire the first tick immediately so consumers don't wait one
159
+ // full interval for their first event. The interval timer
160
+ // takes over from there.
161
+ void tick();
162
+ timer = setInterval(() => {
163
+ void tick();
164
+ }, pollIntervalMs);
165
+ },
166
+ stop: () => {
167
+ if (timer !== null) {
168
+ clearInterval(timer);
169
+ timer = null;
170
+ }
171
+ started = false;
172
+ // Subscriber registry is intentionally preserved across stop —
173
+ // a start/stop/start pattern keeps existing subscriptions
174
+ // alive, matching the gas-oracle convention. Block-dedup +
175
+ // head-probe-gate state ARE reset though: a consumer that
176
+ // paused and resumed should get a current snapshot on first
177
+ // re-tick rather than wait for the next chain block, and the
178
+ // gate must defensively re-fetch in case the chain advanced
179
+ // (or reorged) during the pause.
180
+ lastEmittedBlockHash = undefined;
181
+ lastSeenBlockNumber = undefined;
182
+ },
183
+ pollOnce: () => tick(),
184
+ ready: () => readyPromise,
185
+ subscribeBlocks: (cb) => blockSubs.subscribe(cb),
186
+ subscribeMempool: (cb) => mempoolSubs.subscribe(cb),
187
+ getBlock: (tag) => fetchBlock(options.client, tag, errSink('eth_getBlockByNumber')),
188
+ getFeeHistory: (blockCount, percentiles) => fetchFeeHistory(options.client, blockCount, percentiles, errSink('eth_feeHistory')),
189
+ getMempoolSnapshot: async () => {
190
+ const txPool = await fetchTxPool(options.client, errSink('txpool_content'));
191
+ return txPool ? normalizeMempool(txPool) : null;
192
+ },
193
+ getReceipt: (hash) => fetchReceipt(options.client, hash, errSink('eth_getTransactionReceipt')),
194
+ getTransaction: (hash) => fetchTransaction(options.client, hash, errSink('eth_getTransactionByHash')),
195
+ capabilities: () => cachedCapabilities,
196
+ };
197
+ };
198
+ //# sourceMappingURL=source.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"source.js","sourceRoot":"","sources":["../src/source.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Hand-rolled typed pub/sub primitive. Browser/mobile-safe — does **not**
3
+ * import Node's `events` module, which would tie this package to a Node
4
+ * runtime and break browser / React Native / edge bundles. The primitive
5
+ * is small enough that the size cost is negligible compared to the
6
+ * portability win.
7
+ *
8
+ * Posture matches `oracle.subscribe` in `@valve-tech/gas-oracle`:
9
+ *
10
+ * - Per-subscriber throws are **swallowed**. A bad consumer cannot take
11
+ * down the others — emit always reaches every subscriber that was
12
+ * registered when the emit started.
13
+ * - The set fanned out for an `emit` call is the snapshot taken at the
14
+ * start of the call. Subscribers added during the in-flight emit are
15
+ * ignored for that emit and join the set for the next one. This is
16
+ * the only safe iteration order — mutating the underlying set
17
+ * mid-iteration would require defensive cloning that is more
18
+ * expensive than always cloning once.
19
+ * - Unsubscribe handles are idempotent: calling the returned unsub
20
+ * function twice is a no-op the second time.
21
+ * - Re-subscribing the same callback reference is a no-op (the backing
22
+ * set deduplicates by reference). Callers that want "deliver twice"
23
+ * register two distinct closures.
24
+ */
25
+ export declare class Subscriptions<E> {
26
+ private subscribers;
27
+ /**
28
+ * Deliver `event` to every subscriber registered at the moment this
29
+ * call started. Per-subscriber throws are swallowed; a single bad
30
+ * consumer cannot affect delivery to the others.
31
+ */
32
+ emit(event: E): void;
33
+ /**
34
+ * Register `cb` for future emits. Returns an idempotent unsubscribe
35
+ * function — calling it once removes the callback; calling it again
36
+ * is a no-op.
37
+ */
38
+ subscribe(cb: (event: E) => void): () => void;
39
+ /** Number of currently-registered subscribers. */
40
+ size(): number;
41
+ }
42
+ //# sourceMappingURL=subscriptions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscriptions.d.ts","sourceRoot":"","sources":["../src/subscriptions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,aAAa,CAAC,CAAC;IAC1B,OAAO,CAAC,WAAW,CAAgC;IAEnD;;;;OAIG;IACH,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI;IAgBpB;;;;OAIG;IACH,SAAS,CAAC,EAAE,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,MAAM,IAAI;IAO7C,kDAAkD;IAClD,IAAI,IAAI,MAAM;CAGf"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Hand-rolled typed pub/sub primitive. Browser/mobile-safe — does **not**
3
+ * import Node's `events` module, which would tie this package to a Node
4
+ * runtime and break browser / React Native / edge bundles. The primitive
5
+ * is small enough that the size cost is negligible compared to the
6
+ * portability win.
7
+ *
8
+ * Posture matches `oracle.subscribe` in `@valve-tech/gas-oracle`:
9
+ *
10
+ * - Per-subscriber throws are **swallowed**. A bad consumer cannot take
11
+ * down the others — emit always reaches every subscriber that was
12
+ * registered when the emit started.
13
+ * - The set fanned out for an `emit` call is the snapshot taken at the
14
+ * start of the call. Subscribers added during the in-flight emit are
15
+ * ignored for that emit and join the set for the next one. This is
16
+ * the only safe iteration order — mutating the underlying set
17
+ * mid-iteration would require defensive cloning that is more
18
+ * expensive than always cloning once.
19
+ * - Unsubscribe handles are idempotent: calling the returned unsub
20
+ * function twice is a no-op the second time.
21
+ * - Re-subscribing the same callback reference is a no-op (the backing
22
+ * set deduplicates by reference). Callers that want "deliver twice"
23
+ * register two distinct closures.
24
+ */
25
+ export class Subscriptions {
26
+ constructor() {
27
+ this.subscribers = new Set();
28
+ }
29
+ /**
30
+ * Deliver `event` to every subscriber registered at the moment this
31
+ * call started. Per-subscriber throws are swallowed; a single bad
32
+ * consumer cannot affect delivery to the others.
33
+ */
34
+ emit(event) {
35
+ // Snapshot before iteration so a subscriber can't mutate the
36
+ // delivery set mid-fanout (e.g. by subscribing a new callback or
37
+ // unsubscribing a later one). The cost is one Set copy per emit;
38
+ // the alternative (iterating the live set) silently changes
39
+ // delivery semantics in subtle ways.
40
+ const snapshot = Array.from(this.subscribers);
41
+ for (const cb of snapshot) {
42
+ try {
43
+ cb(event);
44
+ }
45
+ catch {
46
+ /* swallow per-subscriber errors */
47
+ }
48
+ }
49
+ }
50
+ /**
51
+ * Register `cb` for future emits. Returns an idempotent unsubscribe
52
+ * function — calling it once removes the callback; calling it again
53
+ * is a no-op.
54
+ */
55
+ subscribe(cb) {
56
+ this.subscribers.add(cb);
57
+ return () => {
58
+ this.subscribers.delete(cb);
59
+ };
60
+ }
61
+ /** Number of currently-registered subscribers. */
62
+ size() {
63
+ return this.subscribers.size;
64
+ }
65
+ }
66
+ //# sourceMappingURL=subscriptions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"subscriptions.js","sourceRoot":"","sources":["../src/subscriptions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,OAAO,aAAa;IAA1B;QACU,gBAAW,GAAG,IAAI,GAAG,EAAsB,CAAA;IAuCrD,CAAC;IArCC;;;;OAIG;IACH,IAAI,CAAC,KAAQ;QACX,6DAA6D;QAC7D,iEAAiE;QACjE,iEAAiE;QACjE,4DAA4D;QAC5D,qCAAqC;QACrC,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;QAC7C,KAAK,MAAM,EAAE,IAAI,QAAQ,EAAE,CAAC;YAC1B,IAAI,CAAC;gBACH,EAAE,CAAC,KAAK,CAAC,CAAA;YACX,CAAC;YAAC,MAAM,CAAC;gBACP,mCAAmC;YACrC,CAAC;QACH,CAAC;IACH,CAAC;IAED;;;;OAIG;IACH,SAAS,CAAC,EAAsB;QAC9B,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC,CAAA;QACxB,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC,CAAA;QAC7B,CAAC,CAAA;IACH,CAAC;IAED,kDAAkD;IAClD,IAAI;QACF,OAAO,IAAI,CAAC,WAAW,CAAC,IAAI,CAAA;IAC9B,CAAC;CACF"}
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Low-level RPC fan-out for `@valve-tech/chain-source`. One thin
3
+ * wrapper per JSON-RPC method the source needs:
4
+ *
5
+ * - `eth_blockNumber` — cheap head probe (block-gating).
6
+ * - `eth_getBlockByNumber` — full block (header + txs).
7
+ * - `eth_feeHistory` — base-fee history + percentile rewards.
8
+ * - `txpool_content` — pending + queued mempool snapshot.
9
+ * - `eth_getTransactionReceipt` — inclusion-watch fallback.
10
+ * - `eth_getTransactionByHash` — replacement detection (from, nonce).
11
+ *
12
+ * Each wrapper uses the same `safeRequest` posture: turn errors,
13
+ * `null`, and `undefined` upstream responses into a `null` return —
14
+ * never throw across this layer. The higher-level source module
15
+ * decides what each `null` means (capability gated, transient
16
+ * upstream failure, no such tx).
17
+ *
18
+ * Why not viem's typed methods? Two reasons:
19
+ *
20
+ * 1. `txpool_content` is non-standard, so we'd be reaching for
21
+ * `client.request` for it anyway. Doing every method through
22
+ * the same shape keeps call sites symmetric.
23
+ * 2. Some fields of interest (`excessBlobGas`, `blobGasUsed`,
24
+ * `parentHash`) are surfaced inconsistently across viem versions
25
+ * in the typed `getBlock` shape. Going straight through
26
+ * `client.request` gives us the wire response untouched and the
27
+ * types we declare here describe exactly what we actually use.
28
+ */
29
+ import type { PublicClient } from 'viem';
30
+ import type { BlockResult, FeeHistoryResult, RawTx, TransactionReceipt, TxPoolContent } from './types.js';
31
+ /** Canonical 32-byte zero hash. Used for the receipt-by-hash probe. */
32
+ export declare const zeroHash: string;
33
+ /**
34
+ * Issue an arbitrary JSON-RPC method through the client. A single
35
+ * failure (method-not-found, transport error, malformed response)
36
+ * becomes `null` rather than a thrown exception — the caller decides
37
+ * what each missing-data case means. This is the load-bearing
38
+ * convention for capability-gated methods like `txpool_content`.
39
+ */
40
+ export declare const safeRequest: <T>(client: PublicClient, method: string, params: unknown[], onError?: (err: unknown) => void) => Promise<T | null>;
41
+ /**
42
+ * Fetch the latest head block number as a `bigint`. Used by the
43
+ * source's block-gating optimization: when the head hasn't moved
44
+ * since the previous tick, the expensive cycle (full block + fee
45
+ * history + mempool) is skipped because no fee landscape change is
46
+ * possible without a new block.
47
+ *
48
+ * Returns `null` if the upstream `eth_blockNumber` fails or returns
49
+ * something that won't decode as a bigint.
50
+ */
51
+ export declare const fetchHeadBlockNumber: (client: PublicClient, onError?: (err: unknown) => void) => Promise<bigint | null>;
52
+ /**
53
+ * Fetch a block (full transactions). `tag` may be `'latest'` or an
54
+ * absolute block number as a `bigint`; the bigint is hex-encoded
55
+ * before the RPC call. Returns the raw `BlockResult` (hex-encoded
56
+ * numeric fields untouched) or `null` if the upstream failed.
57
+ */
58
+ export declare const fetchBlock: (client: PublicClient, tag: "latest" | bigint, onError?: (err: unknown) => void) => Promise<BlockResult | null>;
59
+ /**
60
+ * Fetch fee history. `blockCount` is hex-encoded per the spec; the
61
+ * latest block is the implicit upper bound. `percentiles` is passed
62
+ * through unchanged (RPC accepts a JSON number array).
63
+ */
64
+ export declare const fetchFeeHistory: (client: PublicClient, blockCount: number, percentiles: number[], onError?: (err: unknown) => void) => Promise<FeeHistoryResult | null>;
65
+ /**
66
+ * Fetch a `txpool_content` snapshot. `null` is the expected return
67
+ * when the provider gates the method (most public RPCs do). The
68
+ * source surfaces this via `capabilities().txpoolContent === 'gated'`
69
+ * so consumers can react.
70
+ */
71
+ export declare const fetchTxPool: (client: PublicClient, onError?: (err: unknown) => void) => Promise<TxPoolContent | null>;
72
+ /**
73
+ * Fetch a transaction receipt by hash. `null` covers both "no such
74
+ * tx" and "method not available." Distinguish the two via
75
+ * `capabilities().receiptByHash`.
76
+ */
77
+ export declare const fetchReceipt: (client: PublicClient, hash: string, onError?: (err: unknown) => void) => Promise<TransactionReceipt | null>;
78
+ /**
79
+ * Fetch a transaction by hash. Used by downstream tx-tracker for
80
+ * replacement detection (looks up the (from, nonce) pair so the
81
+ * tracker can detect a different hash with the same nonce mining).
82
+ */
83
+ export declare const fetchTransaction: (client: PublicClient, hash: string, onError?: (err: unknown) => void) => Promise<RawTx | null>;
84
+ //# sourceMappingURL=transport.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAA;AAExC,OAAO,KAAK,EACV,WAAW,EACX,gBAAgB,EAChB,KAAK,EACL,kBAAkB,EAClB,aAAa,EACd,MAAM,YAAY,CAAA;AAEnB,uEAAuE;AACvE,eAAO,MAAM,QAAQ,QAAyB,CAAA;AAE9C;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,GAAU,CAAC,EACjC,QAAQ,YAAY,EACpB,QAAQ,MAAM,EACd,QAAQ,OAAO,EAAE,EACjB,UAAU,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,KAC/B,OAAO,CAAC,CAAC,GAAG,IAAI,CAWlB,CAAA;AAKD;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,YAAY,EACpB,UAAU,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,KAC/B,OAAO,CAAC,MAAM,GAAG,IAAI,CAQvB,CAAA;AAED;;;;;GAKG;AACH,eAAO,MAAM,UAAU,GACrB,QAAQ,YAAY,EACpB,KAAK,QAAQ,GAAG,MAAM,EACtB,UAAU,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,KAC/B,OAAO,CAAC,WAAW,GAAG,IAAI,CAM1B,CAAA;AAEH;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAC1B,QAAQ,YAAY,EACpB,YAAY,MAAM,EAClB,aAAa,MAAM,EAAE,EACrB,UAAU,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,KAC/B,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAM/B,CAAA;AAEH;;;;;GAKG;AACH,eAAO,MAAM,WAAW,GACtB,QAAQ,YAAY,EACpB,UAAU,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,KAC/B,OAAO,CAAC,aAAa,GAAG,IAAI,CACoC,CAAA;AAEnE;;;;GAIG;AACH,eAAO,MAAM,YAAY,GACvB,QAAQ,YAAY,EACpB,MAAM,MAAM,EACZ,UAAU,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,KAC/B,OAAO,CAAC,kBAAkB,GAAG,IAAI,CAMjC,CAAA;AAEH;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAC3B,QAAQ,YAAY,EACpB,MAAM,MAAM,EACZ,UAAU,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,KAC/B,OAAO,CAAC,KAAK,GAAG,IAAI,CACkD,CAAA"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Low-level RPC fan-out for `@valve-tech/chain-source`. One thin
3
+ * wrapper per JSON-RPC method the source needs:
4
+ *
5
+ * - `eth_blockNumber` — cheap head probe (block-gating).
6
+ * - `eth_getBlockByNumber` — full block (header + txs).
7
+ * - `eth_feeHistory` — base-fee history + percentile rewards.
8
+ * - `txpool_content` — pending + queued mempool snapshot.
9
+ * - `eth_getTransactionReceipt` — inclusion-watch fallback.
10
+ * - `eth_getTransactionByHash` — replacement detection (from, nonce).
11
+ *
12
+ * Each wrapper uses the same `safeRequest` posture: turn errors,
13
+ * `null`, and `undefined` upstream responses into a `null` return —
14
+ * never throw across this layer. The higher-level source module
15
+ * decides what each `null` means (capability gated, transient
16
+ * upstream failure, no such tx).
17
+ *
18
+ * Why not viem's typed methods? Two reasons:
19
+ *
20
+ * 1. `txpool_content` is non-standard, so we'd be reaching for
21
+ * `client.request` for it anyway. Doing every method through
22
+ * the same shape keeps call sites symmetric.
23
+ * 2. Some fields of interest (`excessBlobGas`, `blobGasUsed`,
24
+ * `parentHash`) are surfaced inconsistently across viem versions
25
+ * in the typed `getBlock` shape. Going straight through
26
+ * `client.request` gives us the wire response untouched and the
27
+ * types we declare here describe exactly what we actually use.
28
+ */
29
+ /** Canonical 32-byte zero hash. Used for the receipt-by-hash probe. */
30
+ export const zeroHash = `0x${'00'.repeat(32)}`;
31
+ /**
32
+ * Issue an arbitrary JSON-RPC method through the client. A single
33
+ * failure (method-not-found, transport error, malformed response)
34
+ * becomes `null` rather than a thrown exception — the caller decides
35
+ * what each missing-data case means. This is the load-bearing
36
+ * convention for capability-gated methods like `txpool_content`.
37
+ */
38
+ export const safeRequest = async (client, method, params, onError) => {
39
+ try {
40
+ // viem's `request` typing is parameterized over a known method
41
+ // union; for non-standard methods (txpool_content, et al.) we
42
+ // cast through `as never` to bypass the union narrowing.
43
+ const result = (await client.request({ method, params }));
44
+ return result ?? null;
45
+ }
46
+ catch (err) {
47
+ if (onError)
48
+ onError(err);
49
+ return null;
50
+ }
51
+ };
52
+ const blockTagOf = (tag) => tag === 'latest' ? 'latest' : `0x${tag.toString(16)}`;
53
+ /**
54
+ * Fetch the latest head block number as a `bigint`. Used by the
55
+ * source's block-gating optimization: when the head hasn't moved
56
+ * since the previous tick, the expensive cycle (full block + fee
57
+ * history + mempool) is skipped because no fee landscape change is
58
+ * possible without a new block.
59
+ *
60
+ * Returns `null` if the upstream `eth_blockNumber` fails or returns
61
+ * something that won't decode as a bigint.
62
+ */
63
+ export const fetchHeadBlockNumber = async (client, onError) => {
64
+ const head = await safeRequest(client, 'eth_blockNumber', [], onError);
65
+ if (head === null)
66
+ return null;
67
+ try {
68
+ return BigInt(head);
69
+ }
70
+ catch {
71
+ return null;
72
+ }
73
+ };
74
+ /**
75
+ * Fetch a block (full transactions). `tag` may be `'latest'` or an
76
+ * absolute block number as a `bigint`; the bigint is hex-encoded
77
+ * before the RPC call. Returns the raw `BlockResult` (hex-encoded
78
+ * numeric fields untouched) or `null` if the upstream failed.
79
+ */
80
+ export const fetchBlock = async (client, tag, onError) => safeRequest(client, 'eth_getBlockByNumber', [blockTagOf(tag), true], onError);
81
+ /**
82
+ * Fetch fee history. `blockCount` is hex-encoded per the spec; the
83
+ * latest block is the implicit upper bound. `percentiles` is passed
84
+ * through unchanged (RPC accepts a JSON number array).
85
+ */
86
+ export const fetchFeeHistory = async (client, blockCount, percentiles, onError) => safeRequest(client, 'eth_feeHistory', [`0x${blockCount.toString(16)}`, 'latest', percentiles], onError);
87
+ /**
88
+ * Fetch a `txpool_content` snapshot. `null` is the expected return
89
+ * when the provider gates the method (most public RPCs do). The
90
+ * source surfaces this via `capabilities().txpoolContent === 'gated'`
91
+ * so consumers can react.
92
+ */
93
+ export const fetchTxPool = async (client, onError) => safeRequest(client, 'txpool_content', [], onError);
94
+ /**
95
+ * Fetch a transaction receipt by hash. `null` covers both "no such
96
+ * tx" and "method not available." Distinguish the two via
97
+ * `capabilities().receiptByHash`.
98
+ */
99
+ export const fetchReceipt = async (client, hash, onError) => safeRequest(client, 'eth_getTransactionReceipt', [hash], onError);
100
+ /**
101
+ * Fetch a transaction by hash. Used by downstream tx-tracker for
102
+ * replacement detection (looks up the (from, nonce) pair so the
103
+ * tracker can detect a different hash with the same nonce mining).
104
+ */
105
+ export const fetchTransaction = async (client, hash, onError) => safeRequest(client, 'eth_getTransactionByHash', [hash], onError);
106
+ //# sourceMappingURL=transport.js.map