@aztec/ethereum 3.0.0-nightly.20251216 → 3.0.0-nightly.20251218

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 (45) hide show
  1. package/dest/deploy_aztec_l1_contracts.d.ts +1 -1
  2. package/dest/deploy_aztec_l1_contracts.d.ts.map +1 -1
  3. package/dest/deploy_aztec_l1_contracts.js +10 -8
  4. package/dest/l1_contract_addresses.d.ts +1 -1
  5. package/dest/l1_contract_addresses.d.ts.map +1 -1
  6. package/dest/l1_contract_addresses.js +3 -3
  7. package/dest/l1_tx_utils/constants.d.ts +7 -1
  8. package/dest/l1_tx_utils/constants.d.ts.map +1 -1
  9. package/dest/l1_tx_utils/constants.js +25 -0
  10. package/dest/l1_tx_utils/fee-strategies/index.d.ts +9 -0
  11. package/dest/l1_tx_utils/fee-strategies/index.d.ts.map +1 -0
  12. package/dest/l1_tx_utils/fee-strategies/index.js +11 -0
  13. package/dest/l1_tx_utils/fee-strategies/p75_competitive.d.ts +18 -0
  14. package/dest/l1_tx_utils/fee-strategies/p75_competitive.d.ts.map +1 -0
  15. package/dest/l1_tx_utils/fee-strategies/p75_competitive.js +111 -0
  16. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.d.ts +32 -0
  17. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.d.ts.map +1 -0
  18. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.js +173 -0
  19. package/dest/l1_tx_utils/fee-strategies/types.d.ts +64 -0
  20. package/dest/l1_tx_utils/fee-strategies/types.d.ts.map +1 -0
  21. package/dest/l1_tx_utils/fee-strategies/types.js +24 -0
  22. package/dest/l1_tx_utils/index.d.ts +3 -1
  23. package/dest/l1_tx_utils/index.d.ts.map +1 -1
  24. package/dest/l1_tx_utils/index.js +2 -0
  25. package/dest/l1_tx_utils/l1_fee_analyzer.d.ts +232 -0
  26. package/dest/l1_tx_utils/l1_fee_analyzer.d.ts.map +1 -0
  27. package/dest/l1_tx_utils/l1_fee_analyzer.js +506 -0
  28. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +1 -10
  29. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -1
  30. package/dest/l1_tx_utils/readonly_l1_tx_utils.js +43 -121
  31. package/dest/utils.d.ts +14 -2
  32. package/dest/utils.d.ts.map +1 -1
  33. package/dest/utils.js +18 -0
  34. package/package.json +6 -5
  35. package/src/deploy_aztec_l1_contracts.ts +14 -7
  36. package/src/l1_contract_addresses.ts +22 -20
  37. package/src/l1_tx_utils/constants.ts +11 -0
  38. package/src/l1_tx_utils/fee-strategies/index.ts +22 -0
  39. package/src/l1_tx_utils/fee-strategies/p75_competitive.ts +159 -0
  40. package/src/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.ts +241 -0
  41. package/src/l1_tx_utils/fee-strategies/types.ts +88 -0
  42. package/src/l1_tx_utils/index.ts +2 -0
  43. package/src/l1_tx_utils/l1_fee_analyzer.ts +803 -0
  44. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +47 -158
  45. package/src/utils.ts +29 -0
@@ -1,4 +1,4 @@
1
- import { getKeys, median, merge, pick, times } from '@aztec/foundation/collection';
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
- const HISTORICAL_BLOCK_COUNT = 5;
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
  */
