@aztec/ethereum 3.0.0-canary.a9708bd → 3.0.0-devnet.3

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 (144) hide show
  1. package/dest/client.d.ts +1 -1
  2. package/dest/client.d.ts.map +1 -1
  3. package/dest/config.d.ts +11 -6
  4. package/dest/config.d.ts.map +1 -1
  5. package/dest/config.js +124 -64
  6. package/dest/contracts/empire_base.d.ts +1 -1
  7. package/dest/contracts/empire_base.d.ts.map +1 -1
  8. package/dest/contracts/empire_slashing_proposer.d.ts +2 -2
  9. package/dest/contracts/empire_slashing_proposer.d.ts.map +1 -1
  10. package/dest/contracts/empire_slashing_proposer.js +1 -1
  11. package/dest/contracts/fee_asset_handler.d.ts +3 -3
  12. package/dest/contracts/fee_asset_handler.d.ts.map +1 -1
  13. package/dest/contracts/governance.js +7 -3
  14. package/dest/contracts/governance_proposer.d.ts +1 -2
  15. package/dest/contracts/governance_proposer.d.ts.map +1 -1
  16. package/dest/contracts/governance_proposer.js +1 -2
  17. package/dest/contracts/multicall.d.ts +3 -5
  18. package/dest/contracts/multicall.d.ts.map +1 -1
  19. package/dest/contracts/multicall.js +6 -4
  20. package/dest/contracts/rollup.d.ts +39 -19
  21. package/dest/contracts/rollup.d.ts.map +1 -1
  22. package/dest/contracts/rollup.js +84 -88
  23. package/dest/contracts/slasher_contract.d.ts +10 -0
  24. package/dest/contracts/slasher_contract.d.ts.map +1 -1
  25. package/dest/contracts/slasher_contract.js +18 -0
  26. package/dest/contracts/tally_slashing_proposer.d.ts +22 -3
  27. package/dest/contracts/tally_slashing_proposer.d.ts.map +1 -1
  28. package/dest/contracts/tally_slashing_proposer.js +55 -5
  29. package/dest/deploy_l1_contracts.d.ts +22 -7
  30. package/dest/deploy_l1_contracts.d.ts.map +1 -1
  31. package/dest/deploy_l1_contracts.js +555 -362
  32. package/dest/index.d.ts +1 -1
  33. package/dest/index.d.ts.map +1 -1
  34. package/dest/index.js +1 -1
  35. package/dest/l1_artifacts.d.ts +8729 -6014
  36. package/dest/l1_artifacts.d.ts.map +1 -1
  37. package/dest/l1_artifacts.js +10 -5
  38. package/dest/l1_contract_addresses.d.ts +5 -1
  39. package/dest/l1_contract_addresses.d.ts.map +1 -1
  40. package/dest/l1_contract_addresses.js +16 -26
  41. package/dest/l1_reader.d.ts +1 -1
  42. package/dest/l1_reader.d.ts.map +1 -1
  43. package/dest/l1_reader.js +8 -8
  44. package/dest/l1_tx_utils/config.d.ts +59 -0
  45. package/dest/l1_tx_utils/config.d.ts.map +1 -0
  46. package/dest/l1_tx_utils/config.js +73 -0
  47. package/dest/l1_tx_utils/constants.d.ts +6 -0
  48. package/dest/l1_tx_utils/constants.d.ts.map +1 -0
  49. package/dest/l1_tx_utils/constants.js +14 -0
  50. package/dest/l1_tx_utils/factory.d.ts +24 -0
  51. package/dest/l1_tx_utils/factory.d.ts.map +1 -0
  52. package/dest/l1_tx_utils/factory.js +12 -0
  53. package/dest/l1_tx_utils/index.d.ts +10 -0
  54. package/dest/l1_tx_utils/index.d.ts.map +1 -0
  55. package/dest/l1_tx_utils/index.js +10 -0
  56. package/dest/l1_tx_utils/interfaces.d.ts +76 -0
  57. package/dest/l1_tx_utils/interfaces.d.ts.map +1 -0
  58. package/dest/l1_tx_utils/interfaces.js +4 -0
  59. package/dest/l1_tx_utils/l1_tx_utils.d.ts +95 -0
  60. package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -0
  61. package/dest/l1_tx_utils/l1_tx_utils.js +610 -0
  62. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts +26 -0
  63. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts.map +1 -0
  64. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.js +26 -0
  65. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +81 -0
  66. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -0
  67. package/dest/l1_tx_utils/readonly_l1_tx_utils.js +294 -0
  68. package/dest/l1_tx_utils/signer.d.ts +4 -0
  69. package/dest/l1_tx_utils/signer.d.ts.map +1 -0
  70. package/dest/l1_tx_utils/signer.js +16 -0
  71. package/dest/l1_tx_utils/types.d.ts +67 -0
  72. package/dest/l1_tx_utils/types.d.ts.map +1 -0
  73. package/dest/l1_tx_utils/types.js +26 -0
  74. package/dest/l1_tx_utils/utils.d.ts +4 -0
  75. package/dest/l1_tx_utils/utils.d.ts.map +1 -0
  76. package/dest/l1_tx_utils/utils.js +14 -0
  77. package/dest/publisher_manager.d.ts +7 -2
  78. package/dest/publisher_manager.d.ts.map +1 -1
  79. package/dest/publisher_manager.js +36 -8
  80. package/dest/queries.d.ts.map +1 -1
  81. package/dest/queries.js +11 -12
  82. package/dest/test/chain_monitor.d.ts +11 -0
  83. package/dest/test/chain_monitor.d.ts.map +1 -1
  84. package/dest/test/chain_monitor.js +81 -12
  85. package/dest/test/delayed_tx_utils.d.ts +2 -2
  86. package/dest/test/delayed_tx_utils.d.ts.map +1 -1
  87. package/dest/test/delayed_tx_utils.js +2 -2
  88. package/dest/test/eth_cheat_codes.d.ts +32 -6
  89. package/dest/test/eth_cheat_codes.d.ts.map +1 -1
  90. package/dest/test/eth_cheat_codes.js +115 -28
  91. package/dest/test/rollup_cheat_codes.d.ts +11 -9
  92. package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
  93. package/dest/test/rollup_cheat_codes.js +38 -6
  94. package/dest/test/upgrade_utils.d.ts.map +1 -1
  95. package/dest/test/upgrade_utils.js +3 -2
  96. package/dest/utils.d.ts.map +1 -1
  97. package/dest/utils.js +10 -161
  98. package/dest/zkPassportVerifierAddress.js +1 -1
  99. package/package.json +7 -7
  100. package/src/client.ts +1 -1
  101. package/src/config.ts +136 -68
  102. package/src/contracts/empire_base.ts +1 -1
  103. package/src/contracts/empire_slashing_proposer.ts +7 -3
  104. package/src/contracts/fee_asset_handler.ts +1 -1
  105. package/src/contracts/governance.ts +3 -3
  106. package/src/contracts/governance_proposer.ts +3 -4
  107. package/src/contracts/multicall.ts +12 -10
  108. package/src/contracts/rollup.ts +104 -106
  109. package/src/contracts/slasher_contract.ts +22 -0
  110. package/src/contracts/tally_slashing_proposer.ts +54 -6
  111. package/src/deploy_l1_contracts.ts +570 -328
  112. package/src/index.ts +1 -1
  113. package/src/l1_artifacts.ts +14 -6
  114. package/src/l1_contract_addresses.ts +17 -26
  115. package/src/l1_reader.ts +9 -9
  116. package/src/l1_tx_utils/README.md +177 -0
  117. package/src/l1_tx_utils/config.ts +140 -0
  118. package/src/l1_tx_utils/constants.ts +18 -0
  119. package/src/l1_tx_utils/factory.ts +64 -0
  120. package/src/l1_tx_utils/index.ts +12 -0
  121. package/src/l1_tx_utils/interfaces.ts +86 -0
  122. package/src/l1_tx_utils/l1_tx_utils.ts +718 -0
  123. package/src/l1_tx_utils/l1_tx_utils_with_blobs.ts +77 -0
  124. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +372 -0
  125. package/src/l1_tx_utils/signer.ts +28 -0
  126. package/src/l1_tx_utils/types.ts +85 -0
  127. package/src/l1_tx_utils/utils.ts +16 -0
  128. package/src/publisher_manager.ts +51 -9
  129. package/src/queries.ts +13 -8
  130. package/src/test/chain_monitor.ts +89 -9
  131. package/src/test/delayed_tx_utils.ts +2 -2
  132. package/src/test/eth_cheat_codes.ts +142 -29
  133. package/src/test/rollup_cheat_codes.ts +54 -14
  134. package/src/test/upgrade_utils.ts +3 -2
  135. package/src/utils.ts +13 -185
  136. package/src/zkPassportVerifierAddress.ts +1 -1
  137. package/dest/l1_tx_utils.d.ts +0 -250
  138. package/dest/l1_tx_utils.d.ts.map +0 -1
  139. package/dest/l1_tx_utils.js +0 -826
  140. package/dest/l1_tx_utils_with_blobs.d.ts +0 -19
  141. package/dest/l1_tx_utils_with_blobs.d.ts.map +0 -1
  142. package/dest/l1_tx_utils_with_blobs.js +0 -85
  143. package/src/l1_tx_utils.ts +0 -1105
  144. package/src/l1_tx_utils_with_blobs.ts +0 -144
@@ -6,10 +6,11 @@ import type { Fr } from '@aztec/foundation/fields';
6
6
  import { jsonStringify } from '@aztec/foundation/json-rpc';
