@valve-tech/gas-oracle 0.2.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/LICENSE +21 -0
- package/README.md +270 -0
- package/dist/block-position.d.ts +97 -0
- package/dist/block-position.d.ts.map +1 -0
- package/dist/block-position.js +131 -0
- package/dist/block-position.js.map +1 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/math.d.ts +148 -0
- package/dist/math.d.ts.map +1 -0
- package/dist/math.js +343 -0
- package/dist/math.js.map +1 -0
- package/dist/mempool.d.ts +89 -0
- package/dist/mempool.d.ts.map +1 -0
- package/dist/mempool.js +108 -0
- package/dist/mempool.js.map +1 -0
- package/dist/oracle.d.ts +139 -0
- package/dist/oracle.d.ts.map +1 -0
- package/dist/oracle.js +208 -0
- package/dist/oracle.js.map +1 -0
- package/dist/samples.d.ts +36 -0
- package/dist/samples.d.ts.map +1 -0
- package/dist/samples.js +107 -0
- package/dist/samples.js.map +1 -0
- package/dist/transport.d.ts +75 -0
- package/dist/transport.d.ts.map +1 -0
- package/dist/transport.js +72 -0
- package/dist/transport.js.map +1 -0
- package/dist/types.d.ts +168 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +11 -0
- package/dist/types.js.map +1 -0
- package/dist/viem-actions.d.ts +77 -0
- package/dist/viem-actions.d.ts.map +1 -0
- package/dist/viem-actions.js +118 -0
- package/dist/viem-actions.js.map +1 -0
- package/dist/viem-transport.d.ts +85 -0
- package/dist/viem-transport.d.ts.map +1 -0
- package/dist/viem-transport.js +165 -0
- package/dist/viem-transport.js.map +1 -0
- package/package.json +81 -0
- package/src/index.ts +84 -0
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* viem-native RPC transport. The oracle needs three calls per cycle:
|
|
3
|
+
*
|
|
4
|
+
* 1. eth_feeHistory(20, 'latest', [10, 25, 50, 75, 90])
|
|
5
|
+
* — base-fee history + percentile rewards, for trend detection
|
|
6
|
+
* and as a fallback when a block has zero txs of our own to
|
|
7
|
+
* percentile.
|
|
8
|
+
* 2. eth_getBlockByNumber('latest', true)
|
|
9
|
+
* — full block (header + all txs) so we can compute the same
|
|
10
|
+
* effectiveTip miners themselves maximize.
|
|
11
|
+
* 3. txpool_content
|
|
12
|
+
* — pending + queued txs by sender. Best-effort: many public
|
|
13
|
+
* RPC providers gate this method (returns 405/method-not-found),
|
|
14
|
+
* and a missing mempool just degrades the oracle to block-only
|
|
15
|
+
* signal — it does NOT fail the cycle.
|
|
16
|
+
*
|
|
17
|
+
* Why not viem's getBlock/getFeeHistory directly? `txpool_content` is
|
|
18
|
+
* a non-standard RPC, so we'd reach for `client.request` for that one
|
|
19
|
+
* anyway. Doing all three through `client.request` keeps the call shapes
|
|
20
|
+
* symmetric and avoids viem's type narrowing where we want raw fields
|
|
21
|
+
* (excessBlobGas, blobGasUsed) that viem currently doesn't surface in
|
|
22
|
+
* its decoded Block type uniformly across versions.
|
|
23
|
+
*/
|
|
24
|
+
import type { PublicClient } from 'viem';
|
|
25
|
+
import type { PollOptions, RawTx } from './types.js';
|
|
26
|
+
/** Result shape returned by `eth_feeHistory`. */
|
|
27
|
+
export interface FeeHistoryResult {
|
|
28
|
+
baseFeePerGas: string[];
|
|
29
|
+
reward?: string[][];
|
|
30
|
+
gasUsedRatio: number[];
|
|
31
|
+
oldestBlock: string;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Result shape returned by `eth_getBlockByNumber` with `fullTransactions=true`.
|
|
35
|
+
* Hex-encoded numbers are kept as strings — the oracle decodes them at the
|
|
36
|
+
* point of use to keep transport boundary-typed and the math layer pure.
|
|
37
|
+
*/
|
|
38
|
+
export interface BlockResult {
|
|
39
|
+
number: string;
|
|
40
|
+
hash?: string;
|
|
41
|
+
parentHash?: string;
|
|
42
|
+
timestamp: string;
|
|
43
|
+
baseFeePerGas: string;
|
|
44
|
+
gasLimit: string;
|
|
45
|
+
gasUsed: string;
|
|
46
|
+
transactions: RawTx[];
|
|
47
|
+
excessBlobGas?: string;
|
|
48
|
+
blobGasUsed?: string;
|
|
49
|
+
}
|
|
50
|
+
export interface TxPoolContent {
|
|
51
|
+
pending: Record<string, Record<string, RawTx>>;
|
|
52
|
+
queued: Record<string, Record<string, RawTx>>;
|
|
53
|
+
}
|
|
54
|
+
export interface OraclePollInputs {
|
|
55
|
+
feeHistory: FeeHistoryResult | null;
|
|
56
|
+
block: BlockResult | null;
|
|
57
|
+
txPool: TxPoolContent | null;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* One poll cycle's RPC fan-out. Returns whatever could be fetched —
|
|
61
|
+
* `null` for any sub-call that failed OR was disabled by `poll`. The
|
|
62
|
+
* caller (`oracle.ts`) handles the case where `block` is null (cycle
|
|
63
|
+
* aborts) and where `feeHistory`/`txPool` is null (those signals are
|
|
64
|
+
* dropped, see reducePollInputs).
|
|
65
|
+
*
|
|
66
|
+
* `poll.feeHistory` and `poll.mempool` default to `true`. When `false`,
|
|
67
|
+
* the corresponding RPC is skipped entirely (not even attempted) — this
|
|
68
|
+
* is for callers who know upstream gates the method or who want to
|
|
69
|
+
* minimize their RPC budget. `eth_getBlockByNumber` is non-toggleable.
|
|
70
|
+
*/
|
|
71
|
+
export declare const fetchOracleInputs: (client: PublicClient, options?: {
|
|
72
|
+
onError?: (method: string, err: unknown) => void;
|
|
73
|
+
poll?: PollOptions;
|
|
74
|
+
}) => Promise<OraclePollInputs>;
|
|
75
|
+
//# sourceMappingURL=transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.d.ts","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAA;AAExC,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAEpD,iDAAiD;AACjD,MAAM,WAAW,gBAAgB;IAC/B,aAAa,EAAE,MAAM,EAAE,CAAA;IACvB,MAAM,CAAC,EAAE,MAAM,EAAE,EAAE,CAAA;IACnB,YAAY,EAAE,MAAM,EAAE,CAAA;IACtB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,aAAa,EAAE,MAAM,CAAA;IACrB,QAAQ,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,YAAY,EAAE,KAAK,EAAE,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAA;IAC9C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAA;CAC9C;AAED,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,gBAAgB,GAAG,IAAI,CAAA;IACnC,KAAK,EAAE,WAAW,GAAG,IAAI,CAAA;IACzB,MAAM,EAAE,aAAa,GAAG,IAAI,CAAA;CAC7B;AA2BD;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,iBAAiB,GAC5B,QAAQ,YAAY,EACpB,UAAS;IACP,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;IAChD,IAAI,CAAC,EAAE,WAAW,CAAA;CACd,KACL,OAAO,CAAC,gBAAgB,CAiC1B,CAAA"}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* viem-native RPC transport. The oracle needs three calls per cycle:
|
|
3
|
+
*
|
|
4
|
+
* 1. eth_feeHistory(20, 'latest', [10, 25, 50, 75, 90])
|
|
5
|
+
* — base-fee history + percentile rewards, for trend detection
|
|
6
|
+
* and as a fallback when a block has zero txs of our own to
|
|
7
|
+
* percentile.
|
|
8
|
+
* 2. eth_getBlockByNumber('latest', true)
|
|
9
|
+
* — full block (header + all txs) so we can compute the same
|
|
10
|
+
* effectiveTip miners themselves maximize.
|
|
11
|
+
* 3. txpool_content
|
|
12
|
+
* — pending + queued txs by sender. Best-effort: many public
|
|
13
|
+
* RPC providers gate this method (returns 405/method-not-found),
|
|
14
|
+
* and a missing mempool just degrades the oracle to block-only
|
|
15
|
+
* signal — it does NOT fail the cycle.
|
|
16
|
+
*
|
|
17
|
+
* Why not viem's getBlock/getFeeHistory directly? `txpool_content` is
|
|
18
|
+
* a non-standard RPC, so we'd reach for `client.request` for that one
|
|
19
|
+
* anyway. Doing all three through `client.request` keeps the call shapes
|
|
20
|
+
* symmetric and avoids viem's type narrowing where we want raw fields
|
|
21
|
+
* (excessBlobGas, blobGasUsed) that viem currently doesn't surface in
|
|
22
|
+
* its decoded Block type uniformly across versions.
|
|
23
|
+
*/
|
|
24
|
+
/**
|
|
25
|
+
* Issue an arbitrary JSON-RPC method through the client. Wraps
|
|
26
|
+
* `client.request` so a single failure (method-not-found, transport
|
|
27
|
+
* error, malformed response) becomes `null` rather than a thrown
|
|
28
|
+
* exception — the caller decides how to interpret missing data. This
|
|
29
|
+
* matches the original oracle's behavior where a missing `txpool_content`
|
|
30
|
+
* gracefully falls through to block-only signal.
|
|
31
|
+
*/
|
|
32
|
+
const safeRequest = async (client, method, params, onError) => {
|
|
33
|
+
try {
|
|
34
|
+
// viem's request typing is parameterized over a known method union;
|
|
35
|
+
// for non-standard methods (txpool_content) we cast through unknown.
|
|
36
|
+
const result = (await client.request({ method, params }));
|
|
37
|
+
return result ?? null;
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
if (onError)
|
|
41
|
+
onError(err);
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* One poll cycle's RPC fan-out. Returns whatever could be fetched —
|
|
47
|
+
* `null` for any sub-call that failed OR was disabled by `poll`. The
|
|
48
|
+
* caller (`oracle.ts`) handles the case where `block` is null (cycle
|
|
49
|
+
* aborts) and where `feeHistory`/`txPool` is null (those signals are
|
|
50
|
+
* dropped, see reducePollInputs).
|
|
51
|
+
*
|
|
52
|
+
* `poll.feeHistory` and `poll.mempool` default to `true`. When `false`,
|
|
53
|
+
* the corresponding RPC is skipped entirely (not even attempted) — this
|
|
54
|
+
* is for callers who know upstream gates the method or who want to
|
|
55
|
+
* minimize their RPC budget. `eth_getBlockByNumber` is non-toggleable.
|
|
56
|
+
*/
|
|
57
|
+
export const fetchOracleInputs = async (client, options = {}) => {
|
|
58
|
+
const onErr = (method) => options.onError ? (err) => options.onError(method, err) : undefined;
|
|
59
|
+
const fetchFeeHistory = options.poll?.feeHistory !== false;
|
|
60
|
+
const fetchMempool = options.poll?.mempool !== false;
|
|
61
|
+
const [feeHistory, block, txPool] = await Promise.all([
|
|
62
|
+
fetchFeeHistory
|
|
63
|
+
? safeRequest(client, 'eth_feeHistory', ['0x14', 'latest', [10, 25, 50, 75, 90]], onErr('eth_feeHistory'))
|
|
64
|
+
: Promise.resolve(null),
|
|
65
|
+
safeRequest(client, 'eth_getBlockByNumber', ['latest', true], onErr('eth_getBlockByNumber')),
|
|
66
|
+
fetchMempool
|
|
67
|
+
? safeRequest(client, 'txpool_content', [], onErr('txpool_content'))
|
|
68
|
+
: Promise.resolve(null),
|
|
69
|
+
]);
|
|
70
|
+
return { feeHistory, block, txPool };
|
|
71
|
+
};
|
|
72
|
+
//# sourceMappingURL=transport.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transport.js","sourceRoot":"","sources":["../src/transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AA2CH;;;;;;;GAOG;AACH,MAAM,WAAW,GAAG,KAAK,EACvB,MAAoB,EACpB,MAAc,EACd,MAAiB,EACjB,OAAgC,EACb,EAAE;IACrB,IAAI,CAAC;QACH,oEAAoE;QACpE,qEAAqE;QACrE,MAAM,MAAM,GAAG,CAAC,MAAM,MAAM,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAW,CAAC,CAAM,CAAA;QACvE,OAAO,MAAM,IAAI,IAAI,CAAA;IACvB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,IAAI,OAAO;YAAE,OAAO,CAAC,GAAG,CAAC,CAAA;QACzB,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC,CAAA;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,KAAK,EACpC,MAAoB,EACpB,UAGI,EAAE,EACqB,EAAE;IAC7B,MAAM,KAAK,GAAG,CAAC,MAAc,EAAE,EAAE,CAC/B,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAY,EAAE,EAAE,CAAC,OAAO,CAAC,OAAQ,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAA;IAE/E,MAAM,eAAe,GAAG,OAAO,CAAC,IAAI,EAAE,UAAU,KAAK,KAAK,CAAA;IAC1D,MAAM,YAAY,GAAG,OAAO,CAAC,IAAI,EAAE,OAAO,KAAK,KAAK,CAAA;IAEpD,MAAM,CAAC,UAAU,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QACpD,eAAe;YACb,CAAC,CAAC,WAAW,CACT,MAAM,EACN,gBAAgB,EAChB,CAAC,MAAM,EAAE,QAAQ,EAAE,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,EACxC,KAAK,CAAC,gBAAgB,CAAC,CACxB;YACH,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;QACzB,WAAW,CACT,MAAM,EACN,sBAAsB,EACtB,CAAC,QAAQ,EAAE,IAAI,CAAC,EAChB,KAAK,CAAC,sBAAsB,CAAC,CAC9B;QACD,YAAY;YACV,CAAC,CAAC,WAAW,CACT,MAAM,EACN,gBAAgB,EAChB,EAAE,EACF,KAAK,CAAC,gBAAgB,CAAC,CACxB;YACH,CAAC,CAAC,OAAO,CAAC,OAAO,CAAC,IAAI,CAAC;KAC1B,CAAC,CAAA;IAEF,OAAO,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,EAAE,CAAA;AACtC,CAAC,CAAA"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the gas oracle. Pure data shapes — no deps.
|
|
3
|
+
*
|
|
4
|
+
* Wire format note: every fee field is a `bigint`. Callers serializing
|
|
5
|
+
* across HTTP / Redis / WebSocket should hex-encode (`'0x' + n.toString(16)`)
|
|
6
|
+
* since JSON has no native bigint and `JSON.stringify` will throw on raw
|
|
7
|
+
* bigint values. The caller owns that encoding — this package keeps the
|
|
8
|
+
* canonical numeric form internally.
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Minimal tx shape extracted from `eth_getBlockByNumber(latest, true)` or
|
|
12
|
+
* `txpool_content`. Fee fields drive tip math; `hash`, `from`, `nonce`
|
|
13
|
+
* support mempool lookups (`findByHash` / `findByAddressNonce`). Other
|
|
14
|
+
* tx fields (to/value/data/etc.) are ignored.
|
|
15
|
+
*
|
|
16
|
+
* `hash`, `from`, `nonce` are nominally optional because `eth_get-
|
|
17
|
+
* BlockByNumber(_, true)` blocks always carry them but we want callers
|
|
18
|
+
* to be able to construct `RawTx` from minimal fixtures in tests.
|
|
19
|
+
* geth/reth `txpool_content` includes all three.
|
|
20
|
+
*/
|
|
21
|
+
export interface RawTx {
|
|
22
|
+
maxPriorityFeePerGas?: string;
|
|
23
|
+
maxFeePerGas?: string;
|
|
24
|
+
gasPrice?: string;
|
|
25
|
+
gas?: string;
|
|
26
|
+
type?: string;
|
|
27
|
+
hash?: string;
|
|
28
|
+
from?: string;
|
|
29
|
+
nonce?: string;
|
|
30
|
+
}
|
|
31
|
+
export interface TipPercentiles {
|
|
32
|
+
p10: bigint;
|
|
33
|
+
p25: bigint;
|
|
34
|
+
p50: bigint;
|
|
35
|
+
p75: bigint;
|
|
36
|
+
p90: bigint;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* One fee-tier recommendation. `gasPrice` is included for legacy callers
|
|
40
|
+
* (type-0/1 txs) and is computed as `baseFee + maxPriorityFeePerGas`.
|
|
41
|
+
* `maxFeePerBlobGas` is null on chains without EIP-4844 support.
|
|
42
|
+
*/
|
|
43
|
+
export interface TierRecommendation {
|
|
44
|
+
maxPriorityFeePerGas: bigint;
|
|
45
|
+
maxFeePerGas: bigint;
|
|
46
|
+
gasPrice: bigint;
|
|
47
|
+
maxFeePerBlobGas: bigint | null;
|
|
48
|
+
}
|
|
49
|
+
export type Trend = 'rising' | 'falling' | 'stable';
|
|
50
|
+
export type TierName = 'slow' | 'standard' | 'fast' | 'instant';
|
|
51
|
+
export interface MempoolStats {
|
|
52
|
+
pendingCount: number;
|
|
53
|
+
queuedCount: number;
|
|
54
|
+
/** Sum of `tx.gas` across all pending txs — congestion proxy. */
|
|
55
|
+
pendingGasDemand: bigint;
|
|
56
|
+
/** Latest block's gas limit, useful for "pending demand vs. block capacity". */
|
|
57
|
+
blockGasLimit: bigint;
|
|
58
|
+
}
|
|
59
|
+
export interface BlobStats {
|
|
60
|
+
blobBaseFee: bigint;
|
|
61
|
+
excessBlobGas: bigint;
|
|
62
|
+
blobGasUsed: bigint;
|
|
63
|
+
blobBaseFeeTrend: Trend;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* One per-tx contribution to the priority-fee distribution.
|
|
67
|
+
*
|
|
68
|
+
* - `tip` — effective per-gas tip miners maximize for inclusion.
|
|
69
|
+
* - `gas` — declared gas; the gas-weighting denominator for percentile math.
|
|
70
|
+
* - `txType` — EIP-2718 type byte (0 legacy, 1 EIP-2930, 2 EIP-1559, 3
|
|
71
|
+
* EIP-4844, 4 EIP-7702, …). Used by `priorityModel: 'eip1559'`
|
|
72
|
+
* filtering to keep paying-lane tiers honest on chains that
|
|
73
|
+
* honor the cutoff.
|
|
74
|
+
* - `hash` — the tx hash, for `tipForBlockPosition({ kind: 'aheadOf' })`-
|
|
75
|
+
* style relative targeting. Optional because tests construct
|
|
76
|
+
* minimal samples by hand.
|
|
77
|
+
* - `address` — sender address, lowercased. Pairs with `nonce` to identify
|
|
78
|
+
* a tx without needing its hash.
|
|
79
|
+
* - `nonce` — decimal-string normalized.
|
|
80
|
+
*/
|
|
81
|
+
export interface TipSample {
|
|
82
|
+
tip: bigint;
|
|
83
|
+
gas: bigint;
|
|
84
|
+
txType?: number;
|
|
85
|
+
hash?: string;
|
|
86
|
+
address?: string;
|
|
87
|
+
nonce?: string;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Where the chain's inclusion logic draws its priority cutoff in the
|
|
91
|
+
* tx-type space.
|
|
92
|
+
*
|
|
93
|
+
* - `'flat'` — chain ignores the EIP-2718 type byte for ordering.
|
|
94
|
+
* Tiers derive from a single gas-weighted distribution
|
|
95
|
+
* across all txs. Right for extractive validators
|
|
96
|
+
* (PulseChain et al.) where the only signal that matters
|
|
97
|
+
* is fee per gas, regardless of tx envelope.
|
|
98
|
+
* - `'eip1559'` — type 2+ txs get priority. Paying-lane tiers
|
|
99
|
+
* (standard/fast/instant) derive from type-2+ samples
|
|
100
|
+
* only; `slow` still draws from the full distribution
|
|
101
|
+
* so legacy senders find their lane. Right for chains
|
|
102
|
+
* that honor the 1559 fee-market shape.
|
|
103
|
+
*
|
|
104
|
+
* Future cutoffs can be added (e.g. `'eip4844'` for blob-only priority)
|
|
105
|
+
* without re-interpreting existing values.
|
|
106
|
+
*/
|
|
107
|
+
export type PriorityModel = 'flat' | 'eip1559';
|
|
108
|
+
/**
|
|
109
|
+
* Producer-side toggles: which RPCs the oracle calls upstream each cycle.
|
|
110
|
+
*
|
|
111
|
+
* Fields default to true. `eth_getBlockByNumber` is intentionally not
|
|
112
|
+
* toggleable — without a block we can't compute anything.
|
|
113
|
+
*
|
|
114
|
+
* - `feeHistory: false` — drops `eth_feeHistory`. Trend detection falls
|
|
115
|
+
* back to a single-element history (always
|
|
116
|
+
* reports `'stable'`); percentile fallback
|
|
117
|
+
* becomes block-only.
|
|
118
|
+
* - `mempool: false` — drops `txpool_content`. Tiers reflect block
|
|
119
|
+
* inclusion only, not pending pressure. Useful
|
|
120
|
+
* when the upstream provider gates the
|
|
121
|
+
* method (many public RPCs return 405).
|
|
122
|
+
*/
|
|
123
|
+
export interface PollOptions {
|
|
124
|
+
feeHistory?: boolean;
|
|
125
|
+
mempool?: boolean;
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* One block's worth of state retained in the rolling ring. Per-tx tips
|
|
129
|
+
* + gas are kept so we can re-percentile on each poll without re-fetching
|
|
130
|
+
* historical blocks. Header fields support the reorg/missed-poll detector
|
|
131
|
+
* (see spec §7).
|
|
132
|
+
*/
|
|
133
|
+
export interface BlockSample {
|
|
134
|
+
number: bigint;
|
|
135
|
+
hash: string;
|
|
136
|
+
parentHash: string;
|
|
137
|
+
baseFee: bigint;
|
|
138
|
+
gasUsed: bigint;
|
|
139
|
+
tips: TipSample[];
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Snapshot of a single chain's gas-oracle state. Re-published on every
|
|
143
|
+
* poll cycle. `lastPublishedTips`, `lastPublishedBlockNumber`, and `ring`
|
|
144
|
+
* are the producer-local cap anchor + sample buffer used by `cappedTip`
|
|
145
|
+
* and tier construction; consumers receiving a serialized snapshot can
|
|
146
|
+
* ignore them (the relay's `toPublishable` strips them before publish).
|
|
147
|
+
*/
|
|
148
|
+
export interface GasOracleState {
|
|
149
|
+
chainId: number;
|
|
150
|
+
blockNumber: bigint;
|
|
151
|
+
timestamp: bigint;
|
|
152
|
+
baseFee: bigint;
|
|
153
|
+
baseFeeTrend: Trend;
|
|
154
|
+
baseFeeHistory: bigint[];
|
|
155
|
+
mempool: MempoolStats;
|
|
156
|
+
blob: BlobStats | null;
|
|
157
|
+
tiers: Record<TierName, TierRecommendation>;
|
|
158
|
+
/**
|
|
159
|
+
* Rolling ring of recent blocks (oldest → newest). Producer-only.
|
|
160
|
+
* Currently always single-element; the 20-block lifecycle (append /
|
|
161
|
+
* bridgeGap / clearAndBackfill) is deferred to a follow-up per spec
|
|
162
|
+
* §7-§9.
|
|
163
|
+
*/
|
|
164
|
+
ring: BlockSample[];
|
|
165
|
+
lastPublishedTips?: Record<TierName, bigint>;
|
|
166
|
+
lastPublishedBlockNumber?: bigint;
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the gas oracle. Pure data shapes — no deps.
|
|
3
|
+
*
|
|
4
|
+
* Wire format note: every fee field is a `bigint`. Callers serializing
|
|
5
|
+
* across HTTP / Redis / WebSocket should hex-encode (`'0x' + n.toString(16)`)
|
|
6
|
+
* since JSON has no native bigint and `JSON.stringify` will throw on raw
|
|
7
|
+
* bigint values. The caller owns that encoding — this package keeps the
|
|
8
|
+
* canonical numeric form internally.
|
|
9
|
+
*/
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @valve-tech/gas-oracle/viem-actions — viem client extension.
|
|
3
|
+
*
|
|
4
|
+
* Use when callers want explicit access to the structured tier shape:
|
|
5
|
+
*
|
|
6
|
+
* const client = createPublicClient({ chain, transport: http() })
|
|
7
|
+
* .extend(gasOracleActions({
|
|
8
|
+
* chainId: 1,
|
|
9
|
+
* priorityFeeDecayCap: parseEther('0.125'),
|
|
10
|
+
* priorityModel: 'eip1559',
|
|
11
|
+
* }))
|
|
12
|
+
*
|
|
13
|
+
* await client.getGasTiers() // full snapshot
|
|
14
|
+
* await client.getGasTier('fast') // one tier
|
|
15
|
+
*
|
|
16
|
+
* The actions own a per-extension `GasOracle` keyed off the supplied
|
|
17
|
+
* chainId. The poll loop starts eagerly by default so the first read is
|
|
18
|
+
* served from a populated cache; pass `lifecycle: 'lazy'` to defer
|
|
19
|
+
* polling to the first read instead. Either way the underlying client
|
|
20
|
+
* is what drives the upstream RPC (the same transport viem already
|
|
21
|
+
* uses for everything else), so callers don't have to wire a second
|
|
22
|
+
* URL just to feed the oracle.
|
|
23
|
+
*
|
|
24
|
+
* Stopping the poller is the caller's responsibility — viem clients
|
|
25
|
+
* have no explicit lifecycle, so `stopGasOracle()` is exposed on the
|
|
26
|
+
* extended client for shutdown hooks (Next.js HMR, test teardown,
|
|
27
|
+
* one-shot scripts) that need to drop the interval timer cleanly.
|
|
28
|
+
*/
|
|
29
|
+
import type { PublicClient } from 'viem';
|
|
30
|
+
import { type BlockPositionQuery, type BlockPositionResult } from './block-position.js';
|
|
31
|
+
import { type MempoolHit, type TxIdentifier } from './mempool.js';
|
|
32
|
+
import { type CreateGasOracleOptions } from './oracle.js';
|
|
33
|
+
import type { GasOracleState, TierName, TierRecommendation } from './types.js';
|
|
34
|
+
export interface GasOracleActionsOptions extends Omit<CreateGasOracleOptions, 'client'> {
|
|
35
|
+
/**
|
|
36
|
+
* When the oracle starts polling.
|
|
37
|
+
* - `'eager'` (default): poll loop begins as soon as the extension is
|
|
38
|
+
* attached — first read is served from cache.
|
|
39
|
+
* - `'lazy'`: poll loop starts on the first `getGasTiers`
|
|
40
|
+
* / `getGasTier` call. Trades a small first-read
|
|
41
|
+
* latency for not running background RPCs the
|
|
42
|
+
* caller never asked for.
|
|
43
|
+
*/
|
|
44
|
+
lifecycle?: 'eager' | 'lazy';
|
|
45
|
+
}
|
|
46
|
+
export interface GasOracleActions {
|
|
47
|
+
/**
|
|
48
|
+
* Latest tier snapshot. When state hasn't been populated yet (cold
|
|
49
|
+
* start), runs an out-of-band poll to seed it before returning.
|
|
50
|
+
*/
|
|
51
|
+
getGasTiers: () => Promise<GasOracleState>;
|
|
52
|
+
/** One specific tier's recommendation. Same backing state as getGasTiers. */
|
|
53
|
+
getGasTier: (name: TierName) => Promise<TierRecommendation>;
|
|
54
|
+
/**
|
|
55
|
+
* Look up a tx in the mempool by hash or by sender address + nonce.
|
|
56
|
+
* When `keepMempoolSnapshot: true` is set on the actions options,
|
|
57
|
+
* served from the oracle's cached snapshot (no extra RPC); otherwise
|
|
58
|
+
* issues an on-demand `txpool_content` request.
|
|
59
|
+
*/
|
|
60
|
+
findTxInMempool: (id: TxIdentifier) => Promise<MempoolHit | null>;
|
|
61
|
+
/**
|
|
62
|
+
* Compute the tip required to land at a target position in the next
|
|
63
|
+
* block. Distribution = ring (last block) ∪ pending mempool, the
|
|
64
|
+
* same union `computeTiers` reads. When mempool data isn't available
|
|
65
|
+
* (gated upstream / `keepMempoolSnapshot: false` / first cold-start
|
|
66
|
+
* read), falls back to ring-only.
|
|
67
|
+
*/
|
|
68
|
+
tipForBlockPosition: (query: BlockPositionQuery) => Promise<BlockPositionResult>;
|
|
69
|
+
/**
|
|
70
|
+
* Stop the underlying poller. Idempotent. After stop, subsequent
|
|
71
|
+
* reads will throw — there's no auto-restart, since restart timing
|
|
72
|
+
* is the caller's concern (HMR vs. test teardown vs. real shutdown).
|
|
73
|
+
*/
|
|
74
|
+
stopGasOracle: () => void;
|
|
75
|
+
}
|
|
76
|
+
export declare const gasOracleActions: (options: GasOracleActionsOptions) => (client: PublicClient) => GasOracleActions;
|
|
77
|
+
//# sourceMappingURL=viem-actions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viem-actions.d.ts","sourceRoot":"","sources":["../src/viem-actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAA;AAExC,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,mBAAmB,EACzB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAGL,KAAK,UAAU,EAEf,KAAK,YAAY,EAClB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAmB,KAAK,sBAAsB,EAAkB,MAAM,aAAa,CAAA;AAE1F,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,kBAAkB,EAAa,MAAM,YAAY,CAAA;AAEzF,MAAM,WAAW,uBAAwB,SAAQ,IAAI,CAAC,sBAAsB,EAAE,QAAQ,CAAC;IACrF;;;;;;;;OAQG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;CAC7B;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,WAAW,EAAE,MAAM,OAAO,CAAC,cAAc,CAAC,CAAA;IAC1C,6EAA6E;IAC7E,UAAU,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAA;IAC3D;;;;;OAKG;IACH,eAAe,EAAE,CAAC,EAAE,EAAE,YAAY,KAAK,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAA;IACjE;;;;;;OAMG;IACH,mBAAmB,EAAE,CAAC,KAAK,EAAE,kBAAkB,KAAK,OAAO,CAAC,mBAAmB,CAAC,CAAA;IAChF;;;;OAIG;IACH,aAAa,EAAE,MAAM,IAAI,CAAA;CAC1B;AAED,eAAO,MAAM,gBAAgB,GAAI,SAAS,uBAAuB,MAC9D,QAAQ,YAAY,KAAG,gBAuFvB,CAAA"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @valve-tech/gas-oracle/viem-actions — viem client extension.
|
|
3
|
+
*
|
|
4
|
+
* Use when callers want explicit access to the structured tier shape:
|
|
5
|
+
*
|
|
6
|
+
* const client = createPublicClient({ chain, transport: http() })
|
|
7
|
+
* .extend(gasOracleActions({
|
|
8
|
+
* chainId: 1,
|
|
9
|
+
* priorityFeeDecayCap: parseEther('0.125'),
|
|
10
|
+
* priorityModel: 'eip1559',
|
|
11
|
+
* }))
|
|
12
|
+
*
|
|
13
|
+
* await client.getGasTiers() // full snapshot
|
|
14
|
+
* await client.getGasTier('fast') // one tier
|
|
15
|
+
*
|
|
16
|
+
* The actions own a per-extension `GasOracle` keyed off the supplied
|
|
17
|
+
* chainId. The poll loop starts eagerly by default so the first read is
|
|
18
|
+
* served from a populated cache; pass `lifecycle: 'lazy'` to defer
|
|
19
|
+
* polling to the first read instead. Either way the underlying client
|
|
20
|
+
* is what drives the upstream RPC (the same transport viem already
|
|
21
|
+
* uses for everything else), so callers don't have to wire a second
|
|
22
|
+
* URL just to feed the oracle.
|
|
23
|
+
*
|
|
24
|
+
* Stopping the poller is the caller's responsibility — viem clients
|
|
25
|
+
* have no explicit lifecycle, so `stopGasOracle()` is exposed on the
|
|
26
|
+
* extended client for shutdown hooks (Next.js HMR, test teardown,
|
|
27
|
+
* one-shot scripts) that need to drop the interval timer cleanly.
|
|
28
|
+
*/
|
|
29
|
+
import { tipForBlockPosition, } from './block-position.js';
|
|
30
|
+
import { findInMempool, normalizeMempool, } from './mempool.js';
|
|
31
|
+
import { createGasOracle } from './oracle.js';
|
|
32
|
+
export const gasOracleActions = (options) => (client) => {
|
|
33
|
+
const oracle = createGasOracle({ ...options, client });
|
|
34
|
+
const lifecycle = options.lifecycle ?? 'eager';
|
|
35
|
+
let started = false;
|
|
36
|
+
if (lifecycle === 'eager') {
|
|
37
|
+
oracle.start();
|
|
38
|
+
started = true;
|
|
39
|
+
}
|
|
40
|
+
const ensureState = async () => {
|
|
41
|
+
if (!started) {
|
|
42
|
+
oracle.start();
|
|
43
|
+
started = true;
|
|
44
|
+
}
|
|
45
|
+
const cached = oracle.getState();
|
|
46
|
+
if (cached)
|
|
47
|
+
return cached;
|
|
48
|
+
const polled = await oracle.pollOnce();
|
|
49
|
+
if (!polled) {
|
|
50
|
+
throw new Error(`[gas-oracle] poll cycle returned no state — upstream block fetch failed for chain ${options.chainId}`);
|
|
51
|
+
}
|
|
52
|
+
return polled;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Pull a fresh mempool snapshot — from the oracle's cache when
|
|
56
|
+
* `keepMempoolSnapshot: true`, otherwise via an on-demand RPC.
|
|
57
|
+
* Falls back to `null` when the upstream gates `txpool_content`.
|
|
58
|
+
*/
|
|
59
|
+
const fetchMempool = async () => {
|
|
60
|
+
const cached = oracle.getMempoolSnapshot();
|
|
61
|
+
if (cached)
|
|
62
|
+
return cached;
|
|
63
|
+
try {
|
|
64
|
+
const raw = (await client.request({
|
|
65
|
+
method: 'txpool_content',
|
|
66
|
+
params: [],
|
|
67
|
+
}));
|
|
68
|
+
return raw ? normalizeMempool(raw) : null;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
return {
|
|
75
|
+
getGasTiers: ensureState,
|
|
76
|
+
getGasTier: async (name) => (await ensureState()).tiers[name],
|
|
77
|
+
findTxInMempool: async (id) => findInMempool(await fetchMempool(), id),
|
|
78
|
+
tipForBlockPosition: async (query) => {
|
|
79
|
+
const state = await ensureState();
|
|
80
|
+
const ringSamples = state.ring.flatMap((b) => b.tips);
|
|
81
|
+
const mempool = await fetchMempool();
|
|
82
|
+
const mempoolSamples = mempool
|
|
83
|
+
? Object.entries(mempool.pending).flatMap(([address, byNonce]) => Object.entries(byNonce).flatMap(([nonce, tx]) => {
|
|
84
|
+
if (tx.gas === undefined)
|
|
85
|
+
return [];
|
|
86
|
+
const tip = (() => {
|
|
87
|
+
if (tx.maxFeePerGas && tx.maxPriorityFeePerGas) {
|
|
88
|
+
const maxFee = BigInt(tx.maxFeePerGas);
|
|
89
|
+
const maxPriority = BigInt(tx.maxPriorityFeePerGas);
|
|
90
|
+
const headroom = maxFee - state.baseFee;
|
|
91
|
+
if (headroom <= 0n)
|
|
92
|
+
return 0n;
|
|
93
|
+
return maxPriority < headroom ? maxPriority : headroom;
|
|
94
|
+
}
|
|
95
|
+
if (tx.gasPrice) {
|
|
96
|
+
const price = BigInt(tx.gasPrice);
|
|
97
|
+
return price > state.baseFee ? price - state.baseFee : 0n;
|
|
98
|
+
}
|
|
99
|
+
return 0n;
|
|
100
|
+
})();
|
|
101
|
+
return [
|
|
102
|
+
{
|
|
103
|
+
tip,
|
|
104
|
+
gas: BigInt(tx.gas),
|
|
105
|
+
txType: tx.type !== undefined ? Number(tx.type) : undefined,
|
|
106
|
+
hash: tx.hash,
|
|
107
|
+
address,
|
|
108
|
+
nonce,
|
|
109
|
+
},
|
|
110
|
+
];
|
|
111
|
+
}))
|
|
112
|
+
: [];
|
|
113
|
+
return tipForBlockPosition([...ringSamples, ...mempoolSamples], query);
|
|
114
|
+
},
|
|
115
|
+
stopGasOracle: () => oracle.stop(),
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
//# sourceMappingURL=viem-actions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viem-actions.js","sourceRoot":"","sources":["../src/viem-actions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @valve-tech/gas-oracle/viem-transport — viem Transport wrapper.
|
|
3
|
+
*
|
|
4
|
+
* Use when callers want viem's existing API to *just work better*
|
|
5
|
+
* without changing call-site code:
|
|
6
|
+
*
|
|
7
|
+
* const transport = withGasOracle(http(rpcUrl), {
|
|
8
|
+
* chainId: 1,
|
|
9
|
+
* priorityFeeDecayCap: parseEther('0.125'),
|
|
10
|
+
* priorityModel: 'eip1559',
|
|
11
|
+
* intercept: { eth_maxPriorityFeePerGas: 'fast' },
|
|
12
|
+
* })
|
|
13
|
+
* const client = createPublicClient({ chain: mainnet, transport })
|
|
14
|
+
*
|
|
15
|
+
* // Standard viem APIs now reach the oracle cache instead of upstream:
|
|
16
|
+
* await client.estimateMaxPriorityFeePerGas()
|
|
17
|
+
* await walletClient.sendTransaction({ ... }) // gas auto-fill is corrected
|
|
18
|
+
*
|
|
19
|
+
* Default `intercept` is `{ eth_gasFeeEstimate: true }` only — the
|
|
20
|
+
* additive method that returns the full tier shape. Standard methods
|
|
21
|
+
* (`eth_gasPrice`, `eth_maxPriorityFeePerGas`) pass through to upstream
|
|
22
|
+
* unless the caller opts in AND specifies which tier to map them to.
|
|
23
|
+
* Forcing the tier choice keeps the package out of the silently-pick-a-
|
|
24
|
+
* percentile foot-gun the relay's gas-intercept caught earlier (one
|
|
25
|
+
* customer's eth_gasPrice and another's eth_maxPriorityFeePerGas
|
|
26
|
+
* returning numbers 5x apart because each defaulted to a different
|
|
27
|
+
* percentile).
|
|
28
|
+
*
|
|
29
|
+
* `eth_feeHistory` is intentionally NOT in the intercept options.
|
|
30
|
+
* Synthesizing the historical-percentile array from oracle state is
|
|
31
|
+
* its own design problem; passthrough is the only honest answer in v0.2.
|
|
32
|
+
*/
|
|
33
|
+
import { type Transport } from 'viem';
|
|
34
|
+
import { type CreateGasOracleOptions } from './oracle.js';
|
|
35
|
+
import type { TierName } from './types.js';
|
|
36
|
+
export interface InterceptOptions {
|
|
37
|
+
/**
|
|
38
|
+
* Additive multi-tier read. Returns `{ baseFee, tiers: { slow, standard,
|
|
39
|
+
* fast, instant }, mempoolPendingCount, ... }`. Default `true`.
|
|
40
|
+
*/
|
|
41
|
+
eth_gasFeeEstimate?: boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Map `eth_gasPrice` to a tier's `gasPrice` (legacy field = baseFee + tip).
|
|
44
|
+
* Required tier choice — boolean is intentionally not accepted because
|
|
45
|
+
* a default tier would silently make this method's number depend on the
|
|
46
|
+
* package version.
|
|
47
|
+
*/
|
|
48
|
+
eth_gasPrice?: TierName | false;
|
|
49
|
+
/**
|
|
50
|
+
* Map `eth_maxPriorityFeePerGas` to a tier's `maxPriorityFeePerGas`.
|
|
51
|
+
* Required tier choice. Same reasoning as `eth_gasPrice`.
|
|
52
|
+
*/
|
|
53
|
+
eth_maxPriorityFeePerGas?: TierName | false;
|
|
54
|
+
}
|
|
55
|
+
export interface WithGasOracleOptions extends Omit<CreateGasOracleOptions, 'client'> {
|
|
56
|
+
/**
|
|
57
|
+
* Per-method intercept config. Methods not listed pass through to the
|
|
58
|
+
* inner transport. See `InterceptOptions` field docs for defaults and
|
|
59
|
+
* the rationale for tier-required opt-in on standard methods.
|
|
60
|
+
*/
|
|
61
|
+
intercept?: InterceptOptions;
|
|
62
|
+
/**
|
|
63
|
+
* When the oracle starts polling. `'eager'` (default) starts on
|
|
64
|
+
* construction; `'lazy'` waits for the first intercepted RPC. Lazy
|
|
65
|
+
* mode trades a small first-read latency for not running background
|
|
66
|
+
* RPCs when the wrapped client is constructed but never used.
|
|
67
|
+
*/
|
|
68
|
+
lifecycle?: 'eager' | 'lazy';
|
|
69
|
+
}
|
|
70
|
+
export type GasOracleTransport = Transport & {
|
|
71
|
+
stopGasOracle: () => void;
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Wrap an existing viem Transport with gas-oracle interception.
|
|
75
|
+
*
|
|
76
|
+
* The returned value is a Transport (drops into `createPublicClient({
|
|
77
|
+
* transport })` directly) plus a `stopGasOracle()` handle for shutdown
|
|
78
|
+
* hooks (HMR, test teardown). The oracle is constructed once per
|
|
79
|
+
* `withGasOracle` call — a single Transport instance is shared with
|
|
80
|
+
* the oracle's poll loop, so wrapping `http(url)` once produces one
|
|
81
|
+
* oracle and one stream of upstream RPC, regardless of how many
|
|
82
|
+
* clients consume the transport downstream.
|
|
83
|
+
*/
|
|
84
|
+
export declare const withGasOracle: (innerTransport: Transport, options: WithGasOracleOptions) => GasOracleTransport;
|
|
85
|
+
//# sourceMappingURL=viem-transport.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"viem-transport.d.ts","sourceRoot":"","sources":["../src/viem-transport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,OAAO,EAAU,KAAK,SAAS,EAAE,MAAM,MAAM,CAAA;AAE7C,OAAO,EAAmB,KAAK,sBAAsB,EAAkB,MAAM,aAAa,CAAA;AAC1F,OAAO,KAAK,EAAkB,QAAQ,EAAsB,MAAM,YAAY,CAAA;AAE9E,MAAM,WAAW,gBAAgB;IAC/B;;;OAGG;IACH,kBAAkB,CAAC,EAAE,OAAO,CAAA;IAC5B;;;;;OAKG;IACH,YAAY,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAA;IAC/B;;;OAGG;IACH,wBAAwB,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAA;CAC5C;AAED,MAAM,WAAW,oBAAqB,SAAQ,IAAI,CAAC,sBAAsB,EAAE,QAAQ,CAAC;IAClF;;;;OAIG;IACH,SAAS,CAAC,EAAE,gBAAgB,CAAA;IAC5B;;;;;OAKG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,MAAM,CAAA;CAC7B;AAED,MAAM,MAAM,kBAAkB,GAAG,SAAS,GAAG;IAAE,aAAa,EAAE,MAAM,IAAI,CAAA;CAAE,CAAA;AA8G1E;;;;;;;;;;GAUG;AACH,eAAO,MAAM,aAAa,GACxB,gBAAgB,SAAS,EACzB,SAAS,oBAAoB,KAC5B,kBAyBF,CAAA"}
|