@aztec/ethereum 3.0.0-nightly.20250911 → 3.0.0-nightly.20250912

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dest/utils.js CHANGED
@@ -118,34 +118,16 @@ function getNestedErrorData(error) {
118
118
  if (error instanceof Error) {
119
119
  return new FormattedViemError(error.message, error?.metaMessages);
120
120
  }
121
- // Extract the actual error message and highlight it for clarity
122
- let formattedRes = extractAndFormatRequestBody(error?.message || String(error));
123
- let errorDetail = '';
124
- // Look for specific details in known locations
125
- if (error) {
126
- // Check for details property which often has the most specific error message
127
- if (typeof error.details === 'string' && error.details) {
128
- errorDetail = error.details;
129
- } else if (typeof error.shortMessage === 'string' && error.shortMessage) {
130
- errorDetail = error.shortMessage;
131
- }
132
- }
133
- // If we found a specific error detail, format it clearly
134
- if (errorDetail) {
135
- // Look for key sections of the formatted result to replace with highlighted error
136
- let replaced = false;
137
- // Try to find the Details: section
138
- const detailsMatch = formattedRes.match(/Details: ([^\n]+)/);
139
- if (detailsMatch) {
140
- formattedRes = formattedRes.replace(detailsMatch[0], `Details: *${errorDetail}*`);
141
- replaced = true;
142
- }
143
- // If we didn't find a Details section, add the error at the beginning
144
- if (!replaced) {
145
- formattedRes = `Error: *${errorDetail}*\n\n${formattedRes}`;
146
- }
147
- }
148
- return new FormattedViemError(formattedRes.replace(/\\n/g, '\n'), error?.metaMessages);
121
+ const body = String(error);
122
+ const length = body.length;
123
+ // LogExplorer can only render up to 2500 characters in it's summary view. Try to keep the whole message below this number
124
+ // Limit the error to 2000 chacaters in order to allow code higher up to decorate this error with extra details (up to 500 characters)
125
+ if (length > 2000) {
126
+ const chunk = 950;
127
+ const truncated = length - 2 * chunk;
128
+ return new FormattedViemError(body.slice(0, chunk) + `...${truncated} characters truncated...` + body.slice(-1 * chunk));
129
+ }
130
+ return new FormattedViemError(body);
149
131
  }
150
132
  function stripAbis(obj) {
151
133
  if (!obj || typeof obj !== 'object') {
@@ -166,139 +148,6 @@ function stripAbis(obj) {
166
148
  }
167
149
  });
168
150
  }
