@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.
Files changed (44) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +270 -0
  3. package/dist/block-position.d.ts +97 -0
  4. package/dist/block-position.d.ts.map +1 -0
  5. package/dist/block-position.js +131 -0
  6. package/dist/block-position.js.map +1 -0
  7. package/dist/index.d.ts +24 -0
  8. package/dist/index.d.ts.map +1 -0
  9. package/dist/index.js +23 -0
  10. package/dist/index.js.map +1 -0
  11. package/dist/math.d.ts +148 -0
  12. package/dist/math.d.ts.map +1 -0
  13. package/dist/math.js +343 -0
  14. package/dist/math.js.map +1 -0
  15. package/dist/mempool.d.ts +89 -0
  16. package/dist/mempool.d.ts.map +1 -0
  17. package/dist/mempool.js +108 -0
  18. package/dist/mempool.js.map +1 -0
  19. package/dist/oracle.d.ts +139 -0
  20. package/dist/oracle.d.ts.map +1 -0
  21. package/dist/oracle.js +208 -0
  22. package/dist/oracle.js.map +1 -0
  23. package/dist/samples.d.ts +36 -0
  24. package/dist/samples.d.ts.map +1 -0
  25. package/dist/samples.js +107 -0
  26. package/dist/samples.js.map +1 -0
  27. package/dist/transport.d.ts +75 -0
  28. package/dist/transport.d.ts.map +1 -0
  29. package/dist/transport.js +72 -0
  30. package/dist/transport.js.map +1 -0
  31. package/dist/types.d.ts +168 -0
  32. package/dist/types.d.ts.map +1 -0
  33. package/dist/types.js +11 -0
  34. package/dist/types.js.map +1 -0
  35. package/dist/viem-actions.d.ts +77 -0
  36. package/dist/viem-actions.d.ts.map +1 -0
  37. package/dist/viem-actions.js +118 -0
  38. package/dist/viem-actions.js.map +1 -0
  39. package/dist/viem-transport.d.ts +85 -0
  40. package/dist/viem-transport.d.ts.map +1 -0
  41. package/dist/viem-transport.js +165 -0
  42. package/dist/viem-transport.js.map +1 -0
  43. package/package.json +81 -0
  44. 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"}
@@ -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"}