@rhinestone/service-sdk 0.2.15 → 0.2.16
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/dist/chains.d.ts +48 -0
- package/dist/chains.d.ts.map +1 -1
- package/dist/chains.js +25 -5
- package/package.json +1 -1
package/dist/chains.d.ts
CHANGED
|
@@ -12,6 +12,54 @@ export type EstimateGasFeesOptions = {
|
|
|
12
12
|
* Omit this option to retain the chain-aware EIP-1559 estimation strategy.
|
|
13
13
|
*/
|
|
14
14
|
mode?: 'legacy';
|
|
15
|
+
/**
|
|
16
|
+
* Samples the per-block tip at this percentile instead of the chain's
|
|
17
|
+
* configured `tipPercentile`/`congestedTipPercentile`, for a caller that must
|
|
18
|
+
* deliberately price against something other than our own calibration.
|
|
19
|
+
*
|
|
20
|
+
* The one caller today is the Safe GTF gas-price feed (RHI-4992). Safe
|
|
21
|
+
* benchmarks that feed against Gelato's `relayer_getFeeData` and asked us to
|
|
22
|
+
* match its mainnet buffer, having previously used Gelato for the same
|
|
23
|
+
* self-pay flow. Gelato fits to `base + tip`, 20-block window, NO multiplier —
|
|
24
|
+
* the same shape as our windowed path, so tracking them is a percentile and
|
|
25
|
+
* nothing else. Which percentile, measured on 314 paired live samples over 95
|
|
26
|
+
* minutes (2026-08-10, mainnet):
|
|
27
|
+
*
|
|
28
|
+
* pctl model/gelato p10 median p90 mean|err| gwei
|
|
29
|
+
* p50 0.479 0.669 0.889 0.15124
|
|
30
|
+
* p60 0.624 0.879 1.085 0.10221
|
|
31
|
+
* p65 0.725 1.045 1.264 0.08906 <- parity
|
|
32
|
+
* p70 0.985 1.411 1.793 0.16159
|
|
33
|
+
*
|
|
34
|
+
* MEASURE IT OVER HOURS, NOT MINUTES. Two short fits of this same quantity, on
|
|
35
|
+
* the same day, gave p60 at 0.982 (25 samples / 100s) and 0.785 (40 samples /
|
|
36
|
+
* 4 min) — and the 314-sample answer is 0.879. Even the CUMULATIVE median
|
|
37
|
+
* inside the long run wandered 1.006 -> 0.844 -> 0.929 -> 0.881 as it filled
|
|
38
|
+
* up. A 5-percentile step moves the median 19-35%, so a short window can
|
|
39
|
+
* "prove" almost any percentile. An older fit also needed a x1.17 multiplier;
|
|
40
|
+
* that is gone. Re-fit against live data before trusting any number here.
|
|
41
|
+
*
|
|
42
|
+
* DELIBERATELY NOT the chain default. Our own mainnet calibration is p25 plus
|
|
43
|
+
* a realized-clearing floor. p65 sits on the steep face of mainnet's tip cliff
|
|
44
|
+
* and prices at ~1.5x what a transaction actually costs (Gelato itself: 1.494x
|
|
45
|
+
* median, p10 1.125 / p90 2.089 — they swing because they stand on the same
|
|
46
|
+
* cliff). This override exists so ONE surface can opt into a competitor's
|
|
47
|
+
* pricing without moving intent pricing, which shares this function.
|
|
48
|
+
*
|
|
49
|
+
* The cliff MOVES, which is why it is a cliff and not a constant: a 120-block
|
|
50
|
+
* replay read a realized p50 tip of 0.0094 gwei (39,312 txs) in the morning
|
|
51
|
+
* and 0.148 gwei (34,314 txs) a few hours later — 16x — with p75 going
|
|
52
|
+
* 0.25 -> 1.00 gwei over the same span.
|
|
53
|
+
*
|
|
54
|
+
* Applies to the windowed feeHistory path only (`HISTORICAL_FEE_CHAINS`).
|
|
55
|
+
* Ignored on the viem path, where the tip does not come from a percentile we
|
|
56
|
+
* choose. Mainnet — the only chain that needs it — is on the windowed path.
|
|
57
|
+
*
|
|
58
|
+
* Requests a single reward column and pins the read to it, so congestion
|
|
59
|
+
* escalation cannot silently move the sampled percentile somewhere else — the
|
|
60
|
+
* caller asked to track one number.
|
|
61
|
+
*/
|
|
62
|
+
tipPercentileOverride?: number;
|
|
15
63
|
};
|
|
16
64
|
/**
|
|
17
65
|
* {@link estimateGasFees}' result: viem's EIP-1559 fee values plus the next
|
package/dist/chains.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"chains.d.ts","sourceRoot":"","sources":["../src/chains.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAmB,YAAY,EAAE,MAAM,MAAM,CAAA;AAmB3E,oDAAoD;AAMpD,MAAM,MAAM,mBAAmB,GAAG,IAAI,CACpC,YAAY,EACZ,oBAAoB,GAAG,eAAe,CACvC,GACC,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAA;AAEtC,2CAA2C;AAC3C,MAAM,MAAM,sBAAsB,GAAG;IACnC;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,QAAQ,CAAA;
|
|
1
|
+
{"version":3,"file":"chains.d.ts","sourceRoot":"","sources":["../src/chains.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,gBAAgB,EAAmB,YAAY,EAAE,MAAM,MAAM,CAAA;AAmB3E,oDAAoD;AAMpD,MAAM,MAAM,mBAAmB,GAAG,IAAI,CACpC,YAAY,EACZ,oBAAoB,GAAG,eAAe,CACvC,GACC,OAAO,CAAC,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC,CAAA;AAEtC,2CAA2C;AAC3C,MAAM,MAAM,sBAAsB,GAAG;IACnC;;;;;;;OAOG;IACH,IAAI,CAAC,EAAE,QAAQ,CAAA;IACf;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA8CG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAA;CAC/B,CAAA;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,gBAAgB,GAAG,gBAAgB,GAAG;IAChD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAA;CAC7B,CAAA;AAyWD;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,mBAAmB,EAC3B,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,sBAAsB,GAC/B,OAAO,CAAC,gBAAgB,CAAC,CAqE3B"}
|
package/dist/chains.js
CHANGED
|
@@ -335,6 +335,16 @@ async function nextBlockBaseFee(client, chainId) {
|
|
|
335
335
|
* type.
|
|
336
336
|
*/
|
|
337
337
|
async function estimateGasFees(client, chainId, options) {
|
|
338
|
+
// Validated HERE, at the public entry, rather than where it is used. A bad value
|
|
339
|
+
// otherwise surfaces as an opaque provider error from eth_feeHistory — and only on the
|
|
340
|
+
// chains that take the windowed path, so the same argument would throw on mainnet and
|
|
341
|
+
// be silently ignored on Base. This is a locally decidable input; fail locally and say
|
|
342
|
+
// what was wrong. (Copilot, PR #24.)
|
|
343
|
+
const override = options?.tipPercentileOverride;
|
|
344
|
+
if (override !== undefined &&
|
|
345
|
+
(!Number.isFinite(override) || override < 0 || override > 100)) {
|
|
346
|
+
throw new Error(`tipPercentileOverride must be a finite number in [0, 100], got ${override}`);
|
|
347
|
+
}
|
|
338
348
|
if (options?.mode === 'legacy') {
|
|
339
349
|
const legacyClient = client;
|
|
340
350
|
const { gasPrice } = await legacyClient.estimateFeesPerGas({
|
|
@@ -352,7 +362,7 @@ async function estimateGasFees(client, chainId, options) {
|
|
|
352
362
|
}
|
|
353
363
|
if (HISTORICAL_FEE_CHAINS.has(chainId)) {
|
|
354
364
|
const config = { ...DEFAULT_FEE_CONFIG, ...FEE_CONFIG_OVERRIDES[chainId] };
|
|
355
|
-
return estimateFeeHistoryGasFees(client, chainId, config);
|
|
365
|
+
return estimateFeeHistoryGasFees(client, chainId, config, options?.tipPercentileOverride);
|
|
356
366
|
}
|
|
357
367
|
const tipFloorWei = TIP_FLOOR_WEI[chainId];
|
|
358
368
|
if (tipFloorWei !== undefined) {
|
|
@@ -558,17 +568,27 @@ async function viemFallbackCap(client, config, priorityFee) {
|
|
|
558
568
|
}
|
|
559
569
|
return cap > priorityFee ? cap : priorityFee;
|
|
560
570
|
}
|
|
561
|
-
async function estimateFeeHistoryGasFees(client, chainId, config) {
|
|
571
|
+
async function estimateFeeHistoryGasFees(client, chainId, config, tipPercentileOverride) {
|
|
572
|
+
// An override asks for ONE percentile, so ask the RPC for one column. It
|
|
573
|
+
// cannot be requested as [p, p]: geth rejects a reward-percentile list that is
|
|
574
|
+
// not strictly increasing — "invalid reward percentile: #0:60.000000 >=
|
|
575
|
+
// #1:60.000000", verified live 2026-08-10 against publicnode and 1rpc — so the
|
|
576
|
+
// duplicate form is an RPC error, not a pricing choice.
|
|
577
|
+
const overridden = tipPercentileOverride !== undefined;
|
|
562
578
|
const feeHistory = await client.getFeeHistory({
|
|
563
579
|
blockCount: config.historyBlocks,
|
|
564
|
-
rewardPercentiles:
|
|
580
|
+
rewardPercentiles: overridden
|
|
581
|
+
? [tipPercentileOverride]
|
|
582
|
+
: [config.tipPercentile, config.congestedTipPercentile],
|
|
565
583
|
});
|
|
566
584
|
const ratios = feeHistory.gasUsedRatio ?? [];
|
|
567
585
|
const meanRatio = ratios.length > 0
|
|
568
586
|
? ratios.reduce((sum, r) => sum + r, 0) / ratios.length
|
|
569
587
|
: 0;
|
|
570
|
-
// Under load, read the per-block tip from the higher percentile column.
|
|
571
|
-
|
|
588
|
+
// Under load, read the per-block tip from the higher percentile column. With
|
|
589
|
+
// an override there is only column 0, and escalating is not wanted anyway —
|
|
590
|
+
// the caller asked to track one percentile, so congestion must not move it.
|
|
591
|
+
const column = !overridden && meanRatio > config.congestionRatio ? 1 : 0;
|
|
572
592
|
const perBlockTips = (feeHistory.reward ?? []).map((r) => r?.[column] ?? 0n);
|
|
573
593
|
const windowedTip = percentileBigInt(perBlockTips, config.windowTipPercentile);
|
|
574
594
|
const priorityFee = windowedTip > config.tipFloorWei ? windowedTip : config.tipFloorWei;
|