169
- function extractAndFormatRequestBody(message) {
170
- // First check if message is extremely large and contains very large hex strings
171
- if (message.length > 50000) {
172
- message = replaceHexStrings(message, {
173
- minLength: 10000,
174
- truncateLength: 200
175
- });
176
- }
177
- // Add a specific check for RPC calls with large params
178
- if (message.includes('"method":"eth_sendRawTransaction"')) {
179
- message = replaceHexStrings(message, {
180
- pattern: /"params":\s*\[\s*"(0x[a-fA-F0-9]{1000,})"\s*\]/g,
181
- transform: (hex)=>`"params":["${truncateHex(hex, 200)}"]`
182
- });
183
- }
184
- // First handle Request body JSON
185
- const requestBodyRegex = /Request body: ({[\s\S]*?})\n/g;
186
- let result = message.replace(requestBodyRegex, (match, body)=>{
187
- return `Request body: ${formatRequestBody(body)}\n`;
188
- });
189
- // Then handle Arguments section
190
- const argsRegex = /((?:Request |Estimate Gas )?Arguments:[\s\S]*?(?=\n\n|$))/g;
191
- result = result.replace(argsRegex, (section)=>{
192
- const lines = section.split('\n');
193
- const processedLines = lines.map((line)=>{
194
- // Check if line contains a colon followed by content
195
- const colonIndex = line.indexOf(':');
196
- if (colonIndex !== -1) {
197
- const [prefix, content] = [
198
- line.slice(0, colonIndex + 1),
199
- line.slice(colonIndex + 1).trim()
200
- ];
201
- // If content contains a hex string, truncate it
202
- if (content.includes('0x')) {
203
- const processedContent = replaceHexStrings(content);
204
- return `${prefix} ${processedContent}`;
205
- }
206
- }
207
- return line;
208
- });
209
- return processedLines.join('\n');
210
- });
211
- // Finally, catch any remaining hex strings in the message
212
- result = replaceHexStrings(result);
213
- return result;
214
- }
215
- function truncateHex(hex, length = 100) {
216
- if (!hex || typeof hex !== 'string') {
217
- return hex;
218
- }
219
- if (!hex.startsWith('0x')) {
220
- return hex;
221
- }
222
- if (hex.length <= length * 2) {
223
- return hex;
224
- }
225
- // For extremely large hex strings, use more aggressive truncation
226
- if (hex.length > 10000) {
227
- return `${hex.slice(0, length)}...<${hex.length - length * 2} chars omitted>...${hex.slice(-length)}`;
228
- }
229
- return `${hex.slice(0, length)}...${hex.slice(-length)}`;
230
- }
231
- function replaceHexStrings(text, options = {}) {
232
- const { minLength = 10, maxLength = Infinity, truncateLength = 100, pattern, transform = (hex)=>truncateHex(hex, truncateLength) } = options;
233
- const hexRegex = pattern ?? new RegExp(`(0x[a-fA-F0-9]{${minLength},${maxLength}})`, 'g');
234
- return text.replace(hexRegex, (match)=>transform(match));
235
- }
236
- function formatRequestBody(body) {
237
- try {
238
- // Special handling for eth_sendRawTransaction
239
- if (body.includes('"method":"eth_sendRawTransaction"')) {
240
- try {
241
- const parsed = JSON.parse(body);
242
- if (parsed.params && Array.isArray(parsed.params) && parsed.params.length > 0) {
243
- // These are likely large transaction hex strings
244
- parsed.params = parsed.params.map((param)=>{
245
- if (typeof param === 'string' && param.startsWith('0x') && param.length > 1000) {
246
- return truncateHex(param, 200);
247
- }
248
- return param;
249
- });
250
- }
251
- return JSON.stringify(parsed, null, 2);
252
- } catch {
253
- // If specific parsing fails, fall back to regex-based truncation
254
- return replaceHexStrings(body, {
255
- pattern: /"params":\s*\[\s*"(0x[a-fA-F0-9]{1000,})"\s*\]/g,
256
- transform: (hex)=>`"params":["${truncateHex(hex, 200)}"]`
257
- });
258
- }
259
- }
260
- // For extremely large request bodies, use simple truncation instead of parsing
261
- if (body.length > 50000) {
262
- const jsonStart = body.indexOf('{');
263
- const jsonEnd = body.lastIndexOf('}');
264
- if (jsonStart >= 0 && jsonEnd > jsonStart) {
265
- return replaceHexStrings(body, {
266
- minLength: 10000,
267
- truncateLength: 200
268
- });
269
- }
270
- }
271
- const parsed = JSON.parse(body);
272
- // Process the entire request body
273
- const processed = processParams(parsed);
274
- return JSON.stringify(processed, null, 2);
275
- } catch {
276
- // If JSON parsing fails, do a simple truncation of any large hex strings
277
- return replaceHexStrings(body, {
278
- minLength: 1000,
279
- truncateLength: 150
280
- });
281
- }
282
- }
283
- // Recursively process all parameters that might contain hex strings
284
- function processParams(obj) {
285
- if (Array.isArray(obj)) {
286
- return obj.map((item)=>processParams(item));
287
- }
288
- if (typeof obj === 'object' && obj !== null) {
289
- const result = {};
290
- for (const [key, value] of Object.entries(obj)){
291
- result[key] = processParams(value);
292
- }
293
- return result;
294
- }
295
- if (typeof obj === 'string') {
296
- if (obj.startsWith('0x')) {
297
- return truncateHex(obj);
298
- }
299
- }
300
- return obj;
301
- }
302
151
  export function tryGetCustomErrorName(err) {
303
152
  try {
304
153
  // See https://viem.sh/docs/contract/simulateContract#handling-custom-errors
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/ethereum",
3
- "version": "3.0.0-nightly.20250911",
3
+ "version": "3.0.0-nightly.20250912",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dest/index.js",
@@ -31,10 +31,10 @@
31
31
  "../package.common.json"
32
32
  ],
33
33
  "dependencies": {
34
- "@aztec/blob-lib": "3.0.0-nightly.20250911",
35
- "@aztec/constants": "3.0.0-nightly.20250911",
36
- "@aztec/foundation": "3.0.0-nightly.20250911",
37
- "@aztec/l1-artifacts": "3.0.0-nightly.20250911",
34
+ "@aztec/blob-lib": "3.0.0-nightly.20250912",
35
+ "@aztec/constants": "3.0.0-nightly.20250912",
36
+ "@aztec/foundation": "3.0.0-nightly.20250912",
37
+ "@aztec/l1-artifacts": "3.0.0-nightly.20250912",
38
38
  "@viem/anvil": "^0.0.10",
39
39
  "dotenv": "^16.0.3",
40
40
  "lodash.chunk": "^4.2.0",
package/src/config.ts CHANGED
@@ -28,6 +28,8 @@ export type L1ContractsConfig = {
28
28
  aztecEpochDuration: number;
29
29
  /** The target validator committee size. */
30
30
  aztecTargetCommitteeSize: number;
31
+ /** The number of epochs to lag behind the current epoch for validator selection. */
32
+ lagInEpochs: number;
31
33
  /** The number of epochs after an epoch ends that proofs are still accepted. */
32
34
  aztecProofSubmissionEpochs: number;
33
35
  /** The deposit amount for a validator */
@@ -73,6 +75,7 @@ export const DefaultL1ContractsConfig = {
73
75
  aztecSlotDuration: 36,
74
76
  aztecEpochDuration: 32,
75
77
  aztecTargetCommitteeSize: 48,
78
+ lagInEpochs: 2,
76
79
  aztecProofSubmissionEpochs: 1, // you have a full epoch to submit a proof after the epoch to prove ends
77
80
  activationThreshold: BigInt(100e18),
78
81
  ejectionThreshold: BigInt(50e18),
@@ -303,6 +306,11 @@ export const l1ContractsConfigMappings: ConfigMappingsType<L1ContractsConfig> =
303
306
  description: 'The target validator committee size.',
304
307
  ...numberConfigHelper(DefaultL1ContractsConfig.aztecTargetCommitteeSize),
305
308
  },
309
+ lagInEpochs: {
310
+ env: 'AZTEC_LAG_IN_EPOCHS',
311
+ description: 'The number of epochs to lag behind the current epoch for validator selection.',
312
+ ...numberConfigHelper(DefaultL1ContractsConfig.lagInEpochs),
313
+ },
306
314
  aztecProofSubmissionEpochs: {
307
315
  env: 'AZTEC_PROOF_SUBMISSION_EPOCHS',
308
316
  description: 'The number of epochs after an epoch ends that proofs are still accepted.',
@@ -10,6 +10,7 @@ import {
10
10
  type GetContractReturnType,
11
11
  type Hex,
12
12
  type StateOverride,
13
+ type WatchContractEventReturnType,
13
14
  encodeFunctionData,
14
15
  getContract,
15
16
  hexToBigInt,
@@ -225,6 +226,11 @@ export class RollupContract {
225
226
  return this.rollup.read.getLocalEjectionThreshold();
226
227
  }
227
228
 
229
+ @memoize
230
+ getLagInEpochs() {
231
+ return this.rollup.read.getLagInEpochs();
232
+ }
233
+
228
234
  @memoize
229
235
  getActivationThreshold() {
230
236
  return this.rollup.read.getActivationThreshold();
@@ -436,8 +442,15 @@ export class RollupContract {
436
442
  return this.rollup.read.getEntryQueueLength();
437
443
  }
438
444
 
439
- async getEpochNumber(blockNumber?: bigint) {
440
- blockNumber ??= await this.getBlockNumber();
445
+ getNextFlushableEpoch() {
446
+ return this.rollup.read.getNextFlushableEpoch();
447
+ }
448
+
449
+ getCurrentEpochNumber(): Promise<bigint> {
450
+ return this.rollup.read.getCurrentEpoch();
451
+ }
452
+
453
+ getEpochNumberForBlock(blockNumber: bigint) {
441
454
  return this.rollup.read.getEpochForBlock([BigInt(blockNumber)]);
442
455
  }
443
456
 
@@ -722,7 +735,9 @@ export class RollupContract {
722
735
  });
723
736
  }
724
737
 
725
- public listenToSlasherChanged(callback: (args: { oldSlasher: `0x${string}`; newSlasher: `0x${string}` }) => unknown) {
738
+ public listenToSlasherChanged(
739
+ callback: (args: { oldSlasher: `0x${string}`; newSlasher: `0x${string}` }) => unknown,
740
+ ): WatchContractEventReturnType {
726
741
  return this.rollup.watchEvent.SlasherUpdated(
727
742
  {},
728
743
  {
@@ -738,6 +753,22 @@ export class RollupContract {
738
753
  );
739
754
  }
740
755
 
756
+ public listenToBlockInvalidated(callback: (args: { blockNumber: bigint }) => unknown): WatchContractEventReturnType {
757
+ return this.rollup.watchEvent.BlockInvalidated(
758
+ {},
759
+ {
760
+ onLogs: logs => {
761
+ for (const log of logs) {
762
+ const args = log.args;
763
+ if (args.blockNumber !== undefined) {
764
+ callback({ blockNumber: args.blockNumber });
765
+ }
766
+ }
767
+ },
768
+ },
769
+ );
770
+ }
771
+
741
772
  public async getSlashEvents(l1BlockHash: Hex): Promise<{ amount: bigint; attester: EthAddress }[]> {
742
773
  const events = await this.rollup.getEvents.Slashed({}, { blockHash: l1BlockHash, strict: true });
743
774
  return events.map(event => ({
@@ -746,7 +777,9 @@ export class RollupContract {
746
777
  }));
747
778
  }
748
779
 
749
- public listenToSlash(callback: (args: { amount: bigint; attester: EthAddress }) => unknown) {
780
+ public listenToSlash(
781
+ callback: (args: { amount: bigint; attester: EthAddress }) => unknown,
782
+ ): WatchContractEventReturnType {
750
783
  return this.rollup.watchEvent.Slashed(
751
784
  {},
752
785
  {
@@ -5,6 +5,7 @@ import { EthAddress } from '@aztec/foundation/eth-address';
5
5
  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
+ import { retryUntil } from '@aztec/foundation/retry';
8
9
  import { DateProvider } from '@aztec/foundation/timer';
9
10
  import type { RollupAbi } from '@aztec/l1-artifacts/RollupAbi';
10
11
 
@@ -430,6 +431,7 @@ export const deployRollupForUpgrade = async (
430
431
  registryAddress: EthAddress,
431
432
  logger: Logger,
432
433
  txUtilsConfig: L1TxUtilsConfig,
434
+ flushEntryQueue: boolean = true,
433
435
  ) => {
434
436
  const deployer = new L1Deployer(
435
437
  extendedClient,
@@ -442,7 +444,14 @@ export const deployRollupForUpgrade = async (
442
444
 
443
445
  const addresses = await RegistryContract.collectAddresses(extendedClient, registryAddress, 'canonical');
444
446
 
445
- const { rollup, slashFactoryAddress } = await deployRollup(extendedClient, deployer, args, addresses, logger);
447
+ const { rollup, slashFactoryAddress } = await deployRollup(
448
+ extendedClient,
449
+ deployer,
450
+ args,
451
+ addresses,
452
+ flushEntryQueue,
453
+ logger,
454
+ );
446
455
 
447
456
  await deployer.waitForDeployments();
448
457
 
@@ -501,6 +510,7 @@ export const deployRollup = async (
501
510
  | 'gseAddress'
502
511
  | 'governanceAddress'
503
512
  >,
513
+ flushEntryQueue: boolean,
504
514
  logger: Logger,
505
515
  ) => {
506
516
  if (!addresses.gseAddress) {
@@ -530,6 +540,7 @@ export const deployRollup = async (
530
540
  aztecSlotDuration: BigInt(args.aztecSlotDuration),
531
541
  aztecEpochDuration: BigInt(args.aztecEpochDuration),
532
542
  targetCommitteeSize: BigInt(args.aztecTargetCommitteeSize),
543
+ lagInEpochs: BigInt(args.lagInEpochs),
533
544
  aztecProofSubmissionEpochs: BigInt(args.aztecProofSubmissionEpochs),
534
545
  slashingQuorum: BigInt(args.slashingQuorum ?? (args.slashingRoundSizeInEpochs * args.aztecEpochDuration) / 2 + 1),
535
546
  slashingRoundSize: BigInt(args.slashingRoundSizeInEpochs * args.aztecEpochDuration),
@@ -674,6 +685,7 @@ export const deployRollup = async (
674
685
  addresses.stakingAssetAddress.toString(),
675
686
  args.initialValidators,
676
687
  args.acceleratedTestDeployments,
688
+ flushEntryQueue,
677
689
  logger,
678
690
  );
679
691
  }
@@ -850,6 +862,7 @@ export const addMultipleValidators = async (
850
862
  stakingAssetAddress: Hex,
851
863
  validators: Operator[],
852
864
  acceleratedTestDeployments: boolean | undefined,
865
+ flushEntryQueue: boolean,
853
866
  logger: Logger,
854
867
  ) => {
855
868
  const rollup = new RollupContract(extendedClient, rollupAddress);
@@ -916,7 +929,7 @@ export const addMultipleValidators = async (
916
929
  data: encodeFunctionData({
917
930
  abi: MultiAdderArtifact.contractAbi,
918
931
  functionName: 'addValidators',
919
- args: [validatorsTuples, true],
932
+ args: [validatorsTuples, /* skip flushing */ true],
920
933
  }),
921
934
  },
922
935
  {
@@ -924,19 +937,49 @@ export const addMultipleValidators = async (
924
937
  },
925
938
  );
926
939
 
927
- await deployer.l1TxUtils.sendAndMonitorTransaction(
928
- {
929
- to: rollupAddress,
930
- data: encodeFunctionData({
931
- abi: RollupArtifact.contractAbi,
932
- functionName: 'flushEntryQueue',
933
- args: [],
934
- }),
935
- },
936
- {
937
- gasLimit: 40_000_000n,
938
- },
939
- );
940
+ let queueLength = await rollup.getEntryQueueLength();
941
+ while (flushEntryQueue && queueLength > 0n) {
942
+ logger.info(`Flushing entry queue with ${queueLength} entries`);
943
+
944
+ try {
945
+ await deployer.l1TxUtils.sendAndMonitorTransaction(
946
+ {
947
+ to: rollupAddress,
948
+ data: encodeFunctionData({
949
+ abi: RollupArtifact.contractAbi,
950
+ functionName: 'flushEntryQueue',
951
+ args: [],
952
+ }),
953
+ },
954
+ {
955
+ gasLimit: 20_000_000n,
956
+ },
957
+ );
958
+ } catch (err) {
959
+ logger.warn('Failed to flush queue', { err });
960
+ }
961
+
962
+ queueLength = await rollup.getEntryQueueLength();
963
+ // check if we drained the queue enough here so we can avoid sleep
964
+ if (queueLength === 0n) {
965
+ break;
966
+ }
967
+
968
+ logger.info(`Waiting for next flushable epoch to flush remaining ${queueLength} entries`);
969
+ await retryUntil(
970
+ async () => {
971
+ const [currentEpoch, flushableEpoch] = await Promise.all([
972
+ rollup.getCurrentEpochNumber(),
973
+ rollup.getNextFlushableEpoch(),
974
+ ]);
975
+ logger.debug(`Next flushable epoch is ${flushableEpoch} (current epoch is ${currentEpoch})`);
976
+ return currentEpoch >= flushableEpoch;
977
+ },
978
+ 'wait for next flushable epoch',
979
+ 3600,
980
+ 12,
981
+ );
982
+ }
940
983
  } else {
941
984
  await deployer.l1TxUtils.sendAndMonitorTransaction(
942
985
  {
@@ -944,7 +987,7 @@ export const addMultipleValidators = async (
944
987
  data: encodeFunctionData({
945
988
  abi: MultiAdderArtifact.contractAbi,
946
989
  functionName: 'addValidators',
947
- args: [validatorsTuples, false],
990
+ args: [validatorsTuples, /* skip flushing */ !flushEntryQueue],
948
991
  }),
949
992
  },
950
993
  {
@@ -1027,6 +1070,7 @@ export const deployL1Contracts = async (
1027
1070
  args: DeployL1ContractsArgs,
1028
1071
  txUtilsConfig: L1TxUtilsConfig = getL1TxUtilsConfigEnvVars(),
1029
1072
  createVerificationJson: string | false = false,
1073
+ flushEntryQueue: boolean = true,
1030
1074
  ): Promise<DeployL1ContractsReturnType> => {
1031
1075
  logger.info(`Deploying L1 contracts with config: ${jsonStringify(args)}`);
1032
1076
  validateConfig(args);
@@ -1093,6 +1137,7 @@ export const deployL1Contracts = async (
1093
1137
  stakingAssetAddress,
1094
1138
  governanceAddress,
1095
1139
  },
1140
+ flushEntryQueue,
1096
1141
  logger,
1097
1142
  );
1098
1143
 
@@ -279,7 +279,13 @@ export class ReadOnlyL1TxUtils {
279
279
  let blobBaseFee = 0n;
280
280
  if (isBlobTx) {
281
281
  try {
282
- blobBaseFee = await this.client.getBlobBaseFee();
282
+ blobBaseFee = await retry<bigint>(
283
+ () => this.client.getBlobBaseFee(),
284
+ 'Getting L1 blob base fee',
285
+ makeBackoff(times(2, () => 1)),
286
+ this.logger,
287
+ true,
288
+ );
283
289
  this.logger?.debug('L1 Blob base fee:', { blobBaseFee: formatGwei(blobBaseFee) });
284
290
  } catch {
285
291
  this.logger?.warn('Failed to get L1 blob base fee', attempt);
@@ -864,7 +870,7 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
864
870
  },
865
871
  );
866
872
 
867
- const txData = {
873
+ const txData: PrepareTransactionRequestRequest = {
868
874
  ...request,
869
875
  ...blobInputs,
870
876
  nonce,
@@ -872,6 +878,9 @@ export class L1TxUtils extends ReadOnlyL1TxUtils {
872
878
  maxFeePerGas: newGasPrice.maxFeePerGas,
873
879
  maxPriorityFeePerGas: newGasPrice.maxPriorityFeePerGas,
874
880
  };
881
+ if (isBlobTx && newGasPrice.maxFeePerBlobGas) {
882
+ (txData as any).maxFeePerBlobGas = newGasPrice.maxFeePerBlobGas;
883
+ }
875
884
  const signedRequest = await this.prepareSignedTransaction(txData);
876
885
  const newHash = await this.client.sendRawTransaction({ serializedTransaction: signedRequest });
877
886
  if (!isCancelTx) {
package/src/queries.ts CHANGED
@@ -33,6 +33,7 @@ export async function getL1ContractsConfig(
33
33
  aztecSlotDuration,
34
34
  aztecProofSubmissionEpochs,
35
35
  aztecTargetCommitteeSize,
36
+ lagInEpochs,
36
37
  activationThreshold,
37
38
  ejectionThreshold,
38
39
  localEjectionThreshold,
@@ -57,6 +58,7 @@ export async function getL1ContractsConfig(
57
58
  rollup.getSlotDuration(),
58
59
  rollup.getProofSubmissionEpochs(),
59
60
  rollup.getTargetCommitteeSize(),
61
+ rollup.getLagInEpochs(),
60
62
  rollup.getActivationThreshold(),
61
63
  rollup.getEjectionThreshold(),
62
64
  rollup.getLocalEjectionThreshold(),
@@ -83,6 +85,7 @@ export async function getL1ContractsConfig(
83
85
  aztecSlotDuration: Number(aztecSlotDuration),
84
86
  aztecProofSubmissionEpochs: Number(aztecProofSubmissionEpochs),
85
87
  aztecTargetCommitteeSize: Number(aztecTargetCommitteeSize),
88
+ lagInEpochs: Number(lagInEpochs),
86
89
  governanceProposerQuorum: Number(governanceProposerQuorum),
87
90
  governanceProposerRoundSize: Number(governanceProposerRoundSize),
88
91
  activationThreshold,
@@ -10,7 +10,7 @@ import type { ViemClient } from '../types.js';
10
10
 
11
11
  export type ChainMonitorEventMap = {
12
12
  'l1-block': [{ l1BlockNumber: number; timestamp: bigint }];
13
- 'l2-block': [{ l2BlockNumber: number; l1BlockNumber: number; timestamp: bigint }];
13
+ 'l2-block': [{ l2BlockNumber: number; l1BlockNumber: number; l2SlotNumber: number; timestamp: bigint }];
14
14
  'l2-block-proven': [{ l2ProvenBlockNumber: number; l1BlockNumber: number; timestamp: bigint }];
15
15
  'l2-messages': [{ totalL2Messages: number; l1BlockNumber: number }];
16
16
  'l2-epoch': [{ l2EpochNumber: number; timestamp: bigint; committee: EthAddress[] | undefined }];
@@ -102,8 +102,13 @@ export class ChainMonitor extends EventEmitter<ChainMonitorEventMap> {
102
102
  }
103
103
  this.l1BlockNumber = newL1BlockNumber;
104
104
 
105
- const block = await this.l1Client.getBlock({ blockNumber: BigInt(newL1BlockNumber), includeTransactions: false });
106
- const timestamp = block.timestamp;
105
+ const [l2SlotNumber, l2Epoch, l1block] = await Promise.all([
106
+ this.rollup.getSlotNumber(),
107
+ this.rollup.getCurrentEpoch(),
108
+ this.l1Client.getBlock({ blockNumber: BigInt(newL1BlockNumber), includeTransactions: false }),
109
+ ]);
110
+
111
+ const timestamp = l1block.timestamp;
107
112
  const timestampString = new Date(Number(timestamp) * 1000).toTimeString().split(' ')[0];
108
113
 
109
114
  this.emit('l1-block', { l1BlockNumber: newL1BlockNumber, timestamp });
@@ -111,16 +116,21 @@ export class ChainMonitor extends EventEmitter<ChainMonitorEventMap> {
111
116
 
112
117
  const newL2BlockNumber = Number(await this.rollup.getBlockNumber());
113
118
  if (this.l2BlockNumber !== newL2BlockNumber) {
114
- const epochNumber = await this.rollup.getEpochNumber(BigInt(newL2BlockNumber));
119
+ const epochNumber = await this.rollup.getEpochNumberForBlock(BigInt(newL2BlockNumber));
115
120
  msg += ` with new L2 block ${newL2BlockNumber} for epoch ${epochNumber}`;
116
121
  this.l2BlockNumber = newL2BlockNumber;
117
122
  this.l2BlockTimestamp = timestamp;
118
- this.emit('l2-block', { l2BlockNumber: newL2BlockNumber, l1BlockNumber: newL1BlockNumber, timestamp });
123
+ this.emit('l2-block', {
124
+ l2BlockNumber: newL2BlockNumber,
125
+ l1BlockNumber: newL1BlockNumber,
126
+ l2SlotNumber: Number(l2SlotNumber),
127
+ timestamp,
128
+ });
119
129
  }
120
130
 
121
131
  const newL2ProvenBlockNumber = Number(await this.rollup.getProvenBlockNumber());
122
132
  if (this.l2ProvenBlockNumber !== newL2ProvenBlockNumber) {
123
- const epochNumber = await this.rollup.getEpochNumber(BigInt(newL2ProvenBlockNumber));
133
+ const epochNumber = await this.rollup.getEpochNumberForBlock(BigInt(newL2ProvenBlockNumber));
124
134
  msg += ` with proof up to L2 block ${newL2ProvenBlockNumber} for epoch ${epochNumber}`;
125
135
  this.l2ProvenBlockNumber = newL2ProvenBlockNumber;
126
136
  this.l2ProvenBlockTimestamp = timestamp;
@@ -139,8 +149,6 @@ export class ChainMonitor extends EventEmitter<ChainMonitorEventMap> {
139
149
  this.emit('l2-messages', { totalL2Messages: newTotalL2Messages, l1BlockNumber: newL1BlockNumber });
140
150
  }
141
151
 
142
- const [l2SlotNumber, l2Epoch] = await Promise.all([this.rollup.getSlotNumber(), this.rollup.getCurrentEpoch()]);
143
-
144
152
  let committee: EthAddress[] | undefined;
145
153
  if (l2Epoch !== this.l2EpochNumber) {
146
154
  this.l2EpochNumber = l2Epoch;
@@ -184,4 +192,52 @@ export class ChainMonitor extends EventEmitter<ChainMonitorEventMap> {
184
192
  this.on('l2-slot', listener);
185
193
  });
186
194
  }
195
+
196
+ public waitUntilL1Block(block: number | bigint): Promise<void> {
197
+ const targetBlock = typeof block === 'bigint' ? block.valueOf() : block;
198
+ if (this.l1BlockNumber >= targetBlock) {
199
+ return Promise.resolve();
200
+ }
201
+ return new Promise(resolve => {
202
+ const listener = (data: { l1BlockNumber: number; timestamp: bigint }) => {
203
+ if (data.l1BlockNumber >= targetBlock) {
204
+ this.off('l1-block', listener);
205
+ resolve();
206
+ }
207
+ };
208
+ this.on('l1-block', listener);
209
+ });
210
+ }
211
+
212
+ public waitUntilL1Timestamp(timestamp: number | bigint): Promise<void> {
213
+ const targetTimestamp = typeof timestamp === 'bigint' ? timestamp.valueOf() : timestamp;
214
+ if (this.l1BlockNumber >= targetTimestamp) {
215
+ return Promise.resolve();
216
+ }
217
+ return new Promise(resolve => {
218
+ const listener = (data: { l1BlockNumber: number; timestamp: bigint }) => {
219
+ if (data.timestamp >= targetTimestamp) {
220
+ this.off('l1-block', listener);
221
+ resolve();
222
+ }
223
+ };
224
+ this.on('l1-block', listener);
225
+ });
226
+ }
227
+
228
+ public waitUntilL2Block(l2BlockNumber: number | bigint): Promise<void> {
229
+ const targetBlock = typeof l2BlockNumber === 'bigint' ? l2BlockNumber.valueOf() : l2BlockNumber;
230
+ if (this.l2BlockNumber >= targetBlock) {
231
+ return Promise.resolve();
232
+ }
233
+ return new Promise(resolve => {
234
+ const listener = (data: { l2BlockNumber: number; timestamp: bigint }) => {
235
+ if (data.l2BlockNumber >= targetBlock) {
236
+ this.off('l2-block', listener);
237
+ resolve();
238
+ }
239
+ };
240
+ this.on('l2-block', listener);
241
+ });
242
+ }
187
243
  }