7
7
  import { type Logger, createLogger } from '@aztec/foundation/log';
8
8
  import { DateProvider } from '@aztec/foundation/timer';
9
- import type { RollupAbi } from '@aztec/l1-artifacts/RollupAbi';
9
+ import { RollupAbi } from '@aztec/l1-artifacts/RollupAbi';
10
10
 
11
11
  import type { Abi, Narrow } from 'abitype';
12
12
  import { mkdir, writeFile } from 'fs/promises';
13
+ import chunk from 'lodash.chunk';
13
14
  import {
14
15
  type Chain,
15
16
  type ContractConstructorArgs,
@@ -33,7 +34,6 @@ import { createExtendedL1Client } from './client.js';
33
34
  import {
34
35
  type L1ContractsConfig,
35
36
  getEntryQueueConfig,
36
- getGSEConfiguration,
37
37
  getGovernanceConfiguration,
38
38
  getRewardBoostConfig,
39
39
  getRewardConfig,
@@ -45,6 +45,7 @@ import { RegistryContract } from './contracts/registry.js';
45
45
  import { RollupContract, SlashingProposerType } from './contracts/rollup.js';
46
46
  import {
47
47
  CoinIssuerArtifact,
48
+ DateGatedRelayerArtifact,
48
49
  FeeAssetArtifact,
49
50
  FeeAssetHandlerArtifact,
50
51
  GSEArtifact,
@@ -63,21 +64,19 @@ import {
63
64
  import type { L1ContractAddresses } from './l1_contract_addresses.js';
64
65
  import {
65
66
  type GasPrice,
66
- type L1GasConfig,
67
+ type L1TxConfig,
67
68
  type L1TxRequest,
68
69
  L1TxUtils,
69
70
  type L1TxUtilsConfig,
70
71
  createL1TxUtilsFromViemWallet,
71
72
  getL1TxUtilsConfigEnvVars,
72
- } from './l1_tx_utils.js';
73
+ } from './l1_tx_utils/index.js';
73
74
  import type { ExtendedViemWalletClient } from './types.js';
74
75
  import { formatViemError } from './utils.js';
75
76
  import { ZK_PASSPORT_DOMAIN, ZK_PASSPORT_SCOPE, ZK_PASSPORT_VERIFIER_ADDRESS } from './zkPassportVerifierAddress.js';
76
77
 
77
78
  export const DEPLOYER_ADDRESS: Hex = '0x4e59b44847b379578588920cA78FbF26c0B4956C';
78
79
 
79
- const networkName = getActiveNetworkName();
80
-
81
80
  export type Operator = {
82
81
  attester: EthAddress;
83
82
  withdrawer: EthAddress;
@@ -148,8 +147,8 @@ export type VerificationRecord = {
148
147
  export interface DeployL1ContractsArgs extends Omit<L1ContractsConfig, keyof L1TxUtilsConfig> {
149
148
  /** The vk tree root. */
150
149
  vkTreeRoot: Fr;
151
- /** The protocol contract tree root. */
152
- protocolContractTreeRoot: Fr;
150
+ /** The hash of the protocol contracts. */
151
+ protocolContractsHash: Fr;
153
152
  /** The genesis root of the archive tree. */
154
153
  genesisArchiveRoot: Fr;
155
154
  /** The salt for CREATE2 deployment. */
@@ -166,6 +165,8 @@ export interface DeployL1ContractsArgs extends Omit<L1ContractsConfig, keyof L1T
166
165
  realVerifier: boolean;
167
166
  /** The zk passport args */
168
167
  zkPassportArgs?: ZKPassportArgs;
168
+ /** If provided, use this token for BOTH fee and staking assets (skip deployments) */
169
+ existingTokenAddress?: EthAddress;
169
170
  }
170
171
 
171
172
  export interface ZKPassportArgs {
@@ -177,39 +178,128 @@ export interface ZKPassportArgs {
177
178
  zkPassportScope?: string;
178
179
  }
179
180
 
181
+ // Minimal ERC20 ABI for validation purposes. We only read view methods.
182
+ const ERC20_VALIDATION_ABI = [
183
+ {
184
+ type: 'function',
185
+ name: 'totalSupply',
186
+ stateMutability: 'view',
187
+ inputs: [],
188
+ outputs: [{ name: '', type: 'uint256' }],
189
+ },
190
+ {
191
+ type: 'function',
192
+ name: 'name',
193
+ stateMutability: 'view',
194
+ inputs: [],
195
+ outputs: [{ name: '', type: 'string' }],
196
+ },
197
+ {
198
+ type: 'function',
199
+ name: 'symbol',
200
+ stateMutability: 'view',
201
+ inputs: [],
202
+ outputs: [{ name: '', type: 'string' }],
203
+ },
204
+ {
205
+ type: 'function',
206
+ name: 'decimals',
207
+ stateMutability: 'view',
208
+ inputs: [],
209
+ outputs: [{ name: '', type: 'uint8' }],
210
+ },
211
+ ] as const;
212
+
213
+ /**
214
+ * Validates that the provided address points to a contract that resembles an ERC20 token.
215
+ * Checks for contract code and attempts common ERC20 view calls.
216
+ * Throws an error if validation fails.
217
+ */
218
+ export async function validateExistingErc20TokenAddress(
219
+ l1Client: ExtendedViemWalletClient,
220
+ tokenAddress: EthAddress,
221
+ logger: Logger,
222
+ ): Promise<void> {
223
+ const addressString = tokenAddress.toString();
224
+
225
+ // Ensure there is contract code at the address
226
+ const code = await l1Client.getCode({ address: addressString });
227
+ if (!code || code === '0x') {
228
+ throw new Error(`No contract code found at provided token address ${addressString}`);
229
+ }
230
+
231
+ const contract = getContract({
232
+ address: getAddress(addressString),
233
+ abi: ERC20_VALIDATION_ABI,
234
+ client: l1Client,
235
+ });
236
+
237
+ // Validate all required ERC20 methods in parallel
238
+ const checks = [
239
+ contract.read.totalSupply().then(total => typeof total === 'bigint'),
240
+ contract.read.name().then(() => true),
241
+ contract.read.symbol().then(() => true),
242
+ contract.read.decimals().then(dec => typeof dec === 'number' || typeof dec === 'bigint'),
243
+ ];
244
+
245
+ const results = await Promise.allSettled(checks);
246
+ const failedChecks = results.filter(result => result.status === 'rejected' || result.value !== true);
247
+
248
+ if (failedChecks.length > 0) {
249
+ throw new Error(`Address ${addressString} does not appear to implement ERC20 view methods`);
250
+ }
251
+
252
+ logger.verbose(`Validated existing token at ${addressString} appears to be ERC20-compatible`);
253
+ }
254
+
180
255
  export const deploySharedContracts = async (
181
256
  l1Client: ExtendedViemWalletClient,
182
257
  deployer: L1Deployer,
183
258
  args: DeployL1ContractsArgs,
184
259
  logger: Logger,
185
260
  ) => {
186
- logger.info(`Deploying shared contracts for network configration: ${networkName}`);
261
+ const networkName = getActiveNetworkName();
262
+
263
+ logger.info(`Deploying shared contracts for network configuration: ${networkName}`);
187
264
 
188
265
  const txHashes: Hex[] = [];
189
266
 
190
- const feeAssetAddress = await deployer.deploy(FeeAssetArtifact, ['FeeJuice', 'FEE', l1Client.account.address]);
191
- logger.verbose(`Deployed Fee Asset at ${feeAssetAddress}`);
267
+ let feeAssetAddress: EthAddress;
268
+ let stakingAssetAddress: EthAddress;
269
+ if (args.existingTokenAddress) {
270
+ await validateExistingErc20TokenAddress(l1Client, args.existingTokenAddress, logger);
271
+ feeAssetAddress = args.existingTokenAddress;
272
+ stakingAssetAddress = args.existingTokenAddress;
273
+ logger.verbose(`Using existing token for fee and staking assets at ${args.existingTokenAddress}`);
274
+ } else {
275
+ const deployedFee = await deployer.deploy(FeeAssetArtifact, ['FeeJuice', 'FEE', l1Client.account.address]);
276
+ feeAssetAddress = deployedFee.address;
277
+ logger.verbose(`Deployed Fee Asset at ${feeAssetAddress}`);
192
278
 
193
- const stakingAssetAddress = await deployer.deploy(StakingAssetArtifact, ['Staking', 'STK', l1Client.account.address]);
194
- logger.verbose(`Deployed Staking Asset at ${stakingAssetAddress}`);
279
+ const deployedStaking = await deployer.deploy(StakingAssetArtifact, ['Staking', 'STK', l1Client.account.address]);
280
+ stakingAssetAddress = deployedStaking.address;
281
+ logger.verbose(`Deployed Staking Asset at ${stakingAssetAddress}`);
195
282
 
196
- const gseConfiguration = getGSEConfiguration(networkName);
283
+ await deployer.waitForDeployments();
284
+ }
197
285
 
198
- const gseAddress = await deployer.deploy(GSEArtifact, [
199
- l1Client.account.address,
200
- stakingAssetAddress.toString(),
201
- gseConfiguration.activationThreshold,
202
- gseConfiguration.ejectionThreshold,
203
- ]);
286
+ const gseAddress = (
287
+ await deployer.deploy(GSEArtifact, [
288
+ l1Client.account.address,
289
+ stakingAssetAddress.toString(),
290
+ args.activationThreshold,
291
+ args.ejectionThreshold,
292
+ ])
293
+ ).address;
204
294
  logger.verbose(`Deployed GSE at ${gseAddress}`);
205
295
 
206
- const registryAddress = await deployer.deploy(RegistryArtifact, [
296
+ const { address: registryAddress } = await deployer.deploy(RegistryArtifact, [
207
297
  l1Client.account.address,
208
298
  feeAssetAddress.toString(),
209
299
  ]);
210
300
  logger.verbose(`Deployed Registry at ${registryAddress}`);
211
301
 
212
- const governanceProposerAddress = await deployer.deploy(GovernanceProposerArtifact, [
302
+ const { address: governanceProposerAddress } = await deployer.deploy(GovernanceProposerArtifact, [
213
303
  registryAddress.toString(),
214
304
  gseAddress.toString(),
215
305
  BigInt(args.governanceProposerQuorum ?? args.governanceProposerRoundSize / 2 + 1),
@@ -219,7 +309,7 @@ export const deploySharedContracts = async (
219
309
 
220
310
  // @note @LHerskind the assets are expected to be the same at some point, but for better
221
311
  // configurability they are different for now.
222
- const governanceAddress = await deployer.deploy(GovernanceArtifact, [
312
+ const { address: governanceAddress } = await deployer.deploy(GovernanceArtifact, [
223
313
  stakingAssetAddress.toString(),
224
314
  governanceProposerAddress.toString(),
225
315
  gseAddress.toString(),
@@ -261,11 +351,13 @@ export const deploySharedContracts = async (
261
351
  txHashes.push(txHash);
262
352
  }
263
353
 
264
- const coinIssuerAddress = await deployer.deploy(CoinIssuerArtifact, [
265
- feeAssetAddress.toString(),
266
- 1_000_000n * 10n ** 18n, // @todo #8084
267
- l1Client.account.address,
268
- ]);
354
+ const coinIssuerAddress = (
355
+ await deployer.deploy(CoinIssuerArtifact, [
356
+ feeAssetAddress.toString(),
357
+ 2n * 10n ** 17n, // hard cap of 20% per year
358
+ l1Client.account.address,
359
+ ])
360
+ ).address;
269
361
  logger.verbose(`Deployed CoinIssuer at ${coinIssuerAddress}`);
270
362
 
271
363
  logger.verbose(`Waiting for deployments to complete`);
@@ -277,17 +369,19 @@ export const deploySharedContracts = async (
277
369
  let stakingAssetHandlerAddress: EthAddress | undefined = undefined;
278
370
  let zkPassportVerifierAddress: EthAddress | undefined = undefined;
279
371
 
280
- // Only if not on mainnet will we deploy the handlers
281
- if (l1Client.chain.id !== 1) {
372
+ // Only if not on mainnet will we deploy the handlers, and only when we control the token
373
+ if (l1Client.chain.id !== 1 && !args.existingTokenAddress) {
282
374
  /* -------------------------------------------------------------------------- */
283
375
  /* CHEAT CODES START HERE */
284
376
  /* -------------------------------------------------------------------------- */
285
377
 
286
- feeAssetHandlerAddress = await deployer.deploy(FeeAssetHandlerArtifact, [
287
- l1Client.account.address,
288
- feeAssetAddress.toString(),
289
- BigInt(1000n * 10n ** 18n),
290
- ]);
378
+ feeAssetHandlerAddress = (
379
+ await deployer.deploy(FeeAssetHandlerArtifact, [
380
+ l1Client.account.address,
381
+ feeAssetAddress.toString(),
382
+ BigInt(1000n * 10n ** 18n),
383
+ ])
384
+ ).address;
291
385
  logger.verbose(`Deployed FeeAssetHandler at ${feeAssetHandlerAddress}`);
292
386
 
293
387
  // Only if we are "fresh" will we be adding as a minter, otherwise above will simply get same address
@@ -316,6 +410,7 @@ export const deploySharedContracts = async (
316
410
  stakingAsset: stakingAssetAddress.toString(),
317
411
  registry: registryAddress.toString(),
318
412
  withdrawer: AMIN.toString(),
413
+ validatorsToFlush: 16n,
319
414
  mintInterval: BigInt(60 * 60 * 24),
320
415
  depositsPerMint: BigInt(10),
321
416
  depositMerkleRoot: '0x0000000000000000000000000000000000000000000000000000000000000000',
@@ -329,7 +424,8 @@ export const deploySharedContracts = async (
329
424
  skipMerkleCheck: true, // skip merkle check - needed for testing without generating proofs
330
425
  } as const;
331
426
 
332
- stakingAssetHandlerAddress = await deployer.deploy(StakingAssetHandlerArtifact, [stakingAssetHandlerDeployArgs]);
427
+ stakingAssetHandlerAddress = (await deployer.deploy(StakingAssetHandlerArtifact, [stakingAssetHandlerDeployArgs]))
428
+ .address;
333
429
  logger.verbose(`Deployed StakingAssetHandler at ${stakingAssetHandlerAddress}`);
334
430
 
335
431
  const { txHash: stakingMinterTxHash } = await deployer.sendTransaction({
@@ -365,19 +461,23 @@ export const deploySharedContracts = async (
365
461
 
366
462
  const rewardDistributorAddress = await registry.getRewardDistributor();
367
463
 
368
- const blockReward = getRewardConfig(networkName).blockReward;
464
+ if (!args.existingTokenAddress) {
465
+ const blockReward = getRewardConfig(networkName).blockReward;
369
466
 
370
- const funding = blockReward * 200000n;
371
- const { txHash: fundRewardDistributorTxHash } = await deployer.sendTransaction({
372
- to: feeAssetAddress.toString(),
373
- data: encodeFunctionData({
374
- abi: FeeAssetArtifact.contractAbi,
375
- functionName: 'mint',
376
- args: [rewardDistributorAddress.toString(), funding],
377
- }),
378
- });
467
+ const funding = blockReward * 200000n;
468
+ const { txHash: fundRewardDistributorTxHash } = await deployer.sendTransaction({
469
+ to: feeAssetAddress.toString(),
470
+ data: encodeFunctionData({
471
+ abi: FeeAssetArtifact.contractAbi,
472
+ functionName: 'mint',
473
+ args: [rewardDistributorAddress.toString(), funding],
474
+ }),
475
+ });
379
476
 
380
- logger.verbose(`Funded reward distributor with ${funding} fee asset in ${fundRewardDistributorTxHash}`);
477
+ logger.verbose(`Funded reward distributor with ${funding} fee asset in ${fundRewardDistributorTxHash}`);
478
+ } else {
479
+ logger.verbose(`Skipping reward distributor funding as existing token is provided`);
480
+ }
381
481
 
382
482
  /* -------------------------------------------------------------------------- */
383
483
  /* FUND REWARD DISTRIBUTOR STOP */
@@ -400,7 +500,7 @@ export const deploySharedContracts = async (
400
500
 
401
501
  const getZkPassportVerifierAddress = async (deployer: L1Deployer, args: DeployL1ContractsArgs): Promise<EthAddress> => {
402
502
  if (args.zkPassportArgs?.mockZkPassportVerifier) {
403
- return await deployer.deploy(mockVerifiers.mockZkPassportVerifier);
503
+ return (await deployer.deploy(mockVerifiers.mockZkPassportVerifier)).address;
404
504
  }
405
505
  return ZK_PASSPORT_VERIFIER_ADDRESS;
406
506
  };
@@ -416,6 +516,199 @@ const getZkPassportScopes = (args: DeployL1ContractsArgs): [string, string] => {
416
516
  return [domain, scope];
417
517
  };
418
518
 
519
+ /**
520
+ * Generates verification records for a deployed rollup and its associated contracts (Inbox, Outbox, Slasher, etc).
521
+ * @param rollup - The deployed rollup contract.
522
+ * @param deployer - The L1 deployer instance.
523
+ * @param args - The deployment arguments used for the rollup.
524
+ * @param addresses - The L1 contract addresses.
525
+ * @param extendedClient - The extended viem wallet client.
526
+ * @param logger - The logger.
527
+ */
528
+ async function generateRollupVerificationRecords(
529
+ rollup: RollupContract,
530
+ deployer: L1Deployer,
531
+ args: {
532
+ slashingVetoer: EthAddress;
533
+ slashingRoundSizeInEpochs: number;
534
+ aztecEpochDuration: number;
535
+ slashingQuorum?: number;
536
+ slashingLifetimeInRounds: number;
537
+ slashingExecutionDelayInRounds: number;
538
+ slasherFlavor: 'none' | 'tally' | 'empire';
539
+ slashAmountSmall: bigint;
540
+ slashAmountMedium: bigint;
541
+ slashAmountLarge: bigint;
542
+ aztecTargetCommitteeSize: number;
543
+ slashingOffsetInRounds: number;
544
+ },
545
+ addresses: Pick<L1ContractAddresses, 'feeJuiceAddress'>,
546
+ extendedClient: ExtendedViemWalletClient,
547
+ logger: Logger,
548
+ ): Promise<void> {
549
+ try {
550
+ // Add Inbox / Outbox verification records (constructor args are created inside RollupCore)
551
+ const rollupAddr = rollup.address;
552
+ const rollupAddresses = await rollup.getRollupAddresses();
553
+ const inboxAddr = rollupAddresses.inboxAddress.toString();
554
+ const outboxAddr = rollupAddresses.outboxAddress.toString();
555
+ const feeAsset = rollupAddresses.feeJuiceAddress.toString();
556
+ const version = await rollup.getVersion();
557
+
558
+ const inboxCtor = encodeAbiParameters(
559
+ [{ type: 'address' }, { type: 'address' }, { type: 'uint256' }, { type: 'uint256' }],
560
+ [rollupAddr, feeAsset, version, BigInt(L1_TO_L2_MSG_SUBTREE_HEIGHT)],
561
+ );
562
+
563
+ const outboxCtor = encodeAbiParameters([{ type: 'address' }, { type: 'uint256' }], [rollupAddr, version]);
564
+
565
+ deployer.verificationRecords.push(
566
+ { name: 'Inbox', address: inboxAddr, constructorArgsHex: inboxCtor, libraries: [] },
567
+ { name: 'Outbox', address: outboxAddr, constructorArgsHex: outboxCtor, libraries: [] },
568
+ );
569
+
570
+ // Include Slasher and SlashingProposer (if deployed) in verification data
571
+ try {
572
+ const slasherAddrHex = await rollup.getSlasherAddress();
573
+ const slasherAddr = EthAddress.fromString(slasherAddrHex);
574
+ if (!slasherAddr.isZero()) {
575
+ // Slasher constructor: (address _vetoer, address _governance)
576
+ const slasherCtor = encodeAbiParameters(
577
+ [{ type: 'address' }, { type: 'address' }],
578
+ [args.slashingVetoer.toString(), extendedClient.account.address],
579
+ );
580
+ deployer.verificationRecords.push({
581
+ name: 'Slasher',
582
+ address: slasherAddr.toString(),
583
+ constructorArgsHex: slasherCtor,
584
+ libraries: [],
585
+ });
586
+
587
+ // Proposer address is stored in Slasher.PROPOSER()
588
+ const proposerAddr = (await rollup.getSlashingProposerAddress()).toString();
589
+
590
+ // Compute constructor args matching deployment path in RollupCore
591
+ const computedRoundSize = BigInt(args.slashingRoundSizeInEpochs * args.aztecEpochDuration);
592
+ const computedQuorum = BigInt(
593
+ args.slashingQuorum ?? (args.slashingRoundSizeInEpochs * args.aztecEpochDuration) / 2 + 1,
594
+ );
595
+ const lifetimeInRounds = BigInt(args.slashingLifetimeInRounds);
596
+ const executionDelayInRounds = BigInt(args.slashingExecutionDelayInRounds);
597
+
598
+ if (args.slasherFlavor === 'tally') {
599
+ const slashAmounts: readonly [bigint, bigint, bigint] = [
600
+ args.slashAmountSmall,
601
+ args.slashAmountMedium,
602
+ args.slashAmountLarge,
603
+ ];
604
+ const committeeSize = BigInt(args.aztecTargetCommitteeSize);
605
+ const epochDuration = BigInt(args.aztecEpochDuration);
606
+ const slashOffsetInRounds = BigInt(args.slashingOffsetInRounds);
607
+
608
+ const proposerCtor = encodeAbiParameters(
609
+ [
610
+ { type: 'address' },
611
+ { type: 'address' },
612
+ { type: 'uint256' },
613
+ { type: 'uint256' },
614
+ { type: 'uint256' },
615
+ { type: 'uint256' },
616
+ { type: 'uint256[3]' },
617
+ { type: 'uint256' },
618
+ { type: 'uint256' },
619
+ { type: 'uint256' },
620
+ ],
621
+ [
622
+ rollup.address,
623
+ slasherAddr.toString(),
624
+ computedQuorum,
625
+ computedRoundSize,
626
+ lifetimeInRounds,
627
+ executionDelayInRounds,
628
+ slashAmounts,
629
+ committeeSize,
630
+ epochDuration,
631
+ slashOffsetInRounds,
632
+ ],
633
+ );
634
+
635
+ deployer.verificationRecords.push({
636
+ name: 'TallySlashingProposer',
637
+ address: proposerAddr,
638
+ constructorArgsHex: proposerCtor,
639
+ libraries: [],
640
+ });
641
+ } else if (args.slasherFlavor === 'empire') {
642
+ const proposerCtor = encodeAbiParameters(
643
+ [
644
+ { type: 'address' },
645
+ { type: 'address' },
646
+ { type: 'uint256' },
647
+ { type: 'uint256' },
648
+ { type: 'uint256' },
649
+ { type: 'uint256' },
650
+ ],
651
+ [
652
+ rollup.address,
653
+ slasherAddr.toString(),
654
+ computedQuorum,
655
+ computedRoundSize,
656
+ lifetimeInRounds,
657
+ executionDelayInRounds,
658
+ ],
659
+ );
660
+
661
+ deployer.verificationRecords.push({
662
+ name: 'EmpireSlashingProposer',
663
+ address: proposerAddr,
664
+ constructorArgsHex: proposerCtor,
665
+ libraries: [],
666
+ });
667
+ }
668
+ }
669
+ } catch (e) {
670
+ logger.warn(`Failed to add Slasher/Proposer verification records: ${String(e)}`);
671
+ }
672
+ } catch (e) {
673
+ throw new Error(`Failed to generate rollup verification records: ${String(e)}`);
674
+ }
675
+ }
676
+
677
+ /**
678
+ * Writes verification records to a JSON file for later forge verify.
679
+ * @param deployer - The L1 deployer containing verification records.
680
+ * @param outputDirectory - The directory to write the verification file to.
681
+ * @param chainId - The chain ID.
682
+ * @param filenameSuffix - Optional suffix for the filename (e.g., 'upgrade').
683
+ * @param logger - The logger.
684
+ */
685
+ async function writeVerificationJson(
686
+ deployer: L1Deployer,
687
+ outputDirectory: string,
688
+ chainId: number,
689
+ filenameSuffix: string = '',
690
+ logger: Logger,
691
+ ): Promise<void> {
692
+ try {
693
+ const date = new Date();
694
+ const formattedDate = date.toISOString().slice(2, 19).replace(/[-T:]/g, '');
695
+ // Ensure the verification output directory exists
696
+ await mkdir(outputDirectory, { recursive: true });
697
+ const suffix = filenameSuffix ? `-${filenameSuffix}` : '';
698
+ const verificationOutputPath = `${outputDirectory}/l1-verify${suffix}-${chainId}-${formattedDate.slice(0, 6)}-${formattedDate.slice(6)}.json`;
699
+ const networkName = getActiveNetworkName();
700
+ const verificationData = {
701
+ chainId: chainId,
702
+ network: networkName,
703
+ records: deployer.verificationRecords,
704
+ };
705
+ await writeFile(verificationOutputPath, JSON.stringify(verificationData, null, 2));
706
+ logger.info(`Wrote L1 verification data to ${verificationOutputPath}`);
707
+ } catch (e) {
708
+ logger.warn(`Failed to write L1 verification data file: ${String(e)}`);
709
+ }
710
+ }
711
+
419
712
  /**
420
713
  * Deploys a new rollup, using the existing canonical version to derive certain values (addresses of assets etc).
421
714
  * @param clients - The L1 clients.
@@ -423,6 +716,7 @@ const getZkPassportScopes = (args: DeployL1ContractsArgs): [string, string] => {
423
716
  * @param registryAddress - The address of the registry.
424
717
  * @param logger - The logger.
425
718
  * @param txUtilsConfig - The L1 tx utils config.
719
+ * @param createVerificationJson - Optional path to write verification data for forge verify.
426
720
  */
427
721
  export const deployRollupForUpgrade = async (
428
722
  extendedClient: ExtendedViemWalletClient,
@@ -433,6 +727,7 @@ export const deployRollupForUpgrade = async (
433
727
  registryAddress: EthAddress,
434
728
  logger: Logger,
435
729
  txUtilsConfig: L1TxUtilsConfig,
730
+ createVerificationJson: string | false = false,
436
731
  ) => {
437
732
  const deployer = new L1Deployer(
438
733
  extendedClient,
@@ -441,6 +736,7 @@ export const deployRollupForUpgrade = async (
441
736
  args.acceleratedTestDeployments,
442
737
  logger,
443
738
  txUtilsConfig,
739
+ !!createVerificationJson,
444
740
  );
445
741
 
446
742
  const addresses = await RegistryContract.collectAddresses(extendedClient, registryAddress, 'canonical');
@@ -449,11 +745,17 @@ export const deployRollupForUpgrade = async (
449
745
 
450
746
  await deployer.waitForDeployments();
451
747
 
748
+ // Write verification data (constructor args + linked libraries) to file for later forge verify
749
+ if (createVerificationJson) {
750
+ await generateRollupVerificationRecords(rollup, deployer, args, addresses, extendedClient, logger);
751
+ await writeVerificationJson(deployer, createVerificationJson, extendedClient.chain.id, 'upgrade', logger);
752
+ }
753
+
452
754
  return { rollup, slashFactoryAddress };
453
755
  };
454
756
 
455
757
  export const deploySlashFactory = async (deployer: L1Deployer, rollupAddress: Hex, logger: Logger) => {
456
- const slashFactoryAddress = await deployer.deploy(SlashFactoryArtifact, [rollupAddress]);
758
+ const slashFactoryAddress = (await deployer.deploy(SlashFactoryArtifact, [rollupAddress])).address;
457
759
  logger.verbose(`Deployed SlashFactory at ${slashFactoryAddress}`);
458
760
  return slashFactoryAddress;
459
761
  };
@@ -462,10 +764,12 @@ export const deployUpgradePayload = async (
462
764
  deployer: L1Deployer,
463
765
  addresses: Pick<L1ContractAddresses, 'registryAddress' | 'rollupAddress'>,
464
766
  ) => {
465
- const payloadAddress = await deployer.deploy(RegisterNewRollupVersionPayloadArtifact, [
466
- addresses.registryAddress.toString(),
467
- addresses.rollupAddress.toString(),
468
- ]);
767
+ const payloadAddress = (
768
+ await deployer.deploy(RegisterNewRollupVersionPayloadArtifact, [
769
+ addresses.registryAddress.toString(),
770
+ addresses.rollupAddress.toString(),
771
+ ])
772
+ ).address;
469
773
 
470
774
  return payloadAddress;
471
775
  };
@@ -509,6 +813,7 @@ export const deployRollup = async (
509
813
  if (!addresses.gseAddress) {
510
814
  throw new Error('GSE address is required when deploying');
511
815
  }
816
+ const networkName = getActiveNetworkName();
512
817
 
513
818
  logger.info(`Deploying rollup using network configuration: ${networkName}`);
514
819
 
@@ -517,10 +822,10 @@ export const deployRollup = async (
517
822
  let epochProofVerifier = EthAddress.ZERO;
518
823
 
519
824
  if (args.realVerifier) {
520
- epochProofVerifier = await deployer.deploy(l1ArtifactsVerifiers.honkVerifier);
825
+ epochProofVerifier = (await deployer.deploy(l1ArtifactsVerifiers.honkVerifier)).address;
521
826
  logger.verbose(`Rollup will use the real verifier at ${epochProofVerifier}`);
522
827
  } else {
523
- epochProofVerifier = await deployer.deploy(mockVerifiers.mockVerifier);
828
+ epochProofVerifier = (await deployer.deploy(mockVerifiers.mockVerifier)).address;
524
829
  logger.verbose(`Rollup will use the mock verifier at ${epochProofVerifier}`);
525
830
  }
526
831
 
@@ -533,6 +838,7 @@ export const deployRollup = async (
533
838
  aztecSlotDuration: BigInt(args.aztecSlotDuration),
534
839
  aztecEpochDuration: BigInt(args.aztecEpochDuration),
535
840
  targetCommitteeSize: BigInt(args.aztecTargetCommitteeSize),
841
+ lagInEpochs: BigInt(args.lagInEpochs),
536
842
  aztecProofSubmissionEpochs: BigInt(args.aztecProofSubmissionEpochs),
537
843
  slashingQuorum: BigInt(args.slashingQuorum ?? (args.slashingRoundSizeInEpochs * args.aztecEpochDuration) / 2 + 1),
538
844
  slashingRoundSize: BigInt(args.slashingRoundSizeInEpochs * args.aztecEpochDuration),
@@ -543,17 +849,20 @@ export const deployRollup = async (
543
849
  provingCostPerMana: args.provingCostPerMana,
544
850
  rewardConfig: rewardConfig,
545
851
  version: 0,
546
- rewardBoostConfig: getRewardBoostConfig(networkName),
852
+ rewardBoostConfig: getRewardBoostConfig(),
547
853
  stakingQueueConfig: getEntryQueueConfig(networkName),
548
854
  exitDelaySeconds: BigInt(args.exitDelaySeconds),
549
855
  slasherFlavor: slasherFlavorToSolidityEnum(args.slasherFlavor),
550
856
  slashingOffsetInRounds: BigInt(args.slashingOffsetInRounds),
551
857
  slashAmounts: [args.slashAmountSmall, args.slashAmountMedium, args.slashAmountLarge],
858
+ localEjectionThreshold: args.localEjectionThreshold,
859
+ slashingDisableDuration: BigInt(args.slashingDisableDuration ?? 0n),
860
+ earliestRewardsClaimableTimestamp: 0n,
552
861
  };
553
862
 
554
863
  const genesisStateArgs = {
555
864
  vkTreeRoot: args.vkTreeRoot.toString(),
556
- protocolContractTreeRoot: args.protocolContractTreeRoot.toString(),
865
+ protocolContractsHash: args.protocolContractsHash.toString(),
557
866
  genesisArchiveRoot: args.genesisArchiveRoot.toString(),
558
867
  };
559
868
 
@@ -579,8 +888,10 @@ export const deployRollup = async (
579
888
  rollupConfigArgs,
580
889
  ] as const;
581
890
 
582
- const rollupAddress = await deployer.deploy(RollupArtifact, rollupArgs, { gasLimit: 15_000_000n });
583
- logger.verbose(`Deployed Rollup at ${rollupAddress}`, rollupConfigArgs);
891
+ const { address: rollupAddress, existed: rollupExisted } = await deployer.deploy(RollupArtifact, rollupArgs, {
892
+ gasLimit: 15_000_000n,
893
+ });
894
+ logger.verbose(`Deployed Rollup at ${rollupAddress}, already existed: ${rollupExisted}`, rollupConfigArgs);
584
895
 
585
896
  const rollupContract = new RollupContract(extendedClient, rollupAddress);
586
897
 
@@ -588,24 +899,29 @@ export const deployRollup = async (
588
899
  logger.verbose(`All core contracts have been deployed`);
589
900
 
590
901
  if (args.feeJuicePortalInitialBalance && args.feeJuicePortalInitialBalance > 0n) {
591
- const feeJuicePortalAddress = await rollupContract.getFeeJuicePortal();
902
+ // Skip funding when using an external token, as we likely don't have mint permissions
903
+ if (!('existingTokenAddress' in args) || !args.existingTokenAddress) {
904
+ const feeJuicePortalAddress = await rollupContract.getFeeJuicePortal();
592
905
 
593
- // In fast mode, use the L1TxUtils to send transactions with nonce management
594
- const { txHash: mintTxHash } = await deployer.sendTransaction({
595
- to: addresses.feeJuiceAddress.toString(),
596
- data: encodeFunctionData({
597
- abi: FeeAssetArtifact.contractAbi,
598
- functionName: 'mint',
599
- args: [feeJuicePortalAddress.toString(), args.feeJuicePortalInitialBalance],
600
- }),
601
- });
602
- logger.verbose(
603
- `Funding fee juice portal with ${args.feeJuicePortalInitialBalance} fee juice in ${mintTxHash} (accelerated test deployments)`,
604
- );
605
- txHashes.push(mintTxHash);
906
+ // In fast mode, use the L1TxUtils to send transactions with nonce management
907
+ const { txHash: mintTxHash } = await deployer.sendTransaction({
908
+ to: addresses.feeJuiceAddress.toString(),
909
+ data: encodeFunctionData({
910
+ abi: FeeAssetArtifact.contractAbi,
911
+ functionName: 'mint',
912
+ args: [feeJuicePortalAddress.toString(), args.feeJuicePortalInitialBalance],
913
+ }),
914
+ });
915
+ logger.verbose(
916
+ `Funding fee juice portal with ${args.feeJuicePortalInitialBalance} fee juice in ${mintTxHash} (accelerated test deployments)`,
917
+ );
918
+ txHashes.push(mintTxHash);
919
+ } else {
920
+ logger.verbose('Skipping fee juice portal funding due to external token usage');
921
+ }
606
922
  }
607
923
 
608
- const slashFactoryAddress = await deployer.deploy(SlashFactoryArtifact, [rollupAddress.toString()]);
924
+ const slashFactoryAddress = (await deployer.deploy(SlashFactoryArtifact, [rollupAddress.toString()])).address;
609
925
  logger.verbose(`Deployed SlashFactory at ${slashFactoryAddress}`);
610
926
 
611
927
  // We need to call a function on the registry to set the various contract addresses.
@@ -667,7 +983,17 @@ export const deployRollup = async (
667
983
  logger.verbose(`Not the owner of the gse, skipping rollup addition`);
668
984
  }
669
985
 
670
- if (args.initialValidators && (await gseContract.read.isRollupRegistered([rollupContract.address]))) {
986
+ const activeAttestorCount = await rollupContract.getActiveAttesterCount();
987
+ const queuedAttestorCount = await rollupContract.getEntryQueueLength();
988
+ logger.info(`Rollup has ${activeAttestorCount} active attestors and ${queuedAttestorCount} queued attestors`);
989
+
990
+ const shouldAddValidators = activeAttestorCount === 0n && queuedAttestorCount === 0n;
991
+
992
+ if (
993
+ args.initialValidators &&
994
+ shouldAddValidators &&
995
+ (await gseContract.read.isRollupRegistered([rollupContract.address]))
996
+ ) {
671
997
  await addMultipleValidators(
672
998
  extendedClient,
673
999
  deployer,
@@ -715,6 +1041,7 @@ export const handoverToGovernance = async (
715
1041
  governanceAddress: EthAddress,
716
1042
  logger: Logger,
717
1043
  acceleratedTestDeployments: boolean | undefined,
1044
+ useExternalToken: boolean = false,
718
1045
  ) => {
719
1046
  // We need to call a function on the registry to set the various contract addresses.
720
1047
  const registryContract = getContract({
@@ -780,7 +1107,10 @@ export const handoverToGovernance = async (
780
1107
  txHashes.push(transferOwnershipTxHash);
781
1108
  }
782
1109
 
783
- if (acceleratedTestDeployments || (await feeAsset.read.owner()) !== coinIssuerAddress.toString()) {
1110
+ if (
1111
+ !useExternalToken &&
1112
+ (acceleratedTestDeployments || (await feeAsset.read.owner()) !== coinIssuerAddress.toString())
1113
+ ) {
784
1114
  const { txHash } = await deployer.sendTransaction(
785
1115
  {
786
1116
  to: feeAssetAddress.toString(),
@@ -807,23 +1137,28 @@ export const handoverToGovernance = async (
807
1137
  );
808
1138
  logger.verbose(`Accept ownership of fee asset in ${acceptTokenOwnershipTxHash}`);
809
1139
  txHashes.push(acceptTokenOwnershipTxHash);
1140
+ } else if (useExternalToken) {
1141
+ logger.verbose('Skipping fee asset ownership transfer due to external token usage');
810
1142
  }
811
1143
 
1144
+ // Either deploy or at least predict the address of the date gated relayer
1145
+ const dateGatedRelayer = await deployer.deploy(DateGatedRelayerArtifact, [
1146
+ governanceAddress.toString(),
1147
+ 1798761600n, // 2027-01-01 00:00:00 UTC
1148
+ ]);
1149
+
812
1150
  // If the owner is not the Governance contract, transfer ownership to the Governance contract
813
- if (
814
- acceleratedTestDeployments ||
815
- (await coinIssuerContract.read.owner()) !== getAddress(governanceAddress.toString())
816
- ) {
1151
+ if (acceleratedTestDeployments || (await coinIssuerContract.read.owner()) === deployer.client.account.address) {
817
1152
  const { txHash: transferOwnershipTxHash } = await deployer.sendTransaction({
818
1153
  to: coinIssuerContract.address,
819
1154
  data: encodeFunctionData({
820
1155
  abi: CoinIssuerArtifact.contractAbi,
821
1156
  functionName: 'transferOwnership',
822
- args: [getAddress(governanceAddress.toString())],
1157
+ args: [getAddress(dateGatedRelayer.address.toString())],
823
1158
  }),
824
1159
  });
825
1160
  logger.verbose(
826
- `Transferring the ownership of the coin issuer contract at ${coinIssuerAddress} to the Governance ${governanceAddress} in tx ${transferOwnershipTxHash}`,
1161
+ `Transferring the ownership of the coin issuer contract at ${coinIssuerAddress} to the DateGatedRelayer ${dateGatedRelayer.address} in tx ${transferOwnershipTxHash}`,
827
1162
  );
828
1163
  txHashes.push(transferOwnershipTxHash);
829
1164
  }
@@ -831,6 +1166,8 @@ export const handoverToGovernance = async (
831
1166
  // Wait for all actions to be mined
832
1167
  await deployer.waitForDeployments();
833
1168
  await Promise.all(txHashes.map(txHash => extendedClient.waitForTransactionReceipt({ hash: txHash })));
1169
+
1170
+ return { dateGatedRelayerAddress: dateGatedRelayer.address };
834
1171
  };
835
1172
 
836
1173
  /*
@@ -877,100 +1214,112 @@ export const addMultipleValidators = async (
877
1214
  validators = enrichedValidators.filter(v => v.status === 0).map(v => v.operator);
878
1215
  }
879
1216
 
880
- if (validators.length > 0) {
881
- const gseContract = new GSEContract(extendedClient, gseAddress);
882
- const multiAdder = await deployer.deploy(MultiAdderArtifact, [rollupAddress, deployer.client.account.address]);
883
-
884
- const makeValidatorTuples = async (validator: Operator) => {
885
- const registrationTuple = await gseContract.makeRegistrationTuple(validator.bn254SecretKey.getValue());
886
- return {
887
- attester: getAddress(validator.attester.toString()),
888
- withdrawer: getAddress(validator.withdrawer.toString()),
889
- ...registrationTuple,
890
- };
1217
+ if (validators.length === 0) {
1218
+ logger.warn('No validators to add. Skipping.');
1219
+ return;
1220
+ }
1221
+
1222
+ const gseContract = new GSEContract(extendedClient, gseAddress);
1223
+ const multiAdder = (await deployer.deploy(MultiAdderArtifact, [rollupAddress, deployer.client.account.address]))
1224
+ .address;
1225
+
1226
+ const makeValidatorTuples = async (validator: Operator) => {
1227
+ const registrationTuple = await gseContract.makeRegistrationTuple(validator.bn254SecretKey.getValue());
1228
+ return {
1229
+ attester: getAddress(validator.attester.toString()),
1230
+ withdrawer: getAddress(validator.withdrawer.toString()),
1231
+ ...registrationTuple,
891
1232
  };
1233
+ };
892
1234
 
893
- const validatorsTuples = await Promise.all(validators.map(makeValidatorTuples));
1235
+ const validatorsTuples = await Promise.all(validators.map(makeValidatorTuples));
894
1236
 
895
- // Mint tokens, approve them, use cheat code to initialize validator set without setting up the epoch.
896
- const stakeNeeded = activationThreshold * BigInt(validators.length);
1237
+ // Mint tokens, approve them, use cheat code to initialize validator set without setting up the epoch.
1238
+ const stakeNeeded = activationThreshold * BigInt(validators.length);
897
1239
 
898
- await deployer.l1TxUtils.sendAndMonitorTransaction({
899
- to: stakingAssetAddress,
900
- data: encodeFunctionData({
901
- abi: StakingAssetArtifact.contractAbi,
902
- functionName: 'mint',
903
- args: [multiAdder.toString(), stakeNeeded],
904
- }),
905
- });
1240
+ await deployer.l1TxUtils.sendAndMonitorTransaction({
1241
+ to: stakingAssetAddress,
1242
+ data: encodeFunctionData({
1243
+ abi: StakingAssetArtifact.contractAbi,
1244
+ functionName: 'mint',
1245
+ args: [multiAdder.toString(), stakeNeeded],
1246
+ }),
1247
+ });
906
1248
 
907
- const entryQueueLengthBefore = await rollup.getEntryQueueLength();
908
- const validatorCountBefore = await rollup.getActiveAttesterCount();
909
-
910
- logger.info(`Adding ${validators.length} validators to the rollup`);
911
-
912
- // Adding to the queue and flushing need to be done in two transactions
913
- // if we are adding many validators.
914
- if (validatorsTuples.length > 10) {
915
- await deployer.l1TxUtils.sendAndMonitorTransaction(
916
- {
917
- to: multiAdder.toString(),
918
- data: encodeFunctionData({
919
- abi: MultiAdderArtifact.contractAbi,
920
- functionName: 'addValidators',
921
- args: [validatorsTuples, true],
922
- }),
923
- },
924
- {
925
- gasLimit: 40_000_000n,
926
- },
927
- );
1249
+ const entryQueueLengthBefore = await rollup.getEntryQueueLength();
1250
+ const validatorCountBefore = await rollup.getActiveAttesterCount();
928
1251
 
929
- await deployer.l1TxUtils.sendAndMonitorTransaction(
930
- {
931
- to: rollupAddress,
932
- data: encodeFunctionData({
933
- abi: RollupArtifact.contractAbi,
934
- functionName: 'flushEntryQueue',
935
- args: [],
936
- }),
937
- },
938
- {
939
- gasLimit: 40_000_000n,
940
- },
941
- );
942
- } else {
943
- await deployer.l1TxUtils.sendAndMonitorTransaction(
944
- {
945
- to: multiAdder.toString(),
946
- data: encodeFunctionData({
947
- abi: MultiAdderArtifact.contractAbi,
948
- functionName: 'addValidators',
949
- args: [validatorsTuples, false],
950
- }),
951
- },
952
- {
953
- gasLimit: 45_000_000n,
954
- },
955
- );
956
- }
1252
+ logger.info(`Adding ${validators.length} validators to the rollup`);
957
1253
 
958
- const entryQueueLengthAfter = await rollup.getEntryQueueLength();
959
- const validatorCountAfter = await rollup.getActiveAttesterCount();
1254
+ const chunkSize = 16;
960
1255
 
961
- if (
962
- entryQueueLengthAfter + validatorCountAfter <
963
- entryQueueLengthBefore + validatorCountBefore + BigInt(validators.length)
964
- ) {
965
- throw new Error(
966
- `Failed to add ${validators.length} validators. Active validators: ${validatorCountBefore} -> ${validatorCountAfter}. Queue: ${entryQueueLengthBefore} -> ${entryQueueLengthAfter}`,
967
- );
1256
+ // We will add `chunkSize` validators to the queue until we have covered all of our validators.
1257
+ // The `chunkSize` needs to be small enough to fit inside a single tx, therefore 16.
1258
+ for (const c of chunk(validatorsTuples, chunkSize)) {
1259
+ await deployer.l1TxUtils.sendAndMonitorTransaction(
1260
+ {
1261
+ to: multiAdder.toString(),
1262
+ data: encodeFunctionData({
1263
+ abi: MultiAdderArtifact.contractAbi,
1264
+ functionName: 'addValidators',
1265
+ args: [c, BigInt(0)],
1266
+ }),
1267
+ },
1268
+ {
1269
+ gasLimit: 16_000_000n,
1270
+ },
1271
+ );
1272
+ }
1273
+
1274
+ // After adding to the queue, we will now try to flush from it.
1275
+ // We are explicitly doing this as a second step instead of as part of adding to benefit
1276
+ // from the accounting used to speed the process up.
1277
+ // As the queue computes the amount of possible flushes in an epoch when told to flush,
1278
+ // waiting until we have added all we want allows us to benefit in the case were we added
1279
+ // enough to pass the bootstrap set size without needing to wait another epoch.
1280
+ // This is useful when we are testing as it speeds up the tests slightly.
1281
+ while (true) {
1282
+ // If the queue is empty, we can break
1283
+ if ((await rollup.getEntryQueueLength()) == 0n) {
1284
+ break;
968
1285
  }
969
1286
 
970
- logger.info(
971
- `Added ${validators.length} validators. Active validators: ${validatorCountBefore} -> ${validatorCountAfter}. Queue: ${entryQueueLengthBefore} -> ${entryQueueLengthAfter}`,
1287
+ // If there are no available validator flushes, no need to even try
1288
+ if ((await rollup.getAvailableValidatorFlushes()) == 0n) {
1289
+ break;
1290
+ }
1291
+
1292
+ // Note that we are flushing at most `chunkSize` at each call
1293
+ await deployer.l1TxUtils.sendAndMonitorTransaction(
1294
+ {
1295
+ to: rollup.address,
1296
+ data: encodeFunctionData({
1297
+ abi: RollupArtifact.contractAbi,
1298
+ functionName: 'flushEntryQueue',
1299
+ args: [BigInt(chunkSize)],
1300
+ }),
1301
+ },
1302
+ {
1303
+ gasLimit: 16_000_000n,
1304
+ },
972
1305
  );
973
1306
  }
1307
+
1308
+ const entryQueueLengthAfter = await rollup.getEntryQueueLength();
1309
+ const validatorCountAfter = await rollup.getActiveAttesterCount();
1310
+
1311
+ if (
1312
+ entryQueueLengthAfter + validatorCountAfter <
1313
+ entryQueueLengthBefore + validatorCountBefore + BigInt(validators.length)
1314
+ ) {
1315
+ throw new Error(
1316
+ `Failed to add ${validators.length} validators. Active validators: ${validatorCountBefore} -> ${validatorCountAfter}. Queue: ${entryQueueLengthBefore} -> ${entryQueueLengthAfter}. A likely issue is the bootstrap size.`,
1317
+ );
1318
+ }
1319
+
1320
+ logger.info(
1321
+ `Added ${validators.length} validators. Active validators: ${validatorCountBefore} -> ${validatorCountAfter}. Queue: ${entryQueueLengthBefore} -> ${entryQueueLengthAfter}`,
1322
+ );
974
1323
  }
975
1324
  };
976
1325
 
@@ -993,11 +1342,13 @@ export const cheat_initializeFeeAssetHandler = async (
993
1342
  feeAssetHandlerAddress: EthAddress;
994
1343
  txHash: Hex;
995
1344
  }> => {
996
- const feeAssetHandlerAddress = await deployer.deploy(FeeAssetHandlerArtifact, [
997
- extendedClient.account.address,
998
- feeAssetAddress.toString(),
999
- BigInt(1e18),
1000
- ]);
1345
+ const feeAssetHandlerAddress = (
1346
+ await deployer.deploy(FeeAssetHandlerArtifact, [
1347
+ extendedClient.account.address,
1348
+ feeAssetAddress.toString(),
1349
+ BigInt(1e18),
1350
+ ])
1351
+ ).address;
1001
1352
  logger.verbose(`Deployed FeeAssetHandler at ${feeAssetHandlerAddress}`);
1002
1353
 
1003
1354
  const { txHash } = await deployer.sendTransaction({
@@ -1033,6 +1384,13 @@ export const deployL1Contracts = async (
1033
1384
  logger.info(`Deploying L1 contracts with config: ${jsonStringify(args)}`);
1034
1385
  validateConfig(args);
1035
1386
 
1387
+ if (args.initialValidators && args.initialValidators.length > 0 && args.existingTokenAddress) {
1388
+ throw new Error(
1389
+ 'Cannot deploy with both initialValidators and existingTokenAddress. ' +
1390
+ 'Initial validator funding requires minting tokens, which is not possible with an external token.',
1391
+ );
1392
+ }
1393
+
1036
1394
  const l1Client = createExtendedL1Client(rpcUrls, account, chain);
1037
1395
 
1038
1396
  // Deploy multicall3 if it does not exist in this network
@@ -1102,7 +1460,7 @@ export const deployL1Contracts = async (
1102
1460
  await deployer.waitForDeployments();
1103
1461
 
1104
1462
  // Now that the rollup has been deployed and added to the registry, transfer ownership to governance
1105
- await handoverToGovernance(
1463
+ const { dateGatedRelayerAddress } = await handoverToGovernance(
1106
1464
  l1Client,
1107
1465
  deployer,
1108
1466
  registryAddress,
@@ -1112,6 +1470,7 @@ export const deployL1Contracts = async (
1112
1470
  governanceAddress,
1113
1471
  logger,
1114
1472
  args.acceleratedTestDeployments,
1473
+ !!args.existingTokenAddress,
1115
1474
  );
1116
1475
 
1117
1476
  logger.info(`Handing over to governance complete`);
@@ -1123,143 +1482,8 @@ export const deployL1Contracts = async (
1123
1482
 
1124
1483
  // Write verification data (constructor args + linked libraries) to file for later forge verify
1125
1484
  if (createVerificationJson) {
1126
- try {
1127
- // Add Inbox / Outbox verification records (constructor args are created inside RollupCore)
1128
- const rollupAddr = l1Contracts.rollupAddress.toString();
1129
- const inboxAddr = l1Contracts.inboxAddress.toString();
1130
- const outboxAddr = l1Contracts.outboxAddress.toString();
1131
- const feeAsset = l1Contracts.feeJuiceAddress.toString();
1132
- const version = await rollup.getVersion();
1133
-
1134
- const inboxCtor = encodeAbiParameters(
1135
- [{ type: 'address' }, { type: 'address' }, { type: 'uint256' }, { type: 'uint256' }],
1136
- [rollupAddr, feeAsset, version, BigInt(L1_TO_L2_MSG_SUBTREE_HEIGHT)],
1137
- );
1138
-
1139
- const outboxCtor = encodeAbiParameters([{ type: 'address' }, { type: 'uint256' }], [rollupAddr, version]);
1140
-
1141
- deployer.verificationRecords.push(
1142
- { name: 'Inbox', address: inboxAddr, constructorArgsHex: inboxCtor, libraries: [] },
1143
- { name: 'Outbox', address: outboxAddr, constructorArgsHex: outboxCtor, libraries: [] },
1144
- );
1145
-
1146
- // Include Slasher and SlashingProposer (if deployed) in verification data
1147
- try {
1148
- const slasherAddrHex = await rollup.getSlasher();
1149
- const slasherAddr = EthAddress.fromString(slasherAddrHex);
1150
- if (!slasherAddr.isZero()) {
1151
- // Slasher constructor: (address _vetoer, address _governance)
1152
- const slasherCtor = encodeAbiParameters(
1153
- [{ type: 'address' }, { type: 'address' }],
1154
- [args.slashingVetoer.toString(), l1Client.account.address],
1155
- );
1156
- deployer.verificationRecords.push({
1157
- name: 'Slasher',
1158
- address: slasherAddr.toString(),
1159
- constructorArgsHex: slasherCtor,
1160
- libraries: [],
1161
- });
1162
-
1163
- // Proposer address is stored in Slasher.PROPOSER()
1164
- const proposerAddr = (await rollup.getSlashingProposerAddress()).toString();
1165
-
1166
- // Compute constructor args matching deployment path in RollupCore
1167
- const computedRoundSize = BigInt(args.slashingRoundSizeInEpochs * args.aztecEpochDuration);
1168
- const computedQuorum = BigInt(
1169
- args.slashingQuorum ?? (args.slashingRoundSizeInEpochs * args.aztecEpochDuration) / 2 + 1,
1170
- );
1171
- const lifetimeInRounds = BigInt(args.slashingLifetimeInRounds);
1172
- const executionDelayInRounds = BigInt(args.slashingExecutionDelayInRounds);
1173
-
1174
- if (args.slasherFlavor === 'tally') {
1175
- const slashAmounts: readonly [bigint, bigint, bigint] = [
1176
- args.slashAmountSmall,
1177
- args.slashAmountMedium,
1178
- args.slashAmountLarge,
1179
- ];
1180
- const committeeSize = BigInt(args.aztecTargetCommitteeSize);
1181
- const epochDuration = BigInt(args.aztecEpochDuration);
1182
- const slashOffsetInRounds = BigInt(args.slashingOffsetInRounds);
1183
-
1184
- const proposerCtor = encodeAbiParameters(
1185
- [
1186
- { type: 'address' },
1187
- { type: 'address' },
1188
- { type: 'uint256' },
1189
- { type: 'uint256' },
1190
- { type: 'uint256' },
1191
- { type: 'uint256' },
1192
- { type: 'uint256[3]' },
1193
- { type: 'uint256' },
1194
- { type: 'uint256' },
1195
- { type: 'uint256' },
1196
- ],
1197
- [
1198
- rollup.address,
1199
- slasherAddr.toString(),
1200
- computedQuorum,
1201
- computedRoundSize,
1202
- lifetimeInRounds,
1203
- executionDelayInRounds,
1204
- slashAmounts,
1205
- committeeSize,
1206
- epochDuration,
1207
- slashOffsetInRounds,
1208
- ],
1209
- );
1210
-
1211
- deployer.verificationRecords.push({
1212
- name: 'TallySlashingProposer',
1213
- address: proposerAddr,
1214
- constructorArgsHex: proposerCtor,
1215
- libraries: [],
1216
- });
1217
- } else if (args.slasherFlavor === 'empire') {
1218
- const proposerCtor = encodeAbiParameters(
1219
- [
1220
- { type: 'address' },
1221
- { type: 'address' },
1222
- { type: 'uint256' },
1223
- { type: 'uint256' },
1224
- { type: 'uint256' },
1225
- { type: 'uint256' },
1226
- ],
1227
- [
1228
- rollup.address,
1229
- slasherAddr.toString(),
1230
- computedQuorum,
1231
- computedRoundSize,
1232
- lifetimeInRounds,
1233
- executionDelayInRounds,
1234
- ],
1235
- );
1236
-
1237
- deployer.verificationRecords.push({
1238
- name: 'EmpireSlashingProposer',
1239
- address: proposerAddr,
1240
- constructorArgsHex: proposerCtor,
1241
- libraries: [],
1242
- });
1243
- }
1244
- }
1245
- } catch (e) {
1246
- logger.warn(`Failed to add Slasher/Proposer verification records: ${String(e)}`);
1247
- }
1248
- const date = new Date();
1249
- const formattedDate = date.toISOString().slice(2, 19).replace(/[-T:]/g, '');
1250
- // Ensure the verification output directory exists
1251
- await mkdir(createVerificationJson, { recursive: true });
1252
- const verificationOutputPath = `${createVerificationJson}/l1-verify-${chain.id}-${formattedDate.slice(0, 6)}-${formattedDate.slice(6)}.json`;
1253
- const verificationData = {
1254
- chainId: chain.id,
1255
- network: networkName,
1256
- records: deployer.verificationRecords,
1257
- };
1258
- await writeFile(verificationOutputPath, JSON.stringify(verificationData, null, 2));
1259
- logger.info(`Wrote L1 verification data to ${verificationOutputPath}`);
1260
- } catch (e) {
1261
- logger.warn(`Failed to write L1 verification data file: ${String(e)}`);
1262
- }
1485
+ await generateRollupVerificationRecords(rollup, deployer, args, l1Contracts, l1Client, logger);
1486
+ await writeVerificationJson(deployer, createVerificationJson, chain.id, '', logger);
1263
1487
  }
1264
1488
 
1265
1489
  if (isAnvilTestChain(chain.id)) {
@@ -1295,6 +1519,7 @@ export const deployL1Contracts = async (
1295
1519
  stakingAssetHandlerAddress,
1296
1520
  zkPassportVerifierAddress,
1297
1521
  coinIssuerAddress,
1522
+ dateGatedRelayerAddress,
1298
1523
  },
1299
1524
  };
1300
1525
  };
@@ -1317,10 +1542,8 @@ export class L1Deployer {
1317
1542
  this.salt = maybeSalt ? padHex(numberToHex(maybeSalt), { size: 32 }) : undefined;
1318
1543
  this.l1TxUtils = createL1TxUtilsFromViemWallet(
1319
1544
  this.client,
1320
- this.logger,
1321
- dateProvider,
1322
- this.txUtilsConfig,
1323
- this.acceleratedTestDeployments,
1545
+ { logger: this.logger, dateProvider },
1546
+ { ...this.txUtilsConfig, debugMaxGasLimit: acceleratedTestDeployments },
1324
1547
  );
1325
1548
  }
1326
1549
 
@@ -1328,10 +1551,10 @@ export class L1Deployer {
1328
1551
  params: ContractArtifacts<TAbi>,
1329
1552
  args?: ContractConstructorArgs<TAbi>,
1330
1553
  opts: { gasLimit?: bigint } = {},
1331
- ): Promise<EthAddress> {
1554
+ ): Promise<{ address: EthAddress; existed: boolean }> {
1332
1555
  this.logger.debug(`Deploying ${params.name} contract`, { args });
1333
1556
  try {
1334
- const { txHash, address, deployedLibraries } = await deployL1Contract(
1557
+ const { txHash, address, deployedLibraries, existed } = await deployL1Contract(
1335
1558
  this.client,
1336
1559
  params.contractAbi,
1337
1560
  params.contractBytecode,
@@ -1369,7 +1592,10 @@ export class L1Deployer {
1369
1592
  libraries: deployedLibraries ?? [],
1370
1593
  });
1371
1594
  }
1372
- return address;
1595
+ return {
1596
+ address,
1597
+ existed,
1598
+ };
1373
1599
  } catch (error) {
1374
1600
  throw new Error(`Failed to deploy ${params.name}`, { cause: formatViemError(error) });
1375
1601
  }
@@ -1397,13 +1623,16 @@ export class L1Deployer {
1397
1623
 
1398
1624
  sendTransaction(
1399
1625
  tx: L1TxRequest,
1400
- options?: L1GasConfig,
1626
+ options?: L1TxConfig,
1401
1627
  ): Promise<{ txHash: Hex; gasLimit: bigint; gasPrice: GasPrice }> {
1402
- return this.l1TxUtils.sendTransaction(tx, options);
1628
+ return this.l1TxUtils.sendTransaction(tx, options).then(({ txHash, state }) => ({
1629
+ txHash,
1630
+ gasLimit: state.gasLimit,
1631
+ gasPrice: state.gasPrice,
1632
+ }));
1403
1633
  }
1404
1634
  }
1405
1635
 
1406
- // docs:start:deployL1Contract
1407
1636
  /**
1408
1637
  * Helper function to deploy ETH contracts.
1409
1638
  * @param walletClient - A viem WalletClient.
@@ -1427,7 +1656,12 @@ export async function deployL1Contract(
1427
1656
  gasLimit?: bigint;
1428
1657
  acceleratedTestDeployments?: boolean;
1429
1658
  } = {},
1430
- ): Promise<{ address: EthAddress; txHash: Hex | undefined; deployedLibraries?: VerificationLibraryEntry[] }> {
1659
+ ): Promise<{
1660
+ address: EthAddress;
1661
+ txHash: Hex | undefined;
1662
+ deployedLibraries?: VerificationLibraryEntry[];
1663
+ existed: boolean;
1664
+ }> {
1431
1665
  let txHash: Hex | undefined = undefined;
1432
1666
  let resultingAddress: Hex | null | undefined = undefined;
1433
1667
  const deployedLibraries: VerificationLibraryEntry[] = [];
@@ -1437,7 +1671,11 @@ export async function deployL1Contract(
1437
1671
 
1438
1672
  if (!l1TxUtils) {
1439
1673
  const config = getL1TxUtilsConfigEnvVars();
1440
- l1TxUtils = createL1TxUtilsFromViemWallet(extendedClient, logger, undefined, config, acceleratedTestDeployments);
1674
+ l1TxUtils = createL1TxUtilsFromViemWallet(
1675
+ extendedClient,
1676
+ { logger },
1677
+ { ...config, debugMaxGasLimit: acceleratedTestDeployments },
1678
+ );
1441
1679
  }
1442
1680
 
1443
1681
  if (libraries) {
@@ -1529,6 +1767,8 @@ export async function deployL1Contract(
1529
1767
  }
1530
1768
  }
1531
1769
 
1770
+ let existed = false;
1771
+
1532
1772
  if (saltFromOpts) {
1533
1773
  logger?.info(`Deploying contract with salt ${saltFromOpts}`);
1534
1774
  const { address, paddedSalt: salt, calldata } = getExpectedAddress(abi, bytecode, args, saltFromOpts);
@@ -1536,10 +1776,10 @@ export async function deployL1Contract(
1536
1776
  const existing = await extendedClient.getCode({ address: resultingAddress });
1537
1777
  if (existing === undefined || existing === '0x') {
1538
1778
  try {
1539
- await l1TxUtils.simulate({ to: DEPLOYER_ADDRESS, data: concatHex([salt, calldata]) }, { gasLimit });
1779
+ await l1TxUtils.simulate({ to: DEPLOYER_ADDRESS, data: concatHex([salt, calldata]), gas: gasLimit });
1540
1780
  } catch (err) {
1541
1781
  logger?.error(`Failed to simulate deployment tx using universal deployer`, err);
1542
- await l1TxUtils.simulate({ to: null, data: encodeDeployData({ abi, bytecode, args }) });
1782
+ await l1TxUtils.simulate({ to: null, data: encodeDeployData({ abi, bytecode, args }), gas: gasLimit });
1543
1783
  }
1544
1784
  const res = await l1TxUtils.sendTransaction(
1545
1785
  { to: DEPLOYER_ADDRESS, data: concatHex([salt, calldata]) },
@@ -1550,13 +1790,17 @@ export async function deployL1Contract(
1550
1790
  logger?.verbose(`Deployed contract with salt ${salt} to address ${resultingAddress} in tx ${txHash}.`);
1551
1791
  } else {
1552
1792
  logger?.verbose(`Skipping existing deployment of contract with salt ${salt} to address ${resultingAddress}`);
1793
+ existed = true;
1553
1794
  }
1554
1795
  } else {
1555
1796
  const deployData = encodeDeployData({ abi, bytecode, args });
1556
- const { receipt } = await l1TxUtils.sendAndMonitorTransaction({
1557
- to: null,
1558
- data: deployData,
1559
- });
1797
+ const { receipt } = await l1TxUtils.sendAndMonitorTransaction(
1798
+ {
1799
+ to: null,
1800
+ data: deployData,
1801
+ },
1802
+ { gasLimit },
1803
+ );
1560
1804
 
1561
1805
  txHash = receipt.transactionHash;
1562
1806
  resultingAddress = receipt.contractAddress;
@@ -1569,7 +1813,7 @@ export async function deployL1Contract(
1569
1813
  }
1570
1814
  }
1571
1815
 
1572
- return { address: EthAddress.fromString(resultingAddress!), txHash, deployedLibraries };
1816
+ return { address: EthAddress.fromString(resultingAddress!), txHash, deployedLibraries, existed };
1573
1817
  }
1574
1818
 
1575
1819
  export function getExpectedAddress(
@@ -1592,5 +1836,3 @@ export function getExpectedAddress(
1592
1836
  calldata,
1593
1837
  };
1594
1838
  }
1595
-
1596
- // docs:end:deployL1Contract