@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 +93 -0
- package/README.md +76 -18
- package/dist/capabilities.d.ts +42 -0
- package/dist/capabilities.d.ts.map +1 -0
- package/dist/capabilities.js +98 -0
- package/dist/capabilities.js.map +1 -0
- package/dist/index.d.ts +27 -9
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +30 -1
- package/dist/index.js.map +1 -1
- package/dist/mempool.d.ts +37 -0
- package/dist/mempool.d.ts.map +1 -0
- package/dist/mempool.js +54 -0
- package/dist/mempool.js.map +1 -0
- package/dist/source.d.ts +128 -0
- package/dist/source.d.ts.map +1 -0
- package/dist/source.js +198 -0
- package/dist/source.js.map +1 -0
- package/dist/subscriptions.d.ts +42 -0
- package/dist/subscriptions.d.ts.map +1 -0
- package/dist/subscriptions.js +66 -0
- package/dist/subscriptions.js.map +1 -0
- package/dist/transport.d.ts +84 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +106 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +161 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +22 -0
- package/dist/types.js.map +1 -0
- package/package.json +1 -1
package/dist/source.d.ts
ADDED
|
@@ -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
|