@@ -195,34 +79,31 @@ export class ReadOnlyL1TxUtils {
195
79
  const gasConfig = merge(this.config, gasConfigOverrides);
196
80
 
197
81
  // Make all RPC calls in parallel upfront with retry logic
82
+ // First 2 calls are necessary to complete
198
83
  const latestBlockPromise = this.tryTwice(
199
84
  () => this.client.getBlock({ blockTag: 'latest' }),
200
85
  'Getting latest block',
201
86
  );
202
- const networkEstimatePromise = this.tryTwice(
203
- () => this.client.estimateMaxPriorityFeePerGas(),
204
- 'Estimating max priority fee per gas',
205
- );
206
- const pendingBlockPromise = this.tryTwice(
207
- () => this.client.getBlock({ blockTag: 'pending', includeTransactions: true }),
208
- 'Getting pending block',
209
- );
210
- const feeHistoryPromise = this.tryTwice(
211
- () => this.client.getFeeHistory({ blockCount: HISTORICAL_BLOCK_COUNT, rewardPercentiles: [75] }),
212
- 'Getting fee history',
213
- );
214
- const blobBaseFeePromise = isBlobTx
215
- ? this.tryTwice(() => this.client.getBlobBaseFee(), 'Getting blob base fee')
216
- : null;
217
-
218
- const [latestBlockResult, networkEstimateResult, pendingBlockResult, feeHistoryResult, blobBaseFeeResult] =
219
- await Promise.allSettled([
220
- latestBlockPromise,
221
- networkEstimatePromise,
222
- pendingBlockPromise,
223
- feeHistoryPromise,
224
- blobBaseFeePromise ?? Promise.resolve(0n),
225
- ]);
87
+
88
+ let blobBaseFeePromise = null;
89
+ if (isBlobTx) {
90
+ blobBaseFeePromise = this.tryTwice(() => this.client.getBlobBaseFee(), 'Getting blob base fee');
91
+ }
92
+
93
+ // Get strategy promises for priority fee calculation
94
+ const strategyPromises = CurrentStrategy.getRequiredPromises(this.client, { isBlobTx });
95
+ const strategyPromiseKeys = [];
96
+ const strategyPromisesArr = [];
97
+ for (const [key, promise] of Object.entries(strategyPromises)) {
98
+ strategyPromiseKeys.push(key);
99
+ strategyPromisesArr.push(this.tryTwice(() => promise, `Getting strategy data for ${key}`));
100
+ }
101
+
102
+ const [latestBlockResult, blobBaseFeeResult, ...strategyResults] = await Promise.allSettled([
103
+ latestBlockPromise,
104
+ blobBaseFeePromise ?? Promise.resolve(0n),
105
+ ...strategyPromisesArr,
106
+ ]);
226
107
 
227
108
  // Extract results
228
109
  const baseFee =
@@ -240,23 +121,31 @@ export class ReadOnlyL1TxUtils {
240
121
  this.logger?.warn('Failed to get L1 blob base fee', attempt);
241
122
  }
242
123
 
243
- // Get competitive priority fee
244
- let priorityFee = this.getCompetitivePriorityFee(networkEstimateResult, pendingBlockResult, feeHistoryResult);
124
+ let priorityFee: bigint;
125
+ // Get competitive priority fee using strategy
126
+ // Reconstruct the results object with the same keys as the promises
127
+ const resultsObject: Record<string, PromiseSettledResult<unknown>> = {};
128
+ strategyPromiseKeys.forEach((key, index) => {
129
+ resultsObject[key] = strategyResults[index];
130
+ });
131
+
132
+ const result = CurrentStrategy.calculate(resultsObject as any, {
133
+ gasConfig,
134
+ isBlobTx,
135
+ logger: this.logger,
136
+ });
137
+ priorityFee = result.priorityFee;
245
138
 
246
- // Apply minimum priority fee as a floor if configured
139
+ // Apply minimum priority fee floor if configured
247
140
  if (gasConfig.minimumPriorityFeePerGas) {
248
- const minimumFee = BigInt(Math.trunc(gasConfig.minimumPriorityFeePerGas * Number(WEI_CONST)));
249
- if (minimumFee > priorityFee) {
250
- this.logger?.debug('Using minimum priority fee as floor', {
251
- minimumPriorityFeePerGas: formatGwei(minimumFee),
252
- competitiveFee: formatGwei(priorityFee),
253
- });
254
- priorityFee = minimumFee;
255
- } else {
256
- this.logger?.debug('Competitive fee exceeds minimum, using competitive fee', {
257
- minimumPriorityFeePerGas: formatGwei(minimumFee),
258
- competitiveFee: formatGwei(priorityFee),
141
+ const minimumPriorityFee = BigInt(Math.trunc(gasConfig.minimumPriorityFeePerGas * Number(WEI_CONST)));
142
+ if (priorityFee < minimumPriorityFee) {
143
+ this.logger?.debug('Applying minimum priority fee floor', {
144
+ calculatedPriorityFee: formatGwei(priorityFee),
145
+ minimumPriorityFeePerGas: gasConfig.minimumPriorityFeePerGas,
146
+ appliedFee: formatGwei(minimumPriorityFee),
259
147
  });
148
+ priorityFee = minimumPriorityFee;
260
149
  }
261
150
  }
262
151
  let maxFeePerGas = baseFee;
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
+ }