@aztec/ethereum 2.1.0-rc.9 → 2.1.1-rc.1

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.
@@ -1,4 +1,4 @@
1
- import { getKeys, merge, pick, times } from '@aztec/foundation/collection';
1
+ import { getKeys, median, 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';
@@ -7,6 +7,7 @@ import { MethodNotFoundRpcError, MethodNotSupportedRpcError, decodeErrorResult,
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
9
  import { getCalldataGasUsage, tryGetCustomErrorNameContractFunction } from './utils.js';
10
+ const HISTORICAL_BLOCK_COUNT = 5;
10
11
  export class ReadOnlyL1TxUtils {
11
12
  client;
12
13
  logger;
@@ -35,21 +36,128 @@ export class ReadOnlyL1TxUtils {
35
36
  return this.client.getBlockNumber();
36
37
  }
37
38
  /**
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
+ /**
38
127
  * Gets the current gas price with bounds checking
39
128
  */ async getGasPrice(gasConfigOverrides, isBlobTx = false, attempt = 0, previousGasPrice) {
40
129
  const gasConfig = merge(this.config, gasConfigOverrides);
41
- const block = await this.client.getBlock({
42
- blockTag: 'latest'
43
- });
44
- const baseFee = block.baseFeePerGas ?? 0n;
130
+ // Make all RPC calls in parallel upfront with retry logic
131
+ const latestBlockPromise = this.tryTwice(()=>this.client.getBlock({
132
+ blockTag: 'latest'
133
+ }), 'Getting latest block');
134
+ const networkEstimatePromise = gasConfig.fixedPriorityFeePerGas ? null : this.tryTwice(()=>this.client.estimateMaxPriorityFeePerGas(), 'Estimating max priority fee per gas');
135
+ const pendingBlockPromise = gasConfig.fixedPriorityFeePerGas ? null : this.tryTwice(()=>this.client.getBlock({
136
+ blockTag: 'pending',
137
+ includeTransactions: true
138
+ }), 'Getting pending block');
139
+ const feeHistoryPromise = gasConfig.fixedPriorityFeePerGas ? null : this.tryTwice(()=>this.client.getFeeHistory({
140
+ blockCount: HISTORICAL_BLOCK_COUNT,
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;
45
155
  // Get blob base fee if available
46
156
  let blobBaseFee = 0n;
47
- if (isBlobTx) {
48
- try {
49
- blobBaseFee = await retry(()=>this.client.getBlobBaseFee(), 'Getting L1 blob base fee', makeBackoff(times(2, ()=>1)), this.logger, true);
50
- } catch {
51
- this.logger?.warn('Failed to get L1 blob base fee', attempt);
52
- }
157
+ if (isBlobTx && blobBaseFeeResult.status === 'fulfilled' && typeof blobBaseFeeResult.value === 'bigint') {
158
+ blobBaseFee = blobBaseFeeResult.value;
159
+ } else if (isBlobTx) {
160
+ this.logger?.warn('Failed to get L1 blob base fee', attempt);
53
161
  }
54
162
  let priorityFee;
55
163
  if (gasConfig.fixedPriorityFeePerGas) {
@@ -59,8 +167,8 @@ export class ReadOnlyL1TxUtils {
59
167
  // try to maintain precision up to 1000000 wei
60
168
  priorityFee = BigInt(gasConfig.fixedPriorityFeePerGas * 1_000_000) * (WEI_CONST / 1_000_000n);
61
169
  } else {
62
- // Get initial priority fee from the network
63
- priorityFee = await this.client.estimateMaxPriorityFeePerGas();
170
+ // Get competitive priority fee (includes network estimate + analysis)
171
+ priorityFee = this.getCompetitivePriorityFee(networkEstimateResult, pendingBlockResult, feeHistoryResult);
64
172
  }
65
173
  let maxFeePerGas = baseFee;
66
174
  let maxFeePerBlobGas = blobBaseFee;
@@ -81,26 +189,50 @@ export class ReadOnlyL1TxUtils {
81
189
  // multiply by 100 & divide by 100 to maintain some precision
82
190
  const minPriorityFee = previousGasPrice.maxPriorityFeePerGas * (100_00n + BigInt(bumpPercentage * 1_00)) / 100_00n;
83
191
  const minMaxFee = previousGasPrice.maxFeePerGas * (100_00n + BigInt(bumpPercentage * 1_00)) / 100_00n;
84
- // Add priority fee to maxFeePerGas
85
- maxFeePerGas += priorityFee;
86
- // Use maximum between current network values and minimum required values
87
- priorityFee = priorityFee > minPriorityFee ? priorityFee : minPriorityFee;
192
+ let competitivePriorityFee = priorityFee;
193
+ if (!gasConfig.fixedPriorityFeePerGas) {
194
+ // Apply bump percentage to competitive fee
195
+ competitivePriorityFee = priorityFee * (100_00n + BigInt(configBump * 1_00)) / 100_00n;
196
+ this.logger?.debug(`Speed-up attempt ${attempt}: using competitive fee strategy`, {
197
+ networkEstimate: formatGwei(priorityFee),
198
+ competitiveFee: formatGwei(competitivePriorityFee),
199
+ minRequired: formatGwei(minPriorityFee),
200
+ bumpPercentage: configBump
201
+ });
202
+ }
203
+ // Use maximum between competitive fee and minimum required bump
204
+ const finalPriorityFee = competitivePriorityFee > minPriorityFee ? competitivePriorityFee : minPriorityFee;
205
+ const feeSource = finalPriorityFee === competitivePriorityFee ? 'competitive' : 'minimum-bump';
206
+ priorityFee = finalPriorityFee;
207
+ // Add the final priority fee to maxFeePerGas
208
+ maxFeePerGas += finalPriorityFee;
88
209
  maxFeePerGas = maxFeePerGas > minMaxFee ? maxFeePerGas : minMaxFee;
210
+ if (!gasConfig.fixedPriorityFeePerGas) {
211
+ this.logger?.debug(`Speed-up fee decision: using ${feeSource} fee`, {
212
+ finalPriorityFee: formatGwei(finalPriorityFee)
213
+ });
214
+ }
89
215
  } else {
90
- // first attempt, just bump priority fee, unless it's a fixed config
216
+ // First attempt: apply configured bump percentage to competitive fee
91
217
  // multiply by 100 & divide by 100 to maintain some precision
92
218
  if (!gasConfig.fixedPriorityFeePerGas) {
93
219
  priorityFee = priorityFee * (100_00n + BigInt((gasConfig.priorityFeeBumpPercentage || 0) * 1_00)) / 100_00n;
220
+ this.logger?.debug('Initial transaction: using competitive fee from market analysis', {
221
+ networkEstimate: formatGwei(priorityFee)
222
+ });
94
223
  }
95
224
  maxFeePerGas += priorityFee;
96
225
  }
226
+ // maxGwei and maxBlobGwei are hard limits
227
+ const effectiveMaxGwei = gasConfig.maxGwei * WEI_CONST;
228
+ const effectiveMaxBlobGwei = gasConfig.maxBlobGwei * WEI_CONST;
97
229
  // Ensure we don't exceed maxGwei
98
- const maxGweiInWei = gasConfig.maxGwei * WEI_CONST;
99
- maxFeePerGas = maxFeePerGas > maxGweiInWei ? maxGweiInWei : maxFeePerGas;
230
+ if (effectiveMaxGwei > 0n) {
231
+ maxFeePerGas = maxFeePerGas > effectiveMaxGwei ? effectiveMaxGwei : maxFeePerGas;
232
+ }
100
233
  // Ensure we don't exceed maxBlobGwei
101
- if (maxFeePerBlobGas) {
102
- const maxBlobGweiInWei = gasConfig.maxBlobGwei * WEI_CONST;
103
- maxFeePerBlobGas = maxFeePerBlobGas > maxBlobGweiInWei ? maxBlobGweiInWei : maxFeePerBlobGas;
234
+ if (maxFeePerBlobGas && effectiveMaxBlobGwei > 0n) {
235
+ maxFeePerBlobGas = maxFeePerBlobGas > effectiveMaxBlobGwei ? effectiveMaxBlobGwei : maxFeePerBlobGas;
104
236
  }
105
237
  // Ensure priority fee doesn't exceed max fee
106
238
  const maxPriorityFeePerGas = priorityFee > maxFeePerGas ? maxFeePerGas : priorityFee;
@@ -291,4 +423,9 @@ export class ReadOnlyL1TxUtils {
291
423
  });
292
424
  return bumpedGasLimit;
293
425
  }
426
+ /**
427
+ * Helper function to retry RPC calls twice
428
+ */ tryTwice(fn, description) {
429
+ return retry(fn, description, makeBackoff(times(2, ()=>0)), this.logger, true);
430
+ }
294
431
  }
@@ -2,7 +2,7 @@ import { EthAddress } from '@aztec/foundation/eth-address';
2
2
  /**
3
3
  * The address of the zk passport verifier on sepolia
4
4
  * get address from: ROOT/l1-contracts/lib/circuits/src/solidity/deployments/deployment-11155111.json
5
- */ export const ZK_PASSPORT_VERIFIER_ADDRESS = EthAddress.fromString('0xBec82dec0747C9170D760D5aba9cc44929B17C05');
5
+ */ export const ZK_PASSPORT_VERIFIER_ADDRESS = EthAddress.fromString('0x3101Bad9eA5fACadA5554844a1a88F7Fe48D4DE0');
6
6
  /**
7
7
  * The default domain of the zk passport site
8
8
  */ export const ZK_PASSPORT_DOMAIN = 'testnet.aztec.network';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/ethereum",
3
- "version": "2.1.0-rc.9",
3
+ "version": "2.1.1-rc.1",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dest/index.js",
@@ -31,16 +31,16 @@
31
31
  "../package.common.json"
32
32
  ],
33
33
  "dependencies": {
34
- "@aztec/blob-lib": "2.1.0-rc.9",
35
- "@aztec/constants": "2.1.0-rc.9",
36
- "@aztec/foundation": "2.1.0-rc.9",
37
- "@aztec/l1-artifacts": "2.1.0-rc.9",
34
+ "@aztec/blob-lib": "2.1.1-rc.1",
35
+ "@aztec/constants": "2.1.1-rc.1",
36
+ "@aztec/foundation": "2.1.1-rc.1",
37
+ "@aztec/l1-artifacts": "2.1.1-rc.1",
38
38
  "@viem/anvil": "^0.0.10",
39
39
  "dotenv": "^16.0.3",
40
40
  "lodash.chunk": "^4.2.0",
41
41
  "lodash.pickby": "^4.5.0",
42
42
  "tslib": "^2.4.0",
43
- "viem": "2.23.7",
43
+ "viem": "npm:@spalladino/viem@2.38.2-eip7594.0",
44
44
  "zod": "^3.23.8"
45
45
  },
46
46
  "devDependencies": {
package/src/config.ts CHANGED
@@ -138,7 +138,7 @@ const TestnetGovernanceConfiguration = {
138
138
  gracePeriod: 1n * 24n * 60n * 60n, // 1 day
139
139
  quorum: 2n * 10n ** 17n, // 20%
140
140
  requiredYeaMargin: 1n * 10n ** 17n, // 10%
141
- minimumVotes: 1250n * 200_000n * 10n ** 18n,
141
+ minimumVotes: 100n * 200_000n * 10n ** 18n,
142
142
  };
143
143
 
144
144
  const StagingIgnitionGovernanceConfiguration = {
@@ -156,6 +156,21 @@ const StagingIgnitionGovernanceConfiguration = {
156
156
  minimumVotes: 1250n * 200_000n * 10n ** 18n,
157
157
  };
158
158
 
159
+ const MainnetGovernanceConfiguration = {
160
+ proposeConfig: {
161
+ lockDelay: 90n * 24n * 60n * 60n,
162
+ lockAmount: 258_750_000n * 10n ** 18n,
163
+ },
164
+
165
+ votingDelay: 3n * 24n * 60n * 60n,
166
+ votingDuration: 7n * 24n * 60n * 60n,
167
+ executionDelay: 7n * 24n * 60n * 60n,
168
+ gracePeriod: 7n * 24n * 60n * 60n,
169
+ quorum: 2n * 10n ** 17n, // 20%
170
+ requiredYeaMargin: 33n * 10n ** 16n, // 33%
171
+ minimumVotes: 1000n * 200_000n * 10n ** 18n,
172
+ };
173
+
159
174
  export const getGovernanceConfiguration = (networkName: NetworkNames) => {
160
175
  switch (networkName) {
161
176
  case 'local':
@@ -166,6 +181,8 @@ export const getGovernanceConfiguration = (networkName: NetworkNames) => {
166
181
  return TestnetGovernanceConfiguration;
167
182
  case 'staging-ignition':
168
183
  return StagingIgnitionGovernanceConfiguration;
184
+ case 'mainnet':
185
+ return MainnetGovernanceConfiguration;
169
186
  default:
170
187
  throw new Error(`Unrecognized network name: ${networkName}`);
171
188
  }
@@ -182,6 +199,13 @@ const DefaultRewardConfig = {
182
199
  blockReward: 500n * 10n ** 18n,
183
200
  };
184
201
 
202
+ const MainnetRewardConfig = {
203
+ sequencerBps: 7_000,
204
+ rewardDistributor: EthAddress.ZERO.toString(),
205
+ booster: EthAddress.ZERO.toString(),
206
+ blockReward: 400n * 10n ** 18n,
207
+ };
208
+
185
209
  export const getRewardConfig = (networkName: NetworkNames) => {
186
210
  switch (networkName) {
187
211
  case 'local':
@@ -189,6 +213,8 @@ export const getRewardConfig = (networkName: NetworkNames) => {
189
213
  case 'testnet':
190
214
  case 'staging-ignition':
191
215
  return DefaultRewardConfig;
216
+ case 'mainnet':
217
+ return MainnetRewardConfig;
192
218
  default:
193
219
  throw new Error(`Unrecognized network name: ${networkName}`);
194
220
  }
@@ -198,11 +224,11 @@ export const getRewardBoostConfig = () => {
198
224
  // The reward configuration is specified with a precision of 1e5, and we use the same across
199
225
  // all networks.
200
226
  return {
201
- increment: 125000, // 1.25
202
- maxScore: 15000000, // 150
203
- a: 1000, // 0.01
204
- k: 1000000, // 10
205
- minimum: 100000, // 1
227
+ increment: 125_000, // 1.25
228
+ maxScore: 15_000_000, // 150
229
+ a: 1_000, // 0.01
230
+ k: 1_000_000, // 10
231
+ minimum: 100_000, // 1
206
232
  };
207
233
  };
208
234
 
@@ -224,11 +250,11 @@ const StagingPublicEntryQueueConfig = {
224
250
  };
225
251
 
226
252
  const TestnetEntryQueueConfig = {
227
- bootstrapValidatorSetSize: 750n,
228
- bootstrapFlushSize: 32n,
229
- normalFlushSizeMin: 32n,
230
- normalFlushSizeQuotient: 2475n,
231
- maxQueueFlushSize: 32n,
253
+ bootstrapValidatorSetSize: 48n,
254
+ bootstrapFlushSize: 48n,
255
+ normalFlushSizeMin: 4n,
256
+ normalFlushSizeQuotient: 2048n,
257
+ maxQueueFlushSize: 8n,
232
258
  };
233
259
 
234
260
  const StagingIgnitionEntryQueueConfig = {
@@ -239,6 +265,14 @@ const StagingIgnitionEntryQueueConfig = {
239
265
  maxQueueFlushSize: 24n,
240
266
  };
241
267
 
268
+ const MainnetEntryQueueConfig = {
269
+ bootstrapValidatorSetSize: 500n,
270
+ bootstrapFlushSize: 500n,
271
+ normalFlushSizeMin: 1n,
272
+ normalFlushSizeQuotient: 2_048n,
273
+ maxQueueFlushSize: 8n,
274
+ };
275
+
242
276
  export const getEntryQueueConfig = (networkName: NetworkNames) => {
243
277
  switch (networkName) {
244
278
  case 'local':
@@ -249,6 +283,8 @@ export const getEntryQueueConfig = (networkName: NetworkNames) => {
249
283
  return TestnetEntryQueueConfig;
250
284
  case 'staging-ignition':
251
285
  return StagingIgnitionEntryQueueConfig;
286
+ case 'mainnet':
287
+ return MainnetEntryQueueConfig;
252
288
  default:
253
289
  throw new Error(`Unrecognized network name: ${networkName}`);
254
290
  }