@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 { type Logger, createLogger } from '@aztec/foundation/log';
|
|
3
3
|
import { makeBackoff, retry } from '@aztec/foundation/retry';
|
|
4
4
|
import { DateProvider } from '@aztec/foundation/timer';
|
|
@@ -30,10 +30,12 @@ import {
|
|
|
30
30
|
MIN_REPLACEMENT_BUMP_PERCENTAGE,
|
|
31
31
|
WEI_CONST,
|
|
32
32
|
} from './constants.js';
|
|
33
|
+
import { P75AllTxsPriorityFeeStrategy, type PriorityFeeStrategy } from './fee-strategies/index.js';
|
|
33
34
|
import type { GasPrice, L1BlobInputs, L1TxRequest, TransactionStats } from './types.js';
|
|
34
35
|
import { getCalldataGasUsage, tryGetCustomErrorNameContractFunction } from './utils.js';
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
// Change this to the current strategy we want to use
|
|
38
|
+
const CurrentStrategy: PriorityFeeStrategy = P75AllTxsPriorityFeeStrategy;
|
|
37
39
|
|
|
38
40
|
export class ReadOnlyL1TxUtils {
|
|
39
41
|
public config: Required<L1TxUtilsConfig>;
|
|
@@ -65,124 +67,6 @@ export class ReadOnlyL1TxUtils {
|
|
|
65
67
|
return this.client.getBlockNumber();
|
|
66
68
|
}
|
|
67
69
|
|
|
68
|
-
/**
|
|
69
|
-
* Analyzes pending transactions and recent fee history to determine a competitive priority fee.
|
|
70
|
-
* Falls back to network estimate if data is unavailable or fails.
|
|
71
|
-
* @param networkEstimateResult - Result from estimateMaxPriorityFeePerGas RPC call
|
|
72
|
-
* @param pendingBlockResult - Result from getBlock with pending tag RPC call
|
|
73
|
-
* @param feeHistoryResult - Result from getFeeHistory RPC call
|
|
74
|
-
* @returns A competitive priority fee based on pending txs and recent block history
|
|
75
|
-
*/
|
|
76
|
-
protected getCompetitivePriorityFee(
|
|
77
|
-
networkEstimateResult: PromiseSettledResult<bigint | null>,
|
|
78
|
-
pendingBlockResult: PromiseSettledResult<Awaited<ReturnType<ViemClient['getBlock']>> | null>,
|
|
79
|
-
feeHistoryResult: PromiseSettledResult<Awaited<ReturnType<ViemClient['getFeeHistory']>> | null>,
|
|
80
|
-
): bigint {
|
|
81
|
-
const networkEstimate =
|
|
82
|
-
networkEstimateResult.status === 'fulfilled' && typeof networkEstimateResult.value === 'bigint'
|
|
83
|
-
? networkEstimateResult.value
|
|
84
|
-
: 0n;
|
|
85
|
-
let competitiveFee = networkEstimate;
|
|
86
|
-
|
|
87
|
-
if (
|
|
88
|
-
pendingBlockResult.status === 'fulfilled' &&
|
|
89
|
-
pendingBlockResult.value !== null &&
|
|
90
|
-
pendingBlockResult.value.transactions &&
|
|
91
|
-
pendingBlockResult.value.transactions.length > 0
|
|
92
|
-
) {
|
|
93
|
-
const pendingBlock = pendingBlockResult.value;
|
|
94
|
-
// Extract priority fees from pending transactions
|
|
95
|
-
const pendingFees = pendingBlock.transactions
|
|
96
|
-
.map(tx => {
|
|
97
|
-
// Transaction can be just a hash string, so we need to check if it's an object
|
|
98
|
-
if (typeof tx === 'string') {
|
|
99
|
-
return 0n;
|
|
100
|
-
}
|
|
101
|
-
const fee = tx.maxPriorityFeePerGas || 0n;
|
|
102
|
-
// Debug: Log suspicious fees
|
|
103
|
-
if (fee > 100n * WEI_CONST) {
|
|
104
|
-
this.logger?.warn('Suspicious high priority fee in pending tx', {
|
|
105
|
-
txHash: tx.hash,
|
|
106
|
-
maxPriorityFeePerGas: formatGwei(fee),
|
|
107
|
-
maxFeePerGas: formatGwei(tx.maxFeePerGas || 0n),
|
|
108
|
-
maxFeePerBlobGas: tx.maxFeePerBlobGas ? formatGwei(tx.maxFeePerBlobGas) : 'N/A',
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
return fee;
|
|
112
|
-
})
|
|
113
|
-
.filter((fee: bigint) => fee > 0n);
|
|
114
|
-
|
|
115
|
-
if (pendingFees.length > 0) {
|
|
116
|
-
// Use 75th percentile of pending fees to be competitive
|
|
117
|
-
const sortedPendingFees = [...pendingFees].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
118
|
-
const percentile75Index = Math.floor((sortedPendingFees.length - 1) * 0.75);
|
|
119
|
-
const pendingCompetitiveFee = sortedPendingFees[percentile75Index];
|
|
120
|
-
|
|
121
|
-
if (pendingCompetitiveFee > competitiveFee) {
|
|
122
|
-
competitiveFee = pendingCompetitiveFee;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
this.logger?.debug('Analyzed pending transactions for competitive pricing', {
|
|
126
|
-
pendingTxCount: pendingFees.length,
|
|
127
|
-
pendingP75: formatGwei(pendingCompetitiveFee),
|
|
128
|
-
});
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
if (
|
|
132
|
-
feeHistoryResult.status === 'fulfilled' &&
|
|
133
|
-
feeHistoryResult.value !== null &&
|
|
134
|
-
feeHistoryResult.value.reward &&
|
|
135
|
-
feeHistoryResult.value.reward.length > 0
|
|
136
|
-
) {
|
|
137
|
-
const feeHistory = feeHistoryResult.value;
|
|
138
|
-
// Extract 75th percentile fees from each block
|
|
139
|
-
const percentile75Fees = feeHistory.reward!.map(rewards => rewards[0] || 0n).filter(fee => fee > 0n);
|
|
140
|
-
|
|
141
|
-
if (percentile75Fees.length > 0) {
|
|
142
|
-
// Calculate median of the 75th percentile fees across blocks
|
|
143
|
-
const medianHistoricalFee = median(percentile75Fees) ?? 0n;
|
|
144
|
-
|
|
145
|
-
// Debug: Log suspicious fees from history
|
|
146
|
-
if (medianHistoricalFee > 100n * WEI_CONST) {
|
|
147
|
-
this.logger?.warn('Suspicious high fee in history', {
|
|
148
|
-
historicalMedian: formatGwei(medianHistoricalFee),
|
|
149
|
-
allP75Fees: percentile75Fees.map(f => formatGwei(f)),
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if (medianHistoricalFee > competitiveFee) {
|
|
154
|
-
competitiveFee = medianHistoricalFee;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
this.logger?.debug('Analyzed fee history for competitive pricing', {
|
|
158
|
-
historicalMedian: formatGwei(medianHistoricalFee),
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
// Sanity check: cap competitive fee at 100x network estimate to avoid using unrealistic fees
|
|
164
|
-
// (e.g., Anvil returns inflated historical fees that don't reflect actual network conditions)
|
|
165
|
-
const maxReasonableFee = networkEstimate * 100n;
|
|
166
|
-
if (competitiveFee > maxReasonableFee) {
|
|
167
|
-
this.logger?.warn('Competitive fee exceeds sanity cap, using capped value', {
|
|
168
|
-
competitiveFee: formatGwei(competitiveFee),
|
|
169
|
-
networkEstimate: formatGwei(networkEstimate),
|
|
170
|
-
cappedTo: formatGwei(maxReasonableFee),
|
|
171
|
-
});
|
|
172
|
-
competitiveFee = maxReasonableFee;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Log final decision
|
|
176
|
-
if (competitiveFee > networkEstimate) {
|
|
177
|
-
this.logger?.debug('Using competitive fee from market analysis', {
|
|
178
|
-
networkEstimate: formatGwei(networkEstimate),
|
|
179
|
-
competitive: formatGwei(competitiveFee),
|
|
180
|
-
});
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return competitiveFee;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
70
|
/**
|
|
187
71
|
* Gets the current gas price with bounds checking
|
|
188
72
|
*/
|
|
@@ -194,68 +78,47 @@ export class ReadOnlyL1TxUtils {
|
|
|
194
78
|
): Promise<GasPrice> {
|
|
195
79
|
const gasConfig = merge(this.config, gasConfigOverrides);
|
|
196
80
|
|
|
197
|
-
//
|
|
198
|
-
const
|
|
199
|
-
() =>
|
|
200
|
-
|
|
81
|
+
// Execute strategy - it handles all RPC calls internally and returns everything we need
|
|
82
|
+
const strategyResult = await retry(
|
|
83
|
+
() =>
|
|
84
|
+
CurrentStrategy.execute(this.client, {
|
|
85
|
+
gasConfig,
|
|
86
|
+
isBlobTx,
|
|
87
|
+
logger: this.logger,
|
|
88
|
+
}),
|
|
89
|
+
'Executing priority fee strategy',
|
|
90
|
+
makeBackoff(times(2, () => 0)),
|
|
91
|
+
this.logger,
|
|
92
|
+
true,
|
|
201
93
|
);
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
);
|
|
211
|
-
const feeHistoryPromise = gasConfig.fixedPriorityFeePerGas
|
|
212
|
-
? null
|
|
213
|
-
: this.tryTwice(
|
|
214
|
-
() => this.client.getFeeHistory({ blockCount: HISTORICAL_BLOCK_COUNT, rewardPercentiles: [75] }),
|
|
215
|
-
'Getting fee history',
|
|
216
|
-
);
|
|
217
|
-
const blobBaseFeePromise = isBlobTx
|
|
218
|
-
? this.tryTwice(() => this.client.getBlobBaseFee(), 'Getting blob base fee')
|
|
219
|
-
: null;
|
|
220
|
-
|
|
221
|
-
const [latestBlockResult, networkEstimateResult, pendingBlockResult, feeHistoryResult, blobBaseFeeResult] =
|
|
222
|
-
await Promise.allSettled([
|
|
223
|
-
latestBlockPromise,
|
|
224
|
-
networkEstimatePromise ?? Promise.resolve(0n),
|
|
225
|
-
pendingBlockPromise ?? Promise.resolve(null),
|
|
226
|
-
feeHistoryPromise ?? Promise.resolve(null),
|
|
227
|
-
blobBaseFeePromise ?? Promise.resolve(0n),
|
|
228
|
-
]);
|
|
229
|
-
|
|
230
|
-
// Extract results
|
|
231
|
-
const baseFee =
|
|
232
|
-
latestBlockResult.status === 'fulfilled' &&
|
|
233
|
-
typeof latestBlockResult.value === 'object' &&
|
|
234
|
-
latestBlockResult.value.baseFeePerGas
|
|
235
|
-
? latestBlockResult.value.baseFeePerGas
|
|
236
|
-
: 0n;
|
|
237
|
-
|
|
238
|
-
// Get blob base fee if available
|
|
239
|
-
let blobBaseFee = 0n;
|
|
240
|
-
if (isBlobTx && blobBaseFeeResult.status === 'fulfilled' && typeof blobBaseFeeResult.value === 'bigint') {
|
|
241
|
-
blobBaseFee = blobBaseFeeResult.value;
|
|
242
|
-
} else if (isBlobTx) {
|
|
94
|
+
|
|
95
|
+
const { latestBlock, blobBaseFee, priorityFee: strategyPriorityFee } = strategyResult;
|
|
96
|
+
|
|
97
|
+
// Extract base fee from latest block
|
|
98
|
+
const baseFee = latestBlock.baseFeePerGas ?? 0n;
|
|
99
|
+
|
|
100
|
+
// Handle blob base fee
|
|
101
|
+
if (isBlobTx && blobBaseFee === undefined) {
|
|
243
102
|
this.logger?.warn('Failed to get L1 blob base fee', attempt);
|
|
244
103
|
}
|
|
245
104
|
|
|
246
|
-
let priorityFee
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
105
|
+
let priorityFee = strategyPriorityFee;
|
|
106
|
+
|
|
107
|
+
// Apply minimum priority fee floor if configured
|
|
108
|
+
if (gasConfig.minimumPriorityFeePerGas) {
|
|
109
|
+
const minimumPriorityFee = BigInt(Math.trunc(gasConfig.minimumPriorityFeePerGas * Number(WEI_CONST)));
|
|
110
|
+
if (priorityFee < minimumPriorityFee) {
|
|
111
|
+
this.logger?.debug('Applying minimum priority fee floor', {
|
|
112
|
+
calculatedPriorityFee: formatGwei(priorityFee),
|
|
113
|
+
minimumPriorityFeePerGas: gasConfig.minimumPriorityFeePerGas,
|
|
114
|
+
appliedFee: formatGwei(minimumPriorityFee),
|
|
115
|
+
});
|
|
116
|
+
priorityFee = minimumPriorityFee;
|
|
117
|
+
}
|
|
255
118
|
}
|
|
256
119
|
let maxFeePerGas = baseFee;
|
|
257
120
|
|
|
258
|
-
let maxFeePerBlobGas = blobBaseFee;
|
|
121
|
+
let maxFeePerBlobGas = blobBaseFee ?? 0n;
|
|
259
122
|
|
|
260
123
|
// Bump base fee so it's valid for next blocks if it stalls
|
|
261
124
|
const numBlocks = Math.ceil(gasConfig.stallTimeMs! / BLOCK_TIME_MS);
|
|
@@ -280,18 +143,15 @@ export class ReadOnlyL1TxUtils {
|
|
|
280
143
|
(previousGasPrice!.maxPriorityFeePerGas * (100_00n + BigInt(bumpPercentage * 1_00))) / 100_00n;
|
|
281
144
|
const minMaxFee = (previousGasPrice!.maxFeePerGas * (100_00n + BigInt(bumpPercentage * 1_00))) / 100_00n;
|
|
282
145
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
// Apply bump percentage to competitive fee
|
|
286
|
-
competitivePriorityFee = (priorityFee * (100_00n + BigInt(configBump * 1_00))) / 100_00n;
|
|
146
|
+
// Apply bump percentage to competitive fee
|
|
147
|
+
const competitivePriorityFee = (priorityFee * (100_00n + BigInt(configBump * 1_00))) / 100_00n;
|
|
287
148
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
149
|
+
this.logger?.debug(`Speed-up attempt ${attempt}: using competitive fee strategy`, {
|
|
150
|
+
networkEstimate: formatGwei(priorityFee),
|
|
151
|
+
competitiveFee: formatGwei(competitivePriorityFee),
|
|
152
|
+
minRequired: formatGwei(minPriorityFee),
|
|
153
|
+
bumpPercentage: configBump,
|
|
154
|
+
});
|
|
295
155
|
|
|
296
156
|
// Use maximum between competitive fee and minimum required bump
|
|
297
157
|
const finalPriorityFee = competitivePriorityFee > minPriorityFee ? competitivePriorityFee : minPriorityFee;
|
|
@@ -302,20 +162,16 @@ export class ReadOnlyL1TxUtils {
|
|
|
302
162
|
maxFeePerGas += finalPriorityFee;
|
|
303
163
|
maxFeePerGas = maxFeePerGas > minMaxFee ? maxFeePerGas : minMaxFee;
|
|
304
164
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
});
|
|
309
|
-
}
|
|
165
|
+
this.logger?.debug(`Speed-up fee decision: using ${feeSource} fee`, {
|
|
166
|
+
finalPriorityFee: formatGwei(finalPriorityFee),
|
|
167
|
+
});
|
|
310
168
|
} else {
|
|
311
169
|
// First attempt: apply configured bump percentage to competitive fee
|
|
312
170
|
// multiply by 100 & divide by 100 to maintain some precision
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
});
|
|
318
|
-
}
|
|
171
|
+
priorityFee = (priorityFee * (100_00n + BigInt((gasConfig.priorityFeeBumpPercentage || 0) * 1_00))) / 100_00n;
|
|
172
|
+
this.logger?.debug('Initial transaction: using competitive fee from market analysis', {
|
|
173
|
+
networkEstimate: formatGwei(priorityFee),
|
|
174
|
+
});
|
|
319
175
|
maxFeePerGas += priorityFee;
|
|
320
176
|
}
|
|
321
177
|
|
|
@@ -356,7 +212,7 @@ export class ReadOnlyL1TxUtils {
|
|
|
356
212
|
baseFee: formatGwei(baseFee),
|
|
357
213
|
maxFeePerGas: formatGwei(maxFeePerGas),
|
|
358
214
|
maxPriorityFeePerGas: formatGwei(maxPriorityFeePerGas),
|
|
359
|
-
blobBaseFee: formatGwei(blobBaseFee),
|
|
215
|
+
blobBaseFee: formatGwei(blobBaseFee ?? 0n),
|
|
360
216
|
maxFeePerBlobGas: formatGwei(maxFeePerBlobGas),
|
|
361
217
|
},
|
|
362
218
|
);
|
|
@@ -388,11 +244,17 @@ export class ReadOnlyL1TxUtils {
|
|
|
388
244
|
..._blobInputs,
|
|
389
245
|
maxFeePerBlobGas: gasPrice.maxFeePerBlobGas!,
|
|
390
246
|
gas: LARGE_GAS_LIMIT,
|
|
247
|
+
blockTag: 'latest',
|
|
391
248
|
});
|
|
392
249
|
|
|
393
250
|
this.logger?.trace(`Estimated gas for blob tx: ${initialEstimate}`);
|
|
394
251
|
} else {
|
|
395
|
-
initialEstimate = await this.client.estimateGas({
|
|
252
|
+
initialEstimate = await this.client.estimateGas({
|
|
253
|
+
account,
|
|
254
|
+
...request,
|
|
255
|
+
gas: LARGE_GAS_LIMIT,
|
|
256
|
+
blockTag: 'latest',
|
|
257
|
+
});
|
|
396
258
|
this.logger?.trace(`Estimated gas for non-blob tx: ${initialEstimate}`);
|
|
397
259
|
}
|
|
398
260
|
|
|
@@ -548,11 +410,4 @@ export class ReadOnlyL1TxUtils {
|
|
|
548
410
|
});
|
|
549
411
|
return bumpedGasLimit;
|
|
550
412
|
}
|
|
551
|
-
|
|
552
|
-
/**
|
|
553
|
-
* Helper function to retry RPC calls twice
|
|
554
|
-
*/
|
|
555
|
-
private tryTwice<T>(fn: () => Promise<T>, description: string): Promise<T> {
|
|
556
|
-
return retry<T>(fn, description, makeBackoff(times(2, () => 0)), this.logger, true);
|
|
557
|
-
}
|
|
558
413
|
}
|
package/src/utils.ts
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
type ContractEventName,
|
|
9
9
|
ContractFunctionRevertedError,
|
|
10
10
|
type DecodeEventLogReturnType,
|
|
11
|
+
type FormattedTransaction,
|
|
11
12
|
type Hex,
|
|
12
13
|
type Log,
|
|
13
14
|
decodeErrorResult,
|
|
@@ -233,3 +234,31 @@ export function tryGetCustomErrorName(err: any) {
|
|
|
233
234
|
return undefined;
|
|
234
235
|
}
|
|
235
236
|
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Type guard to check if a transaction is a blob transaction (EIP-4844).
|
|
240
|
+
* Blob transactions have maxFeePerBlobGas and blobVersionedHashes fields.
|
|
241
|
+
*/
|
|
242
|
+
export function isBlobTransaction(tx: FormattedTransaction): tx is FormattedTransaction & {
|
|
243
|
+
maxFeePerBlobGas: bigint;
|
|
244
|
+
blobVersionedHashes: readonly Hex[];
|
|
245
|
+
} {
|
|
246
|
+
return (
|
|
247
|
+
'maxFeePerBlobGas' in tx &&
|
|
248
|
+
tx.maxFeePerBlobGas !== undefined &&
|
|
249
|
+
'blobVersionedHashes' in tx &&
|
|
250
|
+
tx.blobVersionedHashes !== undefined
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Calculates a percentile from an array of bigints
|
|
256
|
+
*/
|
|
257
|
+
export function calculatePercentile(values: bigint[], percentile: number): bigint {
|
|
258
|
+
if (values.length === 0) {
|
|
259
|
+
return 0n;
|
|
260
|
+
}
|
|
261
|
+
const sorted = [...values].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
262
|
+
const index = Math.ceil((sorted.length - 1) * (percentile / 100));
|
|
263
|
+
return sorted[index];
|
|
264
|
+
}
|