@aztec/ethereum 3.0.0-nightly.20251214 → 3.0.0-nightly.20251217

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 (47) hide show
  1. package/dest/deploy_aztec_l1_contracts.d.ts +2 -2
  2. package/dest/deploy_aztec_l1_contracts.d.ts.map +1 -1
  3. package/dest/deploy_aztec_l1_contracts.js +144 -107
  4. package/dest/l1_artifacts.d.ts +61 -41
  5. package/dest/l1_artifacts.d.ts.map +1 -1
  6. package/dest/l1_contract_addresses.d.ts +1 -1
  7. package/dest/l1_contract_addresses.d.ts.map +1 -1
  8. package/dest/l1_contract_addresses.js +3 -3
  9. package/dest/l1_tx_utils/constants.d.ts +7 -1
  10. package/dest/l1_tx_utils/constants.d.ts.map +1 -1
  11. package/dest/l1_tx_utils/constants.js +25 -0
  12. package/dest/l1_tx_utils/fee-strategies/index.d.ts +9 -0
  13. package/dest/l1_tx_utils/fee-strategies/index.d.ts.map +1 -0
  14. package/dest/l1_tx_utils/fee-strategies/index.js +11 -0
  15. package/dest/l1_tx_utils/fee-strategies/p75_competitive.d.ts +18 -0
  16. package/dest/l1_tx_utils/fee-strategies/p75_competitive.d.ts.map +1 -0
  17. package/dest/l1_tx_utils/fee-strategies/p75_competitive.js +111 -0
  18. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.d.ts +32 -0
  19. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.d.ts.map +1 -0
  20. package/dest/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.js +173 -0
  21. package/dest/l1_tx_utils/fee-strategies/types.d.ts +64 -0
  22. package/dest/l1_tx_utils/fee-strategies/types.d.ts.map +1 -0
  23. package/dest/l1_tx_utils/fee-strategies/types.js +24 -0
  24. package/dest/l1_tx_utils/index.d.ts +3 -1
  25. package/dest/l1_tx_utils/index.d.ts.map +1 -1
  26. package/dest/l1_tx_utils/index.js +2 -0
  27. package/dest/l1_tx_utils/l1_fee_analyzer.d.ts +232 -0
  28. package/dest/l1_tx_utils/l1_fee_analyzer.d.ts.map +1 -0
  29. package/dest/l1_tx_utils/l1_fee_analyzer.js +506 -0
  30. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +1 -10
  31. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -1
  32. package/dest/l1_tx_utils/readonly_l1_tx_utils.js +43 -121
  33. package/dest/utils.d.ts +14 -2
  34. package/dest/utils.d.ts.map +1 -1
  35. package/dest/utils.js +18 -0
  36. package/package.json +6 -5
  37. package/src/deploy_aztec_l1_contracts.ts +141 -107
  38. package/src/l1_contract_addresses.ts +22 -20
  39. package/src/l1_tx_utils/constants.ts +11 -0
  40. package/src/l1_tx_utils/fee-strategies/index.ts +22 -0
  41. package/src/l1_tx_utils/fee-strategies/p75_competitive.ts +159 -0
  42. package/src/l1_tx_utils/fee-strategies/p75_competitive_blob_txs_only.ts +241 -0
  43. package/src/l1_tx_utils/fee-strategies/types.ts +88 -0
  44. package/src/l1_tx_utils/index.ts +2 -0
  45. package/src/l1_tx_utils/l1_fee_analyzer.ts +803 -0
  46. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +47 -158
  47. 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 { 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
- const HISTORICAL_BLOCK_COUNT = 5;
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,119 +38,32 @@ 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
44
  // Make all RPC calls in parallel upfront with retry logic
45
+ // First 2 calls are necessary to complete
131
46
  const latestBlockPromise = this.tryTwice(()=>this.client.getBlock({
132
47
  blockTag: 'latest'
133
48
  }), 'Getting latest block');
134
- const networkEstimatePromise = this.tryTwice(()=>this.client.estimateMaxPriorityFeePerGas(), 'Estimating max priority fee per gas');
135
- const pendingBlockPromise = this.tryTwice(()=>this.client.getBlock({
136
- blockTag: 'pending',
137
- includeTransactions: true
138
- }), 'Getting pending block');
139
- const feeHistoryPromise = 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([
49
+ let blobBaseFeePromise = null;
50
+ if (isBlobTx) {
51
+ blobBaseFeePromise = this.tryTwice(()=>this.client.getBlobBaseFee(), 'Getting blob base fee');
52
+ }
53
+ // Get strategy promises for priority fee calculation
54
+ const strategyPromises = CurrentStrategy.getRequiredPromises(this.client, {
55
+ isBlobTx
56
+ });
57
+ const strategyPromiseKeys = [];
58
+ const strategyPromisesArr = [];
59
+ for (const [key, promise] of Object.entries(strategyPromises)){
60
+ strategyPromiseKeys.push(key);
61
+ strategyPromisesArr.push(this.tryTwice(()=>promise, `Getting strategy data for ${key}`));
62
+ }
63
+ const [latestBlockResult, blobBaseFeeResult, ...strategyResults] = await Promise.allSettled([
147
64
  latestBlockPromise,
148
- networkEstimatePromise,
149
- pendingBlockPromise,
150
- feeHistoryPromise,
151
- blobBaseFeePromise ?? Promise.resolve(0n)
65
+ blobBaseFeePromise ?? Promise.resolve(0n),
66
+ ...strategyPromisesArr
152
67
  ]);
153
68
  // Extract results
154
69
  const baseFee = latestBlockResult.status === 'fulfilled' && typeof latestBlockResult.value === 'object' && latestBlockResult.value.baseFeePerGas ? latestBlockResult.value.baseFeePerGas : 0n;
@@ -159,22 +74,29 @@ export class ReadOnlyL1TxUtils {
159
74
  } else if (isBlobTx) {
160
75
  this.logger?.warn('Failed to get L1 blob base fee', attempt);
161
76
  }
162
- // Get competitive priority fee
163
- let priorityFee = this.getCompetitivePriorityFee(networkEstimateResult, pendingBlockResult, feeHistoryResult);
164
- // Apply minimum priority fee as a floor if configured
77
+ let priorityFee;
78
+ // Get competitive priority fee using strategy
79
+ // Reconstruct the results object with the same keys as the promises
80
+ const resultsObject = {};
81
+ strategyPromiseKeys.forEach((key, index)=>{
82
+ resultsObject[key] = strategyResults[index];
83
+ });
84
+ const result = CurrentStrategy.calculate(resultsObject, {
85
+ gasConfig,
86
+ isBlobTx,
87
+ logger: this.logger
88
+ });
89
+ priorityFee = result.priorityFee;
90
+ // Apply minimum priority fee floor if configured
165
91
  if (gasConfig.minimumPriorityFeePerGas) {
166
- const minimumFee = BigInt(Math.trunc(gasConfig.minimumPriorityFeePerGas * Number(WEI_CONST)));
167
- if (minimumFee > priorityFee) {
168
- this.logger?.debug('Using minimum priority fee as floor', {
169
- minimumPriorityFeePerGas: formatGwei(minimumFee),
170
- competitiveFee: formatGwei(priorityFee)
171
- });
172
- priorityFee = minimumFee;
173
- } else {
174
- this.logger?.debug('Competitive fee exceeds minimum, using competitive fee', {
175
- minimumPriorityFeePerGas: formatGwei(minimumFee),
176
- competitiveFee: formatGwei(priorityFee)
92
+ const minimumPriorityFee = BigInt(Math.trunc(gasConfig.minimumPriorityFeePerGas * Number(WEI_CONST)));
93
+ if (priorityFee < minimumPriorityFee) {
94
+ this.logger?.debug('Applying minimum priority fee floor', {
95
+ calculatedPriorityFee: formatGwei(priorityFee),
96
+ minimumPriorityFeePerGas: gasConfig.minimumPriorityFeePerGas,
97
+ appliedFee: formatGwei(minimumPriorityFee)
177
98
  });
99
+ priorityFee = minimumPriorityFee;
178
100
  }
179
101
  }
180
102
  let maxFeePerGas = baseFee;
package/dest/utils.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { Fr } from '@aztec/foundation/curves/bn254';
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
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbHMuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy91dGlscy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxFQUFFLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUN6RCxPQUFPLEtBQUssRUFBRSxNQUFNLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUdwRCxPQUFPLEVBQ0wsS0FBSyxHQUFHLEVBRVIsS0FBSyxpQkFBaUIsRUFFdEIsS0FBSyx3QkFBd0IsRUFDN0IsS0FBSyxHQUFHLEVBQ1IsS0FBSyxHQUFHLEVBR1QsTUFBTSxNQUFNLENBQUM7QUFFZCxNQUFNLFdBQVcsT0FBTztJQUN0QixXQUFXLEVBQUUsRUFBRSxDQUFDO0lBQ2hCLFdBQVcsRUFBRSxFQUFFLENBQUM7SUFDaEIsV0FBVyxFQUFFLEdBQUcsQ0FBQztJQUNqQixnQkFBZ0IsRUFBRSxNQUFNLENBQUM7Q0FDMUI7QUFFRCxxQkFBYSxrQkFBbUIsU0FBUSxLQUFLO0lBQzNDLFlBQVksQ0FBQyxFQUFFLEdBQUcsRUFBRSxDQUFDO0lBRXJCLFlBQVksT0FBTyxFQUFFLE1BQU0sRUFBRSxZQUFZLENBQUMsRUFBRSxHQUFHLEVBQUUsRUFJaEQ7Q0FDRjtBQUVELHdCQUFnQixZQUFZLENBQzFCLEtBQUssQ0FBQyxJQUFJLFNBQVMsR0FBRyxHQUFHLFNBQVMsT0FBTyxFQUFFLEVBQzNDLFVBQVUsU0FBUyxpQkFBaUIsQ0FBQyxJQUFJLENBQUMsRUFDMUMsVUFBVSxHQUFHLHdCQUF3QixDQUFDLElBQUksRUFBRSxVQUFVLEVBQUUsR0FBRyxFQUFFLEVBQUUsU0FBUyxFQUFFLElBQUksQ0FBQyxFQUUvRSxJQUFJLEVBQUUsR0FBRyxFQUFFLEVBQ1gsT0FBTyxFQUFFLEdBQUcsRUFDWixHQUFHLEVBQUUsSUFBSSxFQUNULFNBQVMsRUFBRSxVQUFVLEVBQ3JCLE1BQU0sQ0FBQyxFQUFFLENBQUMsR0FBRyxFQUFFLFVBQVUsS0FBSyxPQUFPLEVBQ3JDLE1BQU0sQ0FBQyxFQUFFLE1BQU0sR0FDZCxVQUFVLENBTVo7QUFFRCx3QkFBZ0IsZUFBZSxDQUM3QixLQUFLLENBQUMsSUFBSSxTQUFTLEdBQUcsR0FBRyxTQUFTLE9BQU8sRUFBRSxFQUMzQyxVQUFVLFNBQVMsaUJBQWlCLENBQUMsSUFBSSxDQUFDLEVBQzFDLFVBQVUsR0FBRyx3QkFBd0IsQ0FBQyxJQUFJLEVBQUUsVUFBVSxFQUFFLEdBQUcsRUFBRSxFQUFFLFNBQVMsRUFBRSxJQUFJLENBQUMsRUFFL0UsSUFBSSxFQUFFLEdBQUcsRUFBRSxFQUNYLE9BQU8sRUFBRSxHQUFHLEVBQ1osR0FBRyxFQUFFLElBQUksRUFDVCxTQUFTLEVBQUUsVUFBVSxFQUNyQixNQUFNLENBQUMsRUFBRSxDQUFDLEdBQUcsRUFBRSxVQUFVLEtBQUssT0FBTyxFQUNyQyxNQUFNLENBQUMsRUFBRSxNQUFNLEdBQ2QsVUFBVSxHQUFHLFNBQVMsQ0FnQnhCO0FBRUQsd0JBQWdCLHFCQUFxQixDQUFDLEdBQUcsRUFBRSxHQUFHLE9BVzdDO0FBMEJEOzs7OztHQUtHO0FBQ0gsd0JBQWdCLGVBQWUsQ0FBQyxLQUFLLEVBQUUsR0FBRyxFQUFFLEdBQUcsR0FBRSxHQUFlLEdBQUcsa0JBQWtCLENBd0VwRjtBQXlCRCx3QkFBZ0IscUJBQXFCLENBQUMsR0FBRyxFQUFFLEdBQUcsc0JBYTdDIn0=
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;
37
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidXRpbHMuZC50cyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy91dGlscy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssRUFBRSxFQUFFLEVBQUUsTUFBTSxnQ0FBZ0MsQ0FBQztBQUN6RCxPQUFPLEtBQUssRUFBRSxNQUFNLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUdwRCxPQUFPLEVBQ0wsS0FBSyxHQUFHLEVBRVIsS0FBSyxpQkFBaUIsRUFFdEIsS0FBSyx3QkFBd0IsRUFDN0IsS0FBSyxvQkFBb0IsRUFDekIsS0FBSyxHQUFHLEVBQ1IsS0FBSyxHQUFHLEVBR1QsTUFBTSxNQUFNLENBQUM7QUFFZCxNQUFNLFdBQVcsT0FBTztJQUN0QixXQUFXLEVBQUUsRUFBRSxDQUFDO0lBQ2hCLFdBQVcsRUFBRSxFQUFFLENBQUM7SUFDaEIsV0FBVyxFQUFFLEdBQUcsQ0FBQztJQUNqQixnQkFBZ0IsRUFBRSxNQUFNLENBQUM7Q0FDMUI7QUFFRCxxQkFBYSxrQkFBbUIsU0FBUSxLQUFLO0lBQzNDLFlBQVksQ0FBQyxFQUFFLEdBQUcsRUFBRSxDQUFDO0lBRXJCLFlBQVksT0FBTyxFQUFFLE1BQU0sRUFBRSxZQUFZLENBQUMsRUFBRSxHQUFHLEVBQUUsRUFJaEQ7Q0FDRjtBQUVELHdCQUFnQixZQUFZLENBQzFCLEtBQUssQ0FBQyxJQUFJLFNBQVMsR0FBRyxHQUFHLFNBQVMsT0FBTyxFQUFFLEVBQzNDLFVBQVUsU0FBUyxpQkFBaUIsQ0FBQyxJQUFJLENBQUMsRUFDMUMsVUFBVSxHQUFHLHdCQUF3QixDQUFDLElBQUksRUFBRSxVQUFVLEVBQUUsR0FBRyxFQUFFLEVBQUUsU0FBUyxFQUFFLElBQUksQ0FBQyxFQUUvRSxJQUFJLEVBQUUsR0FBRyxFQUFFLEVBQ1gsT0FBTyxFQUFFLEdBQUcsRUFDWixHQUFHLEVBQUUsSUFBSSxFQUNULFNBQVMsRUFBRSxVQUFVLEVBQ3JCLE1BQU0sQ0FBQyxFQUFFLENBQUMsR0FBRyxFQUFFLFVBQVUsS0FBSyxPQUFPLEVBQ3JDLE1BQU0sQ0FBQyxFQUFFLE1BQU0sR0FDZCxVQUFVLENBTVo7QUFFRCx3QkFBZ0IsZUFBZSxDQUM3QixLQUFLLENBQUMsSUFBSSxTQUFTLEdBQUcsR0FBRyxTQUFTLE9BQU8sRUFBRSxFQUMzQyxVQUFVLFNBQVMsaUJBQWlCLENBQUMsSUFBSSxDQUFDLEVBQzFDLFVBQVUsR0FBRyx3QkFBd0IsQ0FBQyxJQUFJLEVBQUUsVUFBVSxFQUFFLEdBQUcsRUFBRSxFQUFFLFNBQVMsRUFBRSxJQUFJLENBQUMsRUFFL0UsSUFBSSxFQUFFLEdBQUcsRUFBRSxFQUNYLE9BQU8sRUFBRSxHQUFHLEVBQ1osR0FBRyxFQUFFLElBQUksRUFDVCxTQUFTLEVBQUUsVUFBVSxFQUNyQixNQUFNLENBQUMsRUFBRSxDQUFDLEdBQUcsRUFBRSxVQUFVLEtBQUssT0FBTyxFQUNyQyxNQUFNLENBQUMsRUFBRSxNQUFNLEdBQ2QsVUFBVSxHQUFHLFNBQVMsQ0FnQnhCO0FBRUQsd0JBQWdCLHFCQUFxQixDQUFDLEdBQUcsRUFBRSxHQUFHLE9BVzdDO0FBMEJEOzs7OztHQUtHO0FBQ0gsd0JBQWdCLGVBQWUsQ0FBQyxLQUFLLEVBQUUsR0FBRyxFQUFFLEdBQUcsR0FBRSxHQUFlLEdBQUcsa0JBQWtCLENBd0VwRjtBQXlCRCx3QkFBZ0IscUJBQXFCLENBQUMsR0FBRyxFQUFFLEdBQUcsc0JBYTdDO0FBRUQ7OztHQUdHO0FBQ0gsd0JBQWdCLGlCQUFpQixDQUFDLEVBQUUsRUFBRSxvQkFBb0IsR0FBRyxFQUFFLElBQUksb0JBQW9CLEdBQUc7SUFDeEYsZ0JBQWdCLEVBQUUsTUFBTSxDQUFDO0lBQ3pCLG1CQUFtQixFQUFFLFNBQVMsR0FBRyxFQUFFLENBQUM7Q0FDckMsQ0FPQTtBQUVEOztHQUVHO0FBQ0gsd0JBQWdCLG1CQUFtQixDQUFDLE1BQU0sRUFBRSxNQUFNLEVBQUUsRUFBRSxVQUFVLEVBQUUsTUFBTSxHQUFHLE1BQU0sQ0FPaEYifQ==
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,gCAAgC,CAAC;AACzD,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;IAErB,YAAY,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,GAAG,EAAE,EAIhD;CACF;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,gCAAgC,CAAC;AACzD,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;IAErB,YAAY,OAAO,EAAE,MAAM,EAAE,YAAY,CAAC,EAAE,GAAG,EAAE,EAIhD;CACF;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": "3.0.0-nightly.20251214",
3
+ "version": "3.0.0-nightly.20251217",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./account": "./dest/account.js",
@@ -17,6 +17,7 @@
17
17
  "./forwarder-proxy": "./dest/forwarder_proxy.js",
18
18
  "./l1-artifacts": "./dest/l1_artifacts.js",
19
19
  "./l1-contract-addresses": "./dest/l1_contract_addresses.js",
20
+ "./l1-fee-analysis": "./dest/l1_tx_utils/l1_fee_analyzer.js",
20
21
  "./l1-reader": "./dest/l1_reader.js",
21
22
  "./l1-tx-utils": "./dest/l1_tx_utils/index.js",
22
23
  "./l1-tx-utils-with-blobs": "./dest/l1_tx_utils/index-blobs.js",
@@ -48,10 +49,10 @@
48
49
  "../package.common.json"
49
50
  ],
50
51
  "dependencies": {
51
- "@aztec/blob-lib": "3.0.0-nightly.20251214",
52
- "@aztec/constants": "3.0.0-nightly.20251214",
53
- "@aztec/foundation": "3.0.0-nightly.20251214",
54
- "@aztec/l1-artifacts": "3.0.0-nightly.20251214",
52
+ "@aztec/blob-lib": "3.0.0-nightly.20251217",
53
+ "@aztec/constants": "3.0.0-nightly.20251217",
54
+ "@aztec/foundation": "3.0.0-nightly.20251217",
55
+ "@aztec/l1-artifacts": "3.0.0-nightly.20251217",
55
56
  "@viem/anvil": "^0.0.10",
56
57
  "dotenv": "^16.0.3",
57
58
  "lodash.chunk": "^4.2.0",
@@ -10,7 +10,9 @@ import { fileURLToPath } from '@aztec/foundation/url';
10
10
  import { bn254 } from '@noble/curves/bn254';
11
11
  import type { Abi, Narrow } from 'abitype';
12
12
  import { spawn } from 'child_process';
13
- import { dirname, resolve } from 'path';
13
+ import { cp, mkdtemp, rm } from 'fs/promises';
14
+ import { tmpdir } from 'os';
15
+ import { dirname, join, resolve } from 'path';
14
16
  import readline from 'readline';
15
17
  import type { Hex } from 'viem';
16
18
  import { foundry, mainnet } from 'viem/chains';
@@ -75,6 +77,17 @@ function runProcess<T>(
75
77
  return promise;
76
78
  }
77
79
 
80
+ /**
81
+ * Copies the foundry cache folder to a temporary location to avoid conflicts when running forge in parallel.
82
+ * The cache folder is small metadata that links to the out folder, so this is a fast operation.
83
+ */
84
+ async function copyFoundryCacheToTemp(l1ContractsPath: string): Promise<string> {
85
+ const cacheFolder = join(l1ContractsPath, 'cache');
86
+ const tempCacheFolder = await mkdtemp(join(tmpdir(), 'foundry-cache-'));
87
+ await cp(cacheFolder, tempCacheFolder, { recursive: true });
88
+ return tempCacheFolder;
89
+ }
90
+
78
91
  // Covers an edge where where we may have a cached BlobLib that is not meant for production.
79
92
  // Despite the profile apparently sometimes cached code remains (so says Lasse after his ignition-monorepo arc).
80
93
  async function maybeForgeForceProductionBuild(l1ContractsPath: string, script: string, chainId: number) {
@@ -203,6 +216,7 @@ export async function deployAztecL1Contracts(
203
216
  privateKey: `0x${string}`,
204
217
  chainId: number,
205
218
  args: DeployAztecL1ContractsArgs,
219
+ verifyContracts = false,
206
220
  ): Promise<DeployAztecL1ContractsReturnType> {
207
221
  logger.info(`Deploying L1 contracts with config: ${jsonStringify(args)}`);
208
222
  if (args.initialValidators && args.initialValidators.length > 0 && args.existingTokenAddress) {
@@ -246,90 +260,101 @@ export async function deployAztecL1Contracts(
246
260
  const FORGE_SCRIPT = 'script/deploy/DeployAztecL1Contracts.s.sol';
247
261
  await maybeForgeForceProductionBuild(l1ContractsPath, FORGE_SCRIPT, chainId);
248
262
 
249
- // From heuristic testing. More caused issues with anvil.
250
- const MAGIC_ANVIL_BATCH_SIZE = 12;
251
- // Anvil seems to stall with unbounded batch size. Otherwise no max batch size is desirable.
252
- // On sepolia and mainnet, we verify on etherscan (if etherscan API key is in env)
253
- const forgeArgs = [
254
- 'script',
255
- FORGE_SCRIPT,
256
- '--sig',
257
- 'run()',
258
- '--private-key',
259
- privateKey,
260
- '--rpc-url',
261
- rpcUrl,
262
- '--broadcast',
263
- ...(chainId === foundry.id ? ['--batch-size', MAGIC_ANVIL_BATCH_SIZE.toString()] : ['--verify']),
264
- ];
265
- const forgeEnv = {
266
- // Env vars required by l1-contracts/script/deploy/DeploymentConfiguration.sol.
267
- NETWORK: getActiveNetworkName(),
268
- FOUNDRY_PROFILE: chainId === mainnet.id ? 'production' : undefined,
269
- ...getDeployAztecL1ContractsEnvVars(args),
270
- };
271
- const result = await runProcess<ForgeL1ContractsDeployResult>('forge', forgeArgs, forgeEnv, l1ContractsPath);
272
- if (!result) {
273
- throw new Error('Forge script did not output deployment result');
274
- }
275
- logger.info(`Deployed L1 contracts with L1 addresses: ${jsonStringify(result)}`);
276
-
277
- const rollup = new RollupContract(l1Client, result.rollupAddress);
263
+ // Copy cache to temp location to avoid conflicts when running forge in parallel
264
+ const tempCachePath = await copyFoundryCacheToTemp(l1ContractsPath);
265
+
266
+ try {
267
+ // From heuristic testing. More caused issues with anvil.
268
+ const MAGIC_ANVIL_BATCH_SIZE = 12;
269
+ // Anvil seems to stall with unbounded batch size. Otherwise no max batch size is desirable.
270
+ // On sepolia and mainnet, we verify on etherscan (if etherscan API key is in env)
271
+ const forgeArgs = [
272
+ 'script',
273
+ FORGE_SCRIPT,
274
+ '--sig',
275
+ 'run()',
276
+ '--private-key',
277
+ privateKey,
278
+ '--rpc-url',
279
+ rpcUrl,
280
+ '--broadcast',
281
+ ...(chainId === foundry.id ? ['--batch-size', MAGIC_ANVIL_BATCH_SIZE.toString()] : []),
282
+ ...(verifyContracts ? ['--verify'] : []),
283
+ ];
284
+ const forgeEnv = {
285
+ // Protect against root leaving deployment files in docker that cannot be used later.
286
+ FOUNDRY_BROADCAST: process.getuid?.() === 0 ? 'broadcast-root' : tempCachePath,
287
+ // Env vars required by l1-contracts/script/deploy/DeploymentConfiguration.sol.
288
+ NETWORK: getActiveNetworkName(),
289
+ FOUNDRY_PROFILE: chainId === mainnet.id ? 'production' : undefined,
290
+ FOUNDRY_CACHE_PATH: tempCachePath,
291
+ ...getDeployAztecL1ContractsEnvVars(args),
292
+ };
293
+ const result = await runProcess<ForgeL1ContractsDeployResult>('forge', forgeArgs, forgeEnv, l1ContractsPath);
294
+ if (!result) {
295
+ throw new Error('Forge script did not output deployment result');
296
+ }
297
+ logger.info(`Deployed L1 contracts with L1 addresses: ${jsonStringify(result)}`);
278
298
 
279
- if (isAnvilTestChain(chainId)) {
280
- // @note We make a time jump PAST the very first slot to not have to deal with the edge case of the first slot.
281
- // The edge case being that the genesis block is already occupying slot 0, so we cannot have another block.
282
- try {
283
- // Need to get the time
284
- const currentSlot = await rollup.getSlotNumber();
299
+ const rollup = new RollupContract(l1Client, result.rollupAddress);
285
300
 
286
- if (currentSlot === 0) {
287
- const ts = Number(await rollup.getTimestampForSlot(SlotNumber(1)));
288
- await rpcCall('evm_setNextBlockTimestamp', [ts]);
289
- await rpcCall('hardhat_mine', [1]);
301
+ if (isAnvilTestChain(chainId)) {
302
+ // @note We make a time jump PAST the very first slot to not have to deal with the edge case of the first slot.
303
+ // The edge case being that the genesis block is already occupying slot 0, so we cannot have another block.
304
+ try {
305
+ // Need to get the time
290
306
  const currentSlot = await rollup.getSlotNumber();
291
307
 
292
- if (currentSlot !== 1) {
293
- throw new Error(`Error jumping time: current slot is ${currentSlot}`);
308
+ if (currentSlot === 0) {
309
+ const ts = Number(await rollup.getTimestampForSlot(SlotNumber(1)));
310
+ await rpcCall('evm_setNextBlockTimestamp', [ts]);
311
+ await rpcCall('hardhat_mine', [1]);
312
+ const currentSlot = await rollup.getSlotNumber();
313
+
314
+ if (currentSlot !== 1) {
315
+ throw new Error(`Error jumping time: current slot is ${currentSlot}`);
316
+ }
317
+ logger.info(`Jumped to slot 1`);
294
318
  }
295
- logger.info(`Jumped to slot 1`);
319
+ } catch (e) {
320
+ throw new Error(`Error jumping time: ${e}`);
296
321
  }
297
- } catch (e) {
298
- throw new Error(`Error jumping time: ${e}`);
299
322
  }
300
- }
301
323
 
302
- return {
303
- l1Client,
304
- rollupVersion: result.rollupVersion,
305
- l1ContractAddresses: {
306
- rollupAddress: EthAddress.fromString(result.rollupAddress),
307
- registryAddress: EthAddress.fromString(result.registryAddress),
308
- inboxAddress: EthAddress.fromString(result.inboxAddress),
309
- outboxAddress: EthAddress.fromString(result.outboxAddress),
310
- feeJuiceAddress: EthAddress.fromString(result.feeAssetAddress),
311
- feeJuicePortalAddress: EthAddress.fromString(result.feeJuicePortalAddress),
312
- coinIssuerAddress: EthAddress.fromString(result.coinIssuerAddress),
313
- rewardDistributorAddress: EthAddress.fromString(result.rewardDistributorAddress),
314
- governanceProposerAddress: EthAddress.fromString(result.governanceProposerAddress),
315
- governanceAddress: EthAddress.fromString(result.governanceAddress),
316
- stakingAssetAddress: EthAddress.fromString(result.stakingAssetAddress),
317
- slashFactoryAddress: result.slashFactoryAddress ? EthAddress.fromString(result.slashFactoryAddress) : undefined,
318
- feeAssetHandlerAddress: result.feeAssetHandlerAddress
319
- ? EthAddress.fromString(result.feeAssetHandlerAddress)
320
- : undefined,
321
- stakingAssetHandlerAddress: result.stakingAssetHandlerAddress
322
- ? EthAddress.fromString(result.stakingAssetHandlerAddress)
323
- : undefined,
324
- zkPassportVerifierAddress: result.zkPassportVerifierAddress
325
- ? EthAddress.fromString(result.zkPassportVerifierAddress)
326
- : undefined,
327
- gseAddress: result.gseAddress ? EthAddress.fromString(result.gseAddress) : undefined,
328
- dateGatedRelayerAddress: result.dateGatedRelayerAddress
329
- ? EthAddress.fromString(result.dateGatedRelayerAddress)
330
- : undefined,
331
- },
332
- };
324
+ return {
325
+ l1Client,
326
+ rollupVersion: result.rollupVersion,
327
+ l1ContractAddresses: {
328
+ rollupAddress: EthAddress.fromString(result.rollupAddress),
329
+ registryAddress: EthAddress.fromString(result.registryAddress),
330
+ inboxAddress: EthAddress.fromString(result.inboxAddress),
331
+ outboxAddress: EthAddress.fromString(result.outboxAddress),
332
+ feeJuiceAddress: EthAddress.fromString(result.feeAssetAddress),
333
+ feeJuicePortalAddress: EthAddress.fromString(result.feeJuicePortalAddress),
334
+ coinIssuerAddress: EthAddress.fromString(result.coinIssuerAddress),
335
+ rewardDistributorAddress: EthAddress.fromString(result.rewardDistributorAddress),
336
+ governanceProposerAddress: EthAddress.fromString(result.governanceProposerAddress),
337
+ governanceAddress: EthAddress.fromString(result.governanceAddress),
338
+ stakingAssetAddress: EthAddress.fromString(result.stakingAssetAddress),
339
+ slashFactoryAddress: result.slashFactoryAddress ? EthAddress.fromString(result.slashFactoryAddress) : undefined,
340
+ feeAssetHandlerAddress: result.feeAssetHandlerAddress
341
+ ? EthAddress.fromString(result.feeAssetHandlerAddress)
342
+ : undefined,
343
+ stakingAssetHandlerAddress: result.stakingAssetHandlerAddress
344
+ ? EthAddress.fromString(result.stakingAssetHandlerAddress)
345
+ : undefined,
346
+ zkPassportVerifierAddress: result.zkPassportVerifierAddress
347
+ ? EthAddress.fromString(result.zkPassportVerifierAddress)
348
+ : undefined,
349
+ gseAddress: result.gseAddress ? EthAddress.fromString(result.gseAddress) : undefined,
350
+ dateGatedRelayerAddress: result.dateGatedRelayerAddress
351
+ ? EthAddress.fromString(result.dateGatedRelayerAddress)
352
+ : undefined,
353
+ },
354
+ };
355
+ } finally {
356
+ await rm(tempCachePath, { recursive: true, force: true });
357
+ }
333
358
  }
334
359
 
335
360
  export const DEPLOYER_ADDRESS: Hex = '0x4e59b44847b379578588920cA78FbF26c0B4956C';
@@ -509,37 +534,46 @@ export const deployRollupForUpgrade = async (
509
534
  const FORGE_SCRIPT = 'script/deploy/DeployRollupForUpgrade.s.sol';
510
535
  await maybeForgeForceProductionBuild(l1ContractsPath, FORGE_SCRIPT, chainId);
511
536
 
512
- const forgeArgs = [
513
- 'script',
514
- FORGE_SCRIPT,
515
- '--sig',
516
- 'run()',
517
- '--private-key',
518
- privateKey,
519
- '--rpc-url',
520
- rpcUrl,
521
- '--broadcast',
522
- ];
523
- const forgeEnv = {
524
- FOUNDRY_PROFILE: chainId === mainnet.id ? 'production' : undefined,
525
- // Env vars required by l1-contracts/script/deploy/RollupConfiguration.sol.
526
- REGISTRY_ADDRESS: registryAddress.toString(),
527
- NETWORK: getActiveNetworkName(),
528
- ...getDeployRollupForUpgradeEnvVars(args),
529
- };
530
-
531
- const result = await runProcess<ForgeRollupUpgradeResult>('forge', forgeArgs, forgeEnv, l1ContractsPath);
532
- if (!result) {
533
- throw new Error('Forge script did not output deployment result');
534
- }
537
+ // Copy cache to temp location to avoid conflicts when running forge in parallel
538
+ const tempCachePath = await copyFoundryCacheToTemp(l1ContractsPath);
539
+
540
+ try {
541
+ const forgeArgs = [
542
+ 'script',
543
+ FORGE_SCRIPT,
544
+ '--sig',
545
+ 'run()',
546
+ '--private-key',
547
+ privateKey,
548
+ '--rpc-url',
549
+ rpcUrl,
550
+ '--broadcast',
551
+ ];
552
+ const forgeEnv = {
553
+ FOUNDRY_PROFILE: chainId === mainnet.id ? 'production' : undefined,
554
+ // Env vars required by l1-contracts/script/deploy/RollupConfiguration.sol.
555
+ REGISTRY_ADDRESS: registryAddress.toString(),
556
+ NETWORK: getActiveNetworkName(),
557
+ FOUNDRY_CACHE_PATH: tempCachePath,
558
+ FOUNDRY_BROADCAST: tempCachePath,
559
+ ...getDeployRollupForUpgradeEnvVars(args),
560
+ };
561
+
562
+ const result = await runProcess<ForgeRollupUpgradeResult>('forge', forgeArgs, forgeEnv, l1ContractsPath);
563
+ if (!result) {
564
+ throw new Error('Forge script did not output deployment result');
565
+ }
535
566
 
536
- const extendedClient = createExtendedL1Client([rpcUrl], privateKey);
567
+ const extendedClient = createExtendedL1Client([rpcUrl], privateKey);
537
568
 
538
- // Create RollupContract wrapper for the deployed rollup
539
- const rollup = new RollupContract(extendedClient, result.rollupAddress);
569
+ // Create RollupContract wrapper for the deployed rollup
570
+ const rollup = new RollupContract(extendedClient, result.rollupAddress);
540
571
 
541
- return {
542
- rollup,
543
- slashFactoryAddress: result.slashFactoryAddress,
544
- };
572
+ return {
573
+ rollup,
574
+ slashFactoryAddress: result.slashFactoryAddress,
575
+ };
576
+ } finally {
577
+ await rm(tempCachePath, { recursive: true, force: true });
578
+ }
545
579
  };