@aztec/ethereum 2.1.9 → 2.1.11
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/dest/deploy_l1_contracts.d.ts.map +1 -1
- package/dest/deploy_l1_contracts.js +9 -8
- package/dest/l1_tx_utils/config.d.ts +2 -2
- package/dest/l1_tx_utils/config.d.ts.map +1 -1
- package/dest/l1_tx_utils/config.js +17 -3
- package/dest/l1_tx_utils/constants.d.ts +6 -0
- package/dest/l1_tx_utils/constants.d.ts.map +1 -1
- package/dest/l1_tx_utils/constants.js +25 -0
- package/dest/l1_tx_utils/fee-strategies/index.d.ts +10 -0
- package/dest/l1_tx_utils/fee-strategies/index.d.ts.map +1 -0
- package/dest/l1_tx_utils/fee-strategies/index.js +12 -0
- package/dest/l1_tx_utils/fee-strategies/p75_competitive.d.ts +8 -0
- package/dest/l1_tx_utils/fee-strategies/p75_competitive.d.ts.map +1 -0
- package/dest/l1_tx_utils/fee-strategies/p75_competitive.js +129 -0
- package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.d.ts +23 -0
- package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.d.ts.map +1 -0
- package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.js +191 -0
- package/dest/l1_tx_utils/fee-strategies/types.d.ts +51 -0
- package/dest/l1_tx_utils/fee-strategies/types.d.ts.map +1 -0
- package/dest/l1_tx_utils/fee-strategies/types.js +3 -0
- package/dest/l1_tx_utils/index.d.ts +2 -0
- package/dest/l1_tx_utils/index.d.ts.map +1 -1
- package/dest/l1_tx_utils/index.js +2 -0
- package/dest/l1_tx_utils/l1_fee_analyzer.d.ts +235 -0
- package/dest/l1_tx_utils/l1_fee_analyzer.d.ts.map +1 -0
- package/dest/l1_tx_utils/l1_fee_analyzer.js +506 -0
- package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +0 -13
- package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -1
- package/dest/l1_tx_utils/readonly_l1_tx_utils.js +48 -160
- package/dest/utils.d.ts +13 -1
- package/dest/utils.d.ts.map +1 -1
- package/dest/utils.js +18 -0
- package/package.json +6 -5
- package/src/deploy_l1_contracts.ts +7 -6
- package/src/l1_tx_utils/config.ts +24 -6
- package/src/l1_tx_utils/constants.ts +11 -0
- package/src/l1_tx_utils/fee-strategies/index.ts +22 -0
- package/src/l1_tx_utils/fee-strategies/p75_competitive.ts +163 -0
- package/src/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.ts +245 -0
- package/src/l1_tx_utils/fee-strategies/types.ts +56 -0
- package/src/l1_tx_utils/index.ts +2 -0
- package/src/l1_tx_utils/l1_fee_analyzer.ts +802 -0
- package/src/l1_tx_utils/readonly_l1_tx_utils.ts +61 -206
- package/src/utils.ts +29 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getKeys,
|
|
1
|
+
import { getKeys, merge, pick, times } from '@aztec/foundation/collection';
|
|
2
2
|
import { createLogger } from '@aztec/foundation/log';
|
|
3
3
|
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
4
4
|
import { RollupAbi } from '@aztec/l1-artifacts/RollupAbi';
|
|
@@ -6,8 +6,10 @@ import pickBy from 'lodash.pickby';
|
|
|
6
6
|
import { MethodNotFoundRpcError, MethodNotSupportedRpcError, decodeErrorResult, formatGwei, getContractError, hexToBytes } from 'viem';
|
|
7
7
|
import { defaultL1TxUtilsConfig, l1TxUtilsConfigMappings } from './config.js';
|
|
8
8
|
import { BLOCK_TIME_MS, LARGE_GAS_LIMIT, MIN_BLOB_REPLACEMENT_BUMP_PERCENTAGE, MIN_REPLACEMENT_BUMP_PERCENTAGE, WEI_CONST } from './constants.js';
|
|
9
|
+
import { P75AllTxsPriorityFeeStrategy } from './fee-strategies/index.js';
|
|
9
10
|
import { getCalldataGasUsage, tryGetCustomErrorNameContractFunction } from './utils.js';
|
|
10
|
-
|
|
11
|
+
// Change this to the current strategy we want to use
|
|
12
|
+
const CurrentStrategy = P75AllTxsPriorityFeeStrategy;
|
|
11
13
|
export class ReadOnlyL1TxUtils {
|
|
12
14
|
client;
|
|
13
15
|
logger;
|
|
@@ -36,141 +38,37 @@ export class ReadOnlyL1TxUtils {
|
|
|
36
38
|
return this.client.getBlockNumber();
|
|
37
39
|
}
|
|
38
40
|
/**
|
|
39
|
-
* Analyzes pending transactions and recent fee history to determine a competitive priority fee.
|
|
40
|
-
* Falls back to network estimate if data is unavailable or fails.
|
|
41
|
-
* @param networkEstimateResult - Result from estimateMaxPriorityFeePerGas RPC call
|
|
42
|
-
* @param pendingBlockResult - Result from getBlock with pending tag RPC call
|
|
43
|
-
* @param feeHistoryResult - Result from getFeeHistory RPC call
|
|
44
|
-
* @returns A competitive priority fee based on pending txs and recent block history
|
|
45
|
-
*/ getCompetitivePriorityFee(networkEstimateResult, pendingBlockResult, feeHistoryResult) {
|
|
46
|
-
const networkEstimate = networkEstimateResult.status === 'fulfilled' && typeof networkEstimateResult.value === 'bigint' ? networkEstimateResult.value : 0n;
|
|
47
|
-
let competitiveFee = networkEstimate;
|
|
48
|
-
if (pendingBlockResult.status === 'fulfilled' && pendingBlockResult.value !== null && pendingBlockResult.value.transactions && pendingBlockResult.value.transactions.length > 0) {
|
|
49
|
-
const pendingBlock = pendingBlockResult.value;
|
|
50
|
-
// Extract priority fees from pending transactions
|
|
51
|
-
const pendingFees = pendingBlock.transactions.map((tx)=>{
|
|
52
|
-
// Transaction can be just a hash string, so we need to check if it's an object
|
|
53
|
-
if (typeof tx === 'string') {
|
|
54
|
-
return 0n;
|
|
55
|
-
}
|
|
56
|
-
const fee = tx.maxPriorityFeePerGas || 0n;
|
|
57
|
-
// Debug: Log suspicious fees
|
|
58
|
-
if (fee > 100n * WEI_CONST) {
|
|
59
|
-
this.logger?.warn('Suspicious high priority fee in pending tx', {
|
|
60
|
-
txHash: tx.hash,
|
|
61
|
-
maxPriorityFeePerGas: formatGwei(fee),
|
|
62
|
-
maxFeePerGas: formatGwei(tx.maxFeePerGas || 0n),
|
|
63
|
-
maxFeePerBlobGas: tx.maxFeePerBlobGas ? formatGwei(tx.maxFeePerBlobGas) : 'N/A'
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
return fee;
|
|
67
|
-
}).filter((fee)=>fee > 0n);
|
|
68
|
-
if (pendingFees.length > 0) {
|
|
69
|
-
// Use 75th percentile of pending fees to be competitive
|
|
70
|
-
const sortedPendingFees = [
|
|
71
|
-
...pendingFees
|
|
72
|
-
].sort((a, b)=>a < b ? -1 : a > b ? 1 : 0);
|
|
73
|
-
const percentile75Index = Math.floor((sortedPendingFees.length - 1) * 0.75);
|
|
74
|
-
const pendingCompetitiveFee = sortedPendingFees[percentile75Index];
|
|
75
|
-
if (pendingCompetitiveFee > competitiveFee) {
|
|
76
|
-
competitiveFee = pendingCompetitiveFee;
|
|
77
|
-
}
|
|
78
|
-
this.logger?.debug('Analyzed pending transactions for competitive pricing', {
|
|
79
|
-
pendingTxCount: pendingFees.length,
|
|
80
|
-
pendingP75: formatGwei(pendingCompetitiveFee)
|
|
81
|
-
});
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
if (feeHistoryResult.status === 'fulfilled' && feeHistoryResult.value !== null && feeHistoryResult.value.reward && feeHistoryResult.value.reward.length > 0) {
|
|
85
|
-
const feeHistory = feeHistoryResult.value;
|
|
86
|
-
// Extract 75th percentile fees from each block
|
|
87
|
-
const percentile75Fees = feeHistory.reward.map((rewards)=>rewards[0] || 0n).filter((fee)=>fee > 0n);
|
|
88
|
-
if (percentile75Fees.length > 0) {
|
|
89
|
-
// Calculate median of the 75th percentile fees across blocks
|
|
90
|
-
const medianHistoricalFee = median(percentile75Fees) ?? 0n;
|
|
91
|
-
// Debug: Log suspicious fees from history
|
|
92
|
-
if (medianHistoricalFee > 100n * WEI_CONST) {
|
|
93
|
-
this.logger?.warn('Suspicious high fee in history', {
|
|
94
|
-
historicalMedian: formatGwei(medianHistoricalFee),
|
|
95
|
-
allP75Fees: percentile75Fees.map((f)=>formatGwei(f))
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
if (medianHistoricalFee > competitiveFee) {
|
|
99
|
-
competitiveFee = medianHistoricalFee;
|
|
100
|
-
}
|
|
101
|
-
this.logger?.debug('Analyzed fee history for competitive pricing', {
|
|
102
|
-
historicalMedian: formatGwei(medianHistoricalFee)
|
|
103
|
-
});
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
// Sanity check: cap competitive fee at 100x network estimate to avoid using unrealistic fees
|
|
107
|
-
// (e.g., Anvil returns inflated historical fees that don't reflect actual network conditions)
|
|
108
|
-
const maxReasonableFee = networkEstimate * 100n;
|
|
109
|
-
if (competitiveFee > maxReasonableFee) {
|
|
110
|
-
this.logger?.warn('Competitive fee exceeds sanity cap, using capped value', {
|
|
111
|
-
competitiveFee: formatGwei(competitiveFee),
|
|
112
|
-
networkEstimate: formatGwei(networkEstimate),
|
|
113
|
-
cappedTo: formatGwei(maxReasonableFee)
|
|
114
|
-
});
|
|
115
|
-
competitiveFee = maxReasonableFee;
|
|
116
|
-
}
|
|
117
|
-
// Log final decision
|
|
118
|
-
if (competitiveFee > networkEstimate) {
|
|
119
|
-
this.logger?.debug('Using competitive fee from market analysis', {
|
|
120
|
-
networkEstimate: formatGwei(networkEstimate),
|
|
121
|
-
competitive: formatGwei(competitiveFee)
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
return competitiveFee;
|
|
125
|
-
}
|
|
126
|
-
/**
|
|
127
41
|
* Gets the current gas price with bounds checking
|
|
128
42
|
*/ async getGasPrice(gasConfigOverrides, isBlobTx = false, attempt = 0, previousGasPrice) {
|
|
129
43
|
const gasConfig = merge(this.config, gasConfigOverrides);
|
|
130
|
-
//
|
|
131
|
-
const
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
rewardPercentiles: [
|
|
142
|
-
75
|
|
143
|
-
]
|
|
144
|
-
}), 'Getting fee history');
|
|
145
|
-
const blobBaseFeePromise = isBlobTx ? this.tryTwice(()=>this.client.getBlobBaseFee(), 'Getting blob base fee') : null;
|
|
146
|
-
const [latestBlockResult, networkEstimateResult, pendingBlockResult, feeHistoryResult, blobBaseFeeResult] = await Promise.allSettled([
|
|
147
|
-
latestBlockPromise,
|
|
148
|
-
networkEstimatePromise ?? Promise.resolve(0n),
|
|
149
|
-
pendingBlockPromise ?? Promise.resolve(null),
|
|
150
|
-
feeHistoryPromise ?? Promise.resolve(null),
|
|
151
|
-
blobBaseFeePromise ?? Promise.resolve(0n)
|
|
152
|
-
]);
|
|
153
|
-
// Extract results
|
|
154
|
-
const baseFee = latestBlockResult.status === 'fulfilled' && typeof latestBlockResult.value === 'object' && latestBlockResult.value.baseFeePerGas ? latestBlockResult.value.baseFeePerGas : 0n;
|
|
155
|
-
// Get blob base fee if available
|
|
156
|
-
let blobBaseFee = 0n;
|
|
157
|
-
if (isBlobTx && blobBaseFeeResult.status === 'fulfilled' && typeof blobBaseFeeResult.value === 'bigint') {
|
|
158
|
-
blobBaseFee = blobBaseFeeResult.value;
|
|
159
|
-
} else if (isBlobTx) {
|
|
44
|
+
// Execute strategy - it handles all RPC calls internally and returns everything we need
|
|
45
|
+
const strategyResult = await retry(()=>CurrentStrategy.execute(this.client, {
|
|
46
|
+
gasConfig,
|
|
47
|
+
isBlobTx,
|
|
48
|
+
logger: this.logger
|
|
49
|
+
}), 'Executing priority fee strategy', makeBackoff(times(2, ()=>0)), this.logger, true);
|
|
50
|
+
const { latestBlock, blobBaseFee, priorityFee: strategyPriorityFee } = strategyResult;
|
|
51
|
+
// Extract base fee from latest block
|
|
52
|
+
const baseFee = latestBlock.baseFeePerGas ?? 0n;
|
|
53
|
+
// Handle blob base fee
|
|
54
|
+
if (isBlobTx && blobBaseFee === undefined) {
|
|
160
55
|
this.logger?.warn('Failed to get L1 blob base fee', attempt);
|
|
161
56
|
}
|
|
162
|
-
let priorityFee;
|
|
163
|
-
if
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
57
|
+
let priorityFee = strategyPriorityFee;
|
|
58
|
+
// Apply minimum priority fee floor if configured
|
|
59
|
+
if (gasConfig.minimumPriorityFeePerGas) {
|
|
60
|
+
const minimumPriorityFee = BigInt(Math.trunc(gasConfig.minimumPriorityFeePerGas * Number(WEI_CONST)));
|
|
61
|
+
if (priorityFee < minimumPriorityFee) {
|
|
62
|
+
this.logger?.debug('Applying minimum priority fee floor', {
|
|
63
|
+
calculatedPriorityFee: formatGwei(priorityFee),
|
|
64
|
+
minimumPriorityFeePerGas: gasConfig.minimumPriorityFeePerGas,
|
|
65
|
+
appliedFee: formatGwei(minimumPriorityFee)
|
|
66
|
+
});
|
|
67
|
+
priorityFee = minimumPriorityFee;
|
|
68
|
+
}
|
|
171
69
|
}
|
|
172
70
|
let maxFeePerGas = baseFee;
|
|
173
|
-
let maxFeePerBlobGas = blobBaseFee;
|
|
71
|
+
let maxFeePerBlobGas = blobBaseFee ?? 0n;
|
|
174
72
|
// Bump base fee so it's valid for next blocks if it stalls
|
|
175
73
|
const numBlocks = Math.ceil(gasConfig.stallTimeMs / BLOCK_TIME_MS);
|
|
176
74
|
for(let i = 0; i < numBlocks; i++){
|
|
@@ -188,17 +86,14 @@ export class ReadOnlyL1TxUtils {
|
|
|
188
86
|
// multiply by 100 & divide by 100 to maintain some precision
|
|
189
87
|
const minPriorityFee = previousGasPrice.maxPriorityFeePerGas * (100_00n + BigInt(bumpPercentage * 1_00)) / 100_00n;
|
|
190
88
|
const minMaxFee = previousGasPrice.maxFeePerGas * (100_00n + BigInt(bumpPercentage * 1_00)) / 100_00n;
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
bumpPercentage: configBump
|
|
200
|
-
});
|
|
201
|
-
}
|
|
89
|
+
// Apply bump percentage to competitive fee
|
|
90
|
+
const competitivePriorityFee = priorityFee * (100_00n + BigInt(configBump * 1_00)) / 100_00n;
|
|
91
|
+
this.logger?.debug(`Speed-up attempt ${attempt}: using competitive fee strategy`, {
|
|
92
|
+
networkEstimate: formatGwei(priorityFee),
|
|
93
|
+
competitiveFee: formatGwei(competitivePriorityFee),
|
|
94
|
+
minRequired: formatGwei(minPriorityFee),
|
|
95
|
+
bumpPercentage: configBump
|
|
96
|
+
});
|
|
202
97
|
// Use maximum between competitive fee and minimum required bump
|
|
203
98
|
const finalPriorityFee = competitivePriorityFee > minPriorityFee ? competitivePriorityFee : minPriorityFee;
|
|
204
99
|
const feeSource = finalPriorityFee === competitivePriorityFee ? 'competitive' : 'minimum-bump';
|
|
@@ -206,20 +101,16 @@ export class ReadOnlyL1TxUtils {
|
|
|
206
101
|
// Add the final priority fee to maxFeePerGas
|
|
207
102
|
maxFeePerGas += finalPriorityFee;
|
|
208
103
|
maxFeePerGas = maxFeePerGas > minMaxFee ? maxFeePerGas : minMaxFee;
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
});
|
|
213
|
-
}
|
|
104
|
+
this.logger?.debug(`Speed-up fee decision: using ${feeSource} fee`, {
|
|
105
|
+
finalPriorityFee: formatGwei(finalPriorityFee)
|
|
106
|
+
});
|
|
214
107
|
} else {
|
|
215
108
|
// First attempt: apply configured bump percentage to competitive fee
|
|
216
109
|
// multiply by 100 & divide by 100 to maintain some precision
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
});
|
|
222
|
-
}
|
|
110
|
+
priorityFee = priorityFee * (100_00n + BigInt((gasConfig.priorityFeeBumpPercentage || 0) * 1_00)) / 100_00n;
|
|
111
|
+
this.logger?.debug('Initial transaction: using competitive fee from market analysis', {
|
|
112
|
+
networkEstimate: formatGwei(priorityFee)
|
|
113
|
+
});
|
|
223
114
|
maxFeePerGas += priorityFee;
|
|
224
115
|
}
|
|
225
116
|
// maxGwei and maxBlobGwei are hard limits
|
|
@@ -247,7 +138,7 @@ export class ReadOnlyL1TxUtils {
|
|
|
247
138
|
baseFee: formatGwei(baseFee),
|
|
248
139
|
maxFeePerGas: formatGwei(maxFeePerGas),
|
|
249
140
|
maxPriorityFeePerGas: formatGwei(maxPriorityFeePerGas),
|
|
250
|
-
blobBaseFee: formatGwei(blobBaseFee),
|
|
141
|
+
blobBaseFee: formatGwei(blobBaseFee ?? 0n),
|
|
251
142
|
maxFeePerBlobGas: formatGwei(maxFeePerBlobGas)
|
|
252
143
|
});
|
|
253
144
|
return {
|
|
@@ -274,14 +165,16 @@ export class ReadOnlyL1TxUtils {
|
|
|
274
165
|
...request,
|
|
275
166
|
..._blobInputs,
|
|
276
167
|
maxFeePerBlobGas: gasPrice.maxFeePerBlobGas,
|
|
277
|
-
gas: LARGE_GAS_LIMIT
|
|
168
|
+
gas: LARGE_GAS_LIMIT,
|
|
169
|
+
blockTag: 'latest'
|
|
278
170
|
});
|
|
279
171
|
this.logger?.trace(`Estimated gas for blob tx: ${initialEstimate}`);
|
|
280
172
|
} else {
|
|
281
173
|
initialEstimate = await this.client.estimateGas({
|
|
282
174
|
account,
|
|
283
175
|
...request,
|
|
284
|
-
gas: LARGE_GAS_LIMIT
|
|
176
|
+
gas: LARGE_GAS_LIMIT,
|
|
177
|
+
blockTag: 'latest'
|
|
285
178
|
});
|
|
286
179
|
this.logger?.trace(`Estimated gas for non-blob tx: ${initialEstimate}`);
|
|
287
180
|
}
|
|
@@ -422,9 +315,4 @@ export class ReadOnlyL1TxUtils {
|
|
|
422
315
|
});
|
|
423
316
|
return bumpedGasLimit;
|
|
424
317
|
}
|
|
425
|
-
/**
|
|
426
|
-
* Helper function to retry RPC calls twice
|
|
427
|
-
*/ tryTwice(fn, description) {
|
|
428
|
-
return retry(fn, description, makeBackoff(times(2, ()=>0)), this.logger, true);
|
|
429
|
-
}
|
|
430
318
|
}
|
package/dest/utils.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Fr } from '@aztec/foundation/fields';
|
|
2
2
|
import type { Logger } from '@aztec/foundation/log';
|
|
3
|
-
import { type Abi, type ContractEventName, type DecodeEventLogReturnType, type Hex, type Log } from 'viem';
|
|
3
|
+
import { type Abi, type ContractEventName, type DecodeEventLogReturnType, type FormattedTransaction, type Hex, type Log } from 'viem';
|
|
4
4
|
export interface L2Claim {
|
|
5
5
|
claimSecret: Fr;
|
|
6
6
|
claimAmount: Fr;
|
|
@@ -22,4 +22,16 @@ export declare function prettyLogViemErrorMsg(err: any): any;
|
|
|
22
22
|
*/
|
|
23
23
|
export declare function formatViemError(error: any, abi?: Abi): FormattedViemError;
|
|
24
24
|
export declare function tryGetCustomErrorName(err: any): string | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Type guard to check if a transaction is a blob transaction (EIP-4844).
|
|
27
|
+
* Blob transactions have maxFeePerBlobGas and blobVersionedHashes fields.
|
|
28
|
+
*/
|
|
29
|
+
export declare function isBlobTransaction(tx: FormattedTransaction): tx is FormattedTransaction & {
|
|
30
|
+
maxFeePerBlobGas: bigint;
|
|
31
|
+
blobVersionedHashes: readonly Hex[];
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Calculates a percentile from an array of bigints
|
|
35
|
+
*/
|
|
36
|
+
export declare function calculatePercentile(values: bigint[], percentile: number): bigint;
|
|
25
37
|
//# sourceMappingURL=utils.d.ts.map
|
package/dest/utils.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,0BAA0B,CAAC;AACnD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAGpD,OAAO,EACL,KAAK,GAAG,EAER,KAAK,iBAAiB,EAEtB,KAAK,wBAAwB,EAC7B,KAAK,GAAG,EACR,KAAK,GAAG,EAGT,MAAM,MAAM,CAAC;AAEd,MAAM,WAAW,OAAO;IACtB,WAAW,EAAE,EAAE,CAAC;IAChB,WAAW,EAAE,EAAE,CAAC;IAChB,WAAW,EAAE,GAAG,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,YAAY,CAAC,EAAE,GAAG,EAAE,CAAC;gBAET,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,GAAG,EAAE;CAKlD;AAED,wBAAgB,YAAY,CAC1B,KAAK,CAAC,IAAI,SAAS,GAAG,GAAG,SAAS,OAAO,EAAE,EAC3C,UAAU,SAAS,iBAAiB,CAAC,IAAI,CAAC,EAC1C,UAAU,GAAG,wBAAwB,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,EAE/E,IAAI,EAAE,GAAG,EAAE,EACX,OAAO,EAAE,GAAG,EACZ,GAAG,EAAE,IAAI,EACT,SAAS,EAAE,UAAU,EACrB,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,OAAO,EACrC,MAAM,CAAC,EAAE,MAAM,GACd,UAAU,CAMZ;AAED,wBAAgB,eAAe,CAC7B,KAAK,CAAC,IAAI,SAAS,GAAG,GAAG,SAAS,OAAO,EAAE,EAC3C,UAAU,SAAS,iBAAiB,CAAC,IAAI,CAAC,EAC1C,UAAU,GAAG,wBAAwB,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,EAE/E,IAAI,EAAE,GAAG,EAAE,EACX,OAAO,EAAE,GAAG,EACZ,GAAG,EAAE,IAAI,EACT,SAAS,EAAE,UAAU,EACrB,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,OAAO,EACrC,MAAM,CAAC,EAAE,MAAM,GACd,UAAU,GAAG,SAAS,CAgBxB;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,GAAG,OAW7C;AA0BD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,GAAE,GAAe,GAAG,kBAAkB,CAwEpF;AAyBD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,GAAG,sBAa7C"}
|
|
1
|
+
{"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,0BAA0B,CAAC;AACnD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAGpD,OAAO,EACL,KAAK,GAAG,EAER,KAAK,iBAAiB,EAEtB,KAAK,wBAAwB,EAC7B,KAAK,oBAAoB,EACzB,KAAK,GAAG,EACR,KAAK,GAAG,EAGT,MAAM,MAAM,CAAC;AAEd,MAAM,WAAW,OAAO;IACtB,WAAW,EAAE,EAAE,CAAC;IAChB,WAAW,EAAE,EAAE,CAAC;IAChB,WAAW,EAAE,GAAG,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,YAAY,CAAC,EAAE,GAAG,EAAE,CAAC;gBAET,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,GAAG,EAAE;CAKlD;AAED,wBAAgB,YAAY,CAC1B,KAAK,CAAC,IAAI,SAAS,GAAG,GAAG,SAAS,OAAO,EAAE,EAC3C,UAAU,SAAS,iBAAiB,CAAC,IAAI,CAAC,EAC1C,UAAU,GAAG,wBAAwB,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,EAE/E,IAAI,EAAE,GAAG,EAAE,EACX,OAAO,EAAE,GAAG,EACZ,GAAG,EAAE,IAAI,EACT,SAAS,EAAE,UAAU,EACrB,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,OAAO,EACrC,MAAM,CAAC,EAAE,MAAM,GACd,UAAU,CAMZ;AAED,wBAAgB,eAAe,CAC7B,KAAK,CAAC,IAAI,SAAS,GAAG,GAAG,SAAS,OAAO,EAAE,EAC3C,UAAU,SAAS,iBAAiB,CAAC,IAAI,CAAC,EAC1C,UAAU,GAAG,wBAAwB,CAAC,IAAI,EAAE,UAAU,EAAE,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,EAE/E,IAAI,EAAE,GAAG,EAAE,EACX,OAAO,EAAE,GAAG,EACZ,GAAG,EAAE,IAAI,EACT,SAAS,EAAE,UAAU,EACrB,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,UAAU,KAAK,OAAO,EACrC,MAAM,CAAC,EAAE,MAAM,GACd,UAAU,GAAG,SAAS,CAgBxB;AAED,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,GAAG,OAW7C;AA0BD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,GAAG,EAAE,GAAG,GAAE,GAAe,GAAG,kBAAkB,CAwEpF;AAyBD,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,GAAG,sBAa7C;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,EAAE,EAAE,oBAAoB,GAAG,EAAE,IAAI,oBAAoB,GAAG;IACxF,gBAAgB,EAAE,MAAM,CAAC;IACzB,mBAAmB,EAAE,SAAS,GAAG,EAAE,CAAC;CACrC,CAOA;AAED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM,CAOhF"}
|
package/dest/utils.js
CHANGED
|
@@ -162,3 +162,21 @@ export function tryGetCustomErrorName(err) {
|
|
|
162
162
|
return undefined;
|
|
163
163
|
}
|
|
164
164
|
}
|
|
165
|
+
/**
|
|
166
|
+
* Type guard to check if a transaction is a blob transaction (EIP-4844).
|
|
167
|
+
* Blob transactions have maxFeePerBlobGas and blobVersionedHashes fields.
|
|
168
|
+
*/ export function isBlobTransaction(tx) {
|
|
169
|
+
return 'maxFeePerBlobGas' in tx && tx.maxFeePerBlobGas !== undefined && 'blobVersionedHashes' in tx && tx.blobVersionedHashes !== undefined;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Calculates a percentile from an array of bigints
|
|
173
|
+
*/ export function calculatePercentile(values, percentile) {
|
|
174
|
+
if (values.length === 0) {
|
|
175
|
+
return 0n;
|
|
176
|
+
}
|
|
177
|
+
const sorted = [
|
|
178
|
+
...values
|
|
179
|
+
].sort((a, b)=>a < b ? -1 : a > b ? 1 : 0);
|
|
180
|
+
const index = Math.ceil((sorted.length - 1) * (percentile / 100));
|
|
181
|
+
return sorted[index];
|
|
182
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/ethereum",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": "./dest/index.js",
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"./contracts": "./dest/contracts/index.js",
|
|
9
9
|
"./deploy-l1-contracts": "./dest/deploy_l1_contracts.js",
|
|
10
10
|
"./l1-contract-addresses": "./dest/l1_contract_addresses.js",
|
|
11
|
+
"./l1-fee-analysis": "./dest/l1_tx_utils/l1_fee_analyzer.js",
|
|
11
12
|
"./l1-tx-utils-with-blobs": "./dest/l1_tx_utils/index-blobs.js",
|
|
12
13
|
"./utils": "./dest/utils.js"
|
|
13
14
|
},
|
|
@@ -31,10 +32,10 @@
|
|
|
31
32
|
"../package.common.json"
|
|
32
33
|
],
|
|
33
34
|
"dependencies": {
|
|
34
|
-
"@aztec/blob-lib": "2.1.
|
|
35
|
-
"@aztec/constants": "2.1.
|
|
36
|
-
"@aztec/foundation": "2.1.
|
|
37
|
-
"@aztec/l1-artifacts": "2.1.
|
|
35
|
+
"@aztec/blob-lib": "2.1.11",
|
|
36
|
+
"@aztec/constants": "2.1.11",
|
|
37
|
+
"@aztec/foundation": "2.1.11",
|
|
38
|
+
"@aztec/l1-artifacts": "2.1.11",
|
|
38
39
|
"@viem/anvil": "^0.0.10",
|
|
39
40
|
"dotenv": "^16.0.3",
|
|
40
41
|
"lodash.chunk": "^4.2.0",
|
|
@@ -276,7 +276,14 @@ export const deploySharedContracts = async (
|
|
|
276
276
|
feeAssetAddress = deployedFee.address;
|
|
277
277
|
logger.verbose(`Deployed Fee Asset at ${feeAssetAddress}`);
|
|
278
278
|
|
|
279
|
+
const deployedStaking = await deployer.deploy(StakingAssetArtifact, ['Staking', 'STK', l1Client.account.address]);
|
|
280
|
+
stakingAssetAddress = deployedStaking.address;
|
|
281
|
+
logger.verbose(`Deployed Staking Asset at ${stakingAssetAddress}`);
|
|
282
|
+
|
|
283
|
+
await deployer.waitForDeployments();
|
|
284
|
+
|
|
279
285
|
// Mint a tiny bit of tokens to satisfy coin-issuer constraints
|
|
286
|
+
// This must happen AFTER the FeeAsset deployment is mined
|
|
280
287
|
const { txHash } = await deployer.sendTransaction({
|
|
281
288
|
to: feeAssetAddress.toString(),
|
|
282
289
|
data: encodeFunctionData({
|
|
@@ -287,12 +294,6 @@ export const deploySharedContracts = async (
|
|
|
287
294
|
});
|
|
288
295
|
await l1Client.waitForTransactionReceipt({ hash: txHash });
|
|
289
296
|
logger.verbose(`Minted tiny bit of tokens to satisfy coin-issuer constraints in ${txHash}`);
|
|
290
|
-
|
|
291
|
-
const deployedStaking = await deployer.deploy(StakingAssetArtifact, ['Staking', 'STK', l1Client.account.address]);
|
|
292
|
-
stakingAssetAddress = deployedStaking.address;
|
|
293
|
-
logger.verbose(`Deployed Staking Asset at ${stakingAssetAddress}`);
|
|
294
|
-
|
|
295
|
-
await deployer.waitForDeployments();
|
|
296
297
|
}
|
|
297
298
|
|
|
298
299
|
const gseAddress = (
|
|
@@ -29,9 +29,9 @@ export interface L1TxUtilsConfig {
|
|
|
29
29
|
*/
|
|
30
30
|
priorityFeeRetryBumpPercentage?: number;
|
|
31
31
|
/**
|
|
32
|
-
*
|
|
32
|
+
* Minimum priority fee per gas in Gwei. Acts as a floor for the computed priority fee.
|
|
33
33
|
*/
|
|
34
|
-
|
|
34
|
+
minimumPriorityFeePerGas?: number;
|
|
35
35
|
/**
|
|
36
36
|
* Maximum number of speed-up attempts
|
|
37
37
|
*/
|
|
@@ -90,10 +90,21 @@ export const l1TxUtilsConfigMappings: ConfigMappingsType<L1TxUtilsConfig> = {
|
|
|
90
90
|
env: 'L1_PRIORITY_FEE_RETRY_BUMP_PERCENTAGE',
|
|
91
91
|
...numberConfigHelper(50),
|
|
92
92
|
},
|
|
93
|
-
|
|
94
|
-
description:
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
minimumPriorityFeePerGas: {
|
|
94
|
+
description:
|
|
95
|
+
'Minimum priority fee per gas in Gwei. Acts as a floor for the computed priority fee. If network conditions require a higher fee, the higher fee will be used.',
|
|
96
|
+
env: 'L1_MINIMUM_PRIORITY_FEE_PER_GAS_GWEI',
|
|
97
|
+
fallback: ['L1_FIXED_PRIORITY_FEE_PER_GAS', 'L1_FIXED_PRIORITY_FEE_PER_GAS_GWEI'],
|
|
98
|
+
deprecatedFallback: [
|
|
99
|
+
{
|
|
100
|
+
env: 'L1_FIXED_PRIORITY_FEE_PER_GAS',
|
|
101
|
+
message: deprecatedFixedFeeMessage('L1_FIXED_PRIORITY_FEE_PER_GAS'),
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
env: 'L1_FIXED_PRIORITY_FEE_PER_GAS_GWEI',
|
|
105
|
+
message: deprecatedFixedFeeMessage('L1_FIXED_PRIORITY_FEE_PER_GAS_GWEI'),
|
|
106
|
+
},
|
|
107
|
+
],
|
|
97
108
|
...floatConfigHelper(0),
|
|
98
109
|
},
|
|
99
110
|
maxSpeedUpAttempts: {
|
|
@@ -141,3 +152,10 @@ export const defaultL1TxUtilsConfig = getDefaultConfig<L1TxUtilsConfig>(
|
|
|
141
152
|
export function getL1TxUtilsConfigEnvVars(): L1TxUtilsConfig {
|
|
142
153
|
return getConfigFromMappings(l1TxUtilsConfigMappings);
|
|
143
154
|
}
|
|
155
|
+
|
|
156
|
+
function deprecatedFixedFeeMessage(envVar: string): string {
|
|
157
|
+
return (
|
|
158
|
+
`Environment variable ${envVar} is deprecated. It is now used as a MINIMUM priority fee rather than a fixed value. ` +
|
|
159
|
+
'Please use L1_MINIMUM_PRIORITY_FEE_PER_GAS_GWEI instead. If network conditions require a higher fee, the higher fee will be used.'
|
|
160
|
+
);
|
|
161
|
+
}
|
|
@@ -16,3 +16,14 @@ export const MIN_BLOB_REPLACEMENT_BUMP_PERCENTAGE = 100;
|
|
|
16
16
|
|
|
17
17
|
// Avg ethereum block time is ~12s
|
|
18
18
|
export const BLOCK_TIME_MS = 12_000;
|
|
19
|
+
|
|
20
|
+
// Gas per blob (EIP-4844)
|
|
21
|
+
export const GAS_PER_BLOB = 131072n;
|
|
22
|
+
|
|
23
|
+
// Blob capacity schedule based on Ethereum upgrades
|
|
24
|
+
export const BLOB_CAPACITY_SCHEDULE = [
|
|
25
|
+
{ timestamp: 1734357600, target: 14, max: 21 }, // BPO2: Dec 17, 2025
|
|
26
|
+
{ timestamp: 1733752800, target: 10, max: 15 }, // BPO1: Dec 9, 2025
|
|
27
|
+
{ timestamp: 1733234400, target: 6, max: 9 }, // Fusaka: Dec 3, 2025
|
|
28
|
+
{ timestamp: 0, target: 6, max: 9 }, // Pectra/earlier
|
|
29
|
+
];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { P75AllTxsPriorityFeeStrategy } from './p75_competitive.js';
|
|
2
|
+
import { P75BlobTxsOnlyPriorityFeeStrategy } from './p75_competitive_blob_txs_only.js';
|
|
3
|
+
import type { PriorityFeeStrategy } from './types.js';
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
HISTORICAL_BLOCK_COUNT,
|
|
7
|
+
type PriorityFeeStrategy,
|
|
8
|
+
type PriorityFeeStrategyContext,
|
|
9
|
+
type PriorityFeeStrategyResult,
|
|
10
|
+
} from './types.js';
|
|
11
|
+
|
|
12
|
+
export { P75AllTxsPriorityFeeStrategy } from './p75_competitive.js';
|
|
13
|
+
export { P75BlobTxsOnlyPriorityFeeStrategy } from './p75_competitive_blob_txs_only.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Default list of priority fee strategies to analyze.
|
|
17
|
+
* Add more strategies here for comparison.
|
|
18
|
+
*/
|
|
19
|
+
export const DEFAULT_PRIORITY_FEE_STRATEGIES: PriorityFeeStrategy[] = [
|
|
20
|
+
P75AllTxsPriorityFeeStrategy,
|
|
21
|
+
P75BlobTxsOnlyPriorityFeeStrategy,
|
|
22
|
+
];
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { median } from '@aztec/foundation/collection';
|
|
2
|
+
|
|
3
|
+
import { formatGwei } from 'viem';
|
|
4
|
+
|
|
5
|
+
import type { ViemClient } from '../../types.js';
|
|
6
|
+
import { calculatePercentile } from '../../utils.js';
|
|
7
|
+
import { WEI_CONST } from '../constants.js';
|
|
8
|
+
import {
|
|
9
|
+
HISTORICAL_BLOCK_COUNT,
|
|
10
|
+
type PriorityFeeStrategy,
|
|
11
|
+
type PriorityFeeStrategyContext,
|
|
12
|
+
type PriorityFeeStrategyResult,
|
|
13
|
+
} from './types.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Our current competitive priority fee strategy.
|
|
17
|
+
* Analyzes p75 of pending transactions and 5-block fee history to determine a competitive priority fee.
|
|
18
|
+
* Falls back to network estimate if data is unavailable.
|
|
19
|
+
*/
|
|
20
|
+
export const P75AllTxsPriorityFeeStrategy: PriorityFeeStrategy = {
|
|
21
|
+
name: 'Competitive (P75 + History) - CURRENT',
|
|
22
|
+
id: 'p75_pending_txs_and_history_all_txs',
|
|
23
|
+
|
|
24
|
+
async execute(client: ViemClient, context: PriorityFeeStrategyContext): Promise<PriorityFeeStrategyResult> {
|
|
25
|
+
const { isBlobTx, logger } = context;
|
|
26
|
+
|
|
27
|
+
// Fire all RPC calls in parallel
|
|
28
|
+
const [latestBlockResult, blobBaseFeeResult, networkEstimateResult, pendingBlockResult, feeHistoryResult] =
|
|
29
|
+
await Promise.allSettled([
|
|
30
|
+
client.getBlock({ blockTag: 'latest' }),
|
|
31
|
+
isBlobTx ? client.getBlobBaseFee() : Promise.resolve(undefined),
|
|
32
|
+
client.estimateMaxPriorityFeePerGas().catch(() => 0n),
|
|
33
|
+
client.getBlock({ blockTag: 'pending', includeTransactions: true }).catch(() => null),
|
|
34
|
+
client
|
|
35
|
+
.getFeeHistory({
|
|
36
|
+
blockCount: HISTORICAL_BLOCK_COUNT,
|
|
37
|
+
rewardPercentiles: [75],
|
|
38
|
+
blockTag: 'latest',
|
|
39
|
+
})
|
|
40
|
+
.catch(() => null),
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
// Extract latest block
|
|
44
|
+
if (latestBlockResult.status === 'rejected') {
|
|
45
|
+
throw new Error(`Failed to get latest block: ${latestBlockResult.reason}`);
|
|
46
|
+
}
|
|
47
|
+
const latestBlock = latestBlockResult.value;
|
|
48
|
+
|
|
49
|
+
// Extract blob base fee (only for blob txs)
|
|
50
|
+
let blobBaseFee: bigint | undefined;
|
|
51
|
+
if (isBlobTx) {
|
|
52
|
+
if (blobBaseFeeResult.status === 'fulfilled' && typeof blobBaseFeeResult.value === 'bigint') {
|
|
53
|
+
blobBaseFee = blobBaseFeeResult.value;
|
|
54
|
+
} else {
|
|
55
|
+
logger?.warn('Failed to get L1 blob base fee');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Extract network estimate
|
|
60
|
+
const networkEstimate =
|
|
61
|
+
networkEstimateResult.status === 'fulfilled' && typeof networkEstimateResult.value === 'bigint'
|
|
62
|
+
? networkEstimateResult.value
|
|
63
|
+
: 0n;
|
|
64
|
+
|
|
65
|
+
let competitiveFee = networkEstimate;
|
|
66
|
+
const debugInfo: Record<string, string | number> = {
|
|
67
|
+
networkEstimateGwei: formatGwei(networkEstimate),
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
// Extract pending block
|
|
71
|
+
const pendingBlock = pendingBlockResult.status === 'fulfilled' ? pendingBlockResult.value : null;
|
|
72
|
+
|
|
73
|
+
// Analyze pending block transactions
|
|
74
|
+
if (pendingBlock?.transactions && pendingBlock.transactions.length > 0) {
|
|
75
|
+
const pendingFees = pendingBlock.transactions
|
|
76
|
+
.map(tx => {
|
|
77
|
+
if (typeof tx === 'string') {
|
|
78
|
+
return 0n;
|
|
79
|
+
}
|
|
80
|
+
return tx.maxPriorityFeePerGas || 0n;
|
|
81
|
+
})
|
|
82
|
+
.filter((fee: bigint) => fee > 0n);
|
|
83
|
+
|
|
84
|
+
if (pendingFees.length > 0) {
|
|
85
|
+
// Use 75th percentile of pending fees to be competitive
|
|
86
|
+
const pendingCompetitiveFee = calculatePercentile(pendingFees, 75);
|
|
87
|
+
|
|
88
|
+
if (pendingCompetitiveFee > competitiveFee) {
|
|
89
|
+
competitiveFee = pendingCompetitiveFee;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
debugInfo.pendingTxCount = pendingFees.length;
|
|
93
|
+
debugInfo.pendingP75Gwei = formatGwei(pendingCompetitiveFee);
|
|
94
|
+
|
|
95
|
+
logger?.debug('Analyzed pending transactions for competitive pricing', {
|
|
96
|
+
pendingTxCount: pendingFees.length,
|
|
97
|
+
pendingP75: formatGwei(pendingCompetitiveFee),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Extract fee history
|
|
103
|
+
const feeHistory = feeHistoryResult.status === 'fulfilled' ? feeHistoryResult.value : null;
|
|
104
|
+
|
|
105
|
+
// Analyze fee history
|
|
106
|
+
if (feeHistory?.reward && feeHistory.reward.length > 0) {
|
|
107
|
+
// Extract 75th percentile fees from each block
|
|
108
|
+
const percentile75Fees = feeHistory.reward.map(rewards => rewards[0] || 0n).filter(fee => fee > 0n);
|
|
109
|
+
|
|
110
|
+
if (percentile75Fees.length > 0) {
|
|
111
|
+
// Calculate median of the 75th percentile fees across blocks
|
|
112
|
+
const medianHistoricalFee = median(percentile75Fees) ?? 0n;
|
|
113
|
+
|
|
114
|
+
// Debug: Log suspicious fees from history
|
|
115
|
+
if (medianHistoricalFee > 100n * WEI_CONST) {
|
|
116
|
+
logger?.warn('Suspicious high fee in history', {
|
|
117
|
+
historicalMedian: formatGwei(medianHistoricalFee),
|
|
118
|
+
allP75Fees: percentile75Fees.map(f => formatGwei(f)),
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (medianHistoricalFee > competitiveFee) {
|
|
123
|
+
competitiveFee = medianHistoricalFee;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
debugInfo.historicalMedianGwei = formatGwei(medianHistoricalFee);
|
|
127
|
+
|
|
128
|
+
logger?.debug('Analyzed fee history for competitive pricing', {
|
|
129
|
+
historicalMedian: formatGwei(medianHistoricalFee),
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Sanity check: cap competitive fee at 100x network estimate to avoid using unrealistic fees
|
|
135
|
+
const maxReasonableFee = networkEstimate * 100n;
|
|
136
|
+
if (competitiveFee > maxReasonableFee && networkEstimate > 0n) {
|
|
137
|
+
logger?.warn('Competitive fee exceeds sanity cap, using capped value', {
|
|
138
|
+
competitiveFee: formatGwei(competitiveFee),
|
|
139
|
+
networkEstimate: formatGwei(networkEstimate),
|
|
140
|
+
cappedTo: formatGwei(maxReasonableFee),
|
|
141
|
+
});
|
|
142
|
+
competitiveFee = maxReasonableFee;
|
|
143
|
+
debugInfo.cappedToGwei = formatGwei(maxReasonableFee);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Log final decision
|
|
147
|
+
if (competitiveFee > networkEstimate) {
|
|
148
|
+
logger?.debug('Using competitive fee from market analysis', {
|
|
149
|
+
networkEstimate: formatGwei(networkEstimate),
|
|
150
|
+
competitive: formatGwei(competitiveFee),
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
debugInfo.finalFeeGwei = formatGwei(competitiveFee);
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
priorityFee: competitiveFee,
|
|
158
|
+
latestBlock,
|
|
159
|
+
blobBaseFee,
|
|
160
|
+
debugInfo,
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
};
|