@aztec/ethereum 0.87.2 → 0.87.3-nightly.20250528

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
@@ -109,25 +109,6 @@ function getNestedErrorData(error) {
109
109
  // Create a clone to avoid modifying the original
110
110
  const errorClone = structuredClone(error);
111
111
  // Helper function to recursively remove ABI properties
112
- const stripAbis = (obj)=>{
113
- if (!obj || typeof obj !== 'object') {
114
- return;
115
- }
116
- // Delete ABI property at current level
117
- if ('abi' in obj) {
118
- delete obj.abi;
119
- }
120
- // Process cause property
121
- if (obj.cause) {
122
- stripAbis(obj.cause);
123
- }
124
- // Process arrays and objects
125
- Object.values(obj).forEach((value)=>{
126
- if (value && typeof value === 'object') {
127
- stripAbis(value);
128
- }
129
- });
130
- };
131
112
  // Strip ABIs from the clone
132
113
  stripAbis(errorClone);
133
114
  // Use the cleaned clone for further processing
@@ -137,139 +118,6 @@ function getNestedErrorData(error) {
137
118
  if (error instanceof Error) {
138
119
  return new FormattedViemError(error.message, error?.metaMessages);
139
120
  }
140
- const truncateHex = (hex, length = 100)=>{
141
- if (!hex || typeof hex !== 'string') {
142
- return hex;
143
- }
144
- if (!hex.startsWith('0x')) {
145
- return hex;
146
- }
147
- if (hex.length <= length * 2) {
148
- return hex;
149
- }
150
- // For extremely large hex strings, use more aggressive truncation
151
- if (hex.length > 10000) {
152
- return `${hex.slice(0, length)}...<${hex.length - length * 2} chars omitted>...${hex.slice(-length)}`;
153
- }
154
- return `${hex.slice(0, length)}...${hex.slice(-length)}`;
155
- };
156
- const replaceHexStrings = (text, options = {})=>{
157
- const { minLength = 10, maxLength = Infinity, truncateLength = 100, pattern, transform = (hex)=>truncateHex(hex, truncateLength) } = options;
158
- const hexRegex = pattern ?? new RegExp(`(0x[a-fA-F0-9]{${minLength},${maxLength}})`, 'g');
159
- return text.replace(hexRegex, (match)=>transform(match));
160
- };
161
- const formatRequestBody = (body)=>{
162
- try {
163
- // Special handling for eth_sendRawTransaction
164
- if (body.includes('"method":"eth_sendRawTransaction"')) {
165
- try {
166
- const parsed = JSON.parse(body);
167
- if (parsed.params && Array.isArray(parsed.params) && parsed.params.length > 0) {
168
- // These are likely large transaction hex strings
169
- parsed.params = parsed.params.map((param)=>{
170
- if (typeof param === 'string' && param.startsWith('0x') && param.length > 1000) {
171
- return truncateHex(param, 200);
172
- }
173
- return param;
174
- });
175
- }
176
- return JSON.stringify(parsed, null, 2);
177
- } catch {
178
- // If specific parsing fails, fall back to regex-based truncation
179
- return replaceHexStrings(body, {
180
- pattern: /"params":\s*\[\s*"(0x[a-fA-F0-9]{1000,})"\s*\]/g,
181
- transform: (hex)=>`"params":["${truncateHex(hex, 200)}"]`
182
- });
183
- }
184
- }
185
- // For extremely large request bodies, use simple truncation instead of parsing
186
- if (body.length > 50000) {
187
- const jsonStart = body.indexOf('{');
188
- const jsonEnd = body.lastIndexOf('}');
189
- if (jsonStart >= 0 && jsonEnd > jsonStart) {
190
- return replaceHexStrings(body, {
191
- minLength: 10000,
192
- truncateLength: 200
193
- });
194
- }
195
- }
196
- const parsed = JSON.parse(body);
197
- // Recursively process all parameters that might contain hex strings
198
- const processParams = (obj)=>{
199
- if (Array.isArray(obj)) {
200
- return obj.map((item)=>processParams(item));
201
- }
202
- if (typeof obj === 'object' && obj !== null) {
203
- const result = {};
204
- for (const [key, value] of Object.entries(obj)){
205
- result[key] = processParams(value);
206
- }
207
- return result;
208
- }
209
- if (typeof obj === 'string') {
210
- if (obj.startsWith('0x')) {
211
- return truncateHex(obj);
212
- }
213
- }
214
- return obj;
215
- };
216
- // Process the entire request body
217
- const processed = processParams(parsed);
218
- return JSON.stringify(processed, null, 2);
219
- } catch {
220
- // If JSON parsing fails, do a simple truncation of any large hex strings
221
- return replaceHexStrings(body, {
222
- minLength: 1000,
223
- truncateLength: 150
224
- });
225
- }
226
- };
227
- const extractAndFormatRequestBody = (message)=>{
228
- // First check if message is extremely large and contains very large hex strings
229
- if (message.length > 50000) {
230
- message = replaceHexStrings(message, {
231
- minLength: 10000,
232
- truncateLength: 200
233
- });
234
- }
235
- // Add a specific check for RPC calls with large params
236
- if (message.includes('"method":"eth_sendRawTransaction"')) {
237
- message = replaceHexStrings(message, {
238
- pattern: /"params":\s*\[\s*"(0x[a-fA-F0-9]{1000,})"\s*\]/g,
239
- transform: (hex)=>`"params":["${truncateHex(hex, 200)}"]`
240
- });
241
- }
242
- // First handle Request body JSON
243
- const requestBodyRegex = /Request body: ({[\s\S]*?})\n/g;
244
- let result = message.replace(requestBodyRegex, (match, body)=>{
245
- return `Request body: ${formatRequestBody(body)}\n`;
246
- });
247
- // Then handle Arguments section
248
- const argsRegex = /((?:Request |Estimate Gas )?Arguments:[\s\S]*?(?=\n\n|$))/g;
249
- result = result.replace(argsRegex, (section)=>{
250
- const lines = section.split('\n');
251
- const processedLines = lines.map((line)=>{
252
- // Check if line contains a colon followed by content
253
- const colonIndex = line.indexOf(':');
254
- if (colonIndex !== -1) {
255
- const [prefix, content] = [
256
- line.slice(0, colonIndex + 1),
257
- line.slice(colonIndex + 1).trim()
258
- ];
259
- // If content contains a hex string, truncate it
260
- if (content.includes('0x')) {
261
- const processedContent = replaceHexStrings(content);
262
- return `${prefix} ${processedContent}`;
263
- }
264
- }
265
- return line;
266
- });
267
- return processedLines.join('\n');
268
- });
269
- // Finally, catch any remaining hex strings in the message
270
- result = replaceHexStrings(result);
271
- return result;
272
- };
273
121
  // Extract the actual error message and highlight it for clarity
274
122
  let formattedRes = extractAndFormatRequestBody(error?.message || String(error));
275
123
  let errorDetail = '';
@@ -299,6 +147,158 @@ function getNestedErrorData(error) {
299
147
  }
300
148
  return new FormattedViemError(formattedRes.replace(/\\n/g, '\n'), error?.metaMessages);
301
149
  }
150
+ function stripAbis(obj) {
151
+ if (!obj || typeof obj !== 'object') {
152
+ return;
153
+ }
154
+ // Delete ABI property at current level
155
+ if ('abi' in obj) {
156
+ delete obj.abi;
157
+ }
158
+ // Process cause property
159
+ if (obj.cause) {
160
+ stripAbis(obj.cause);
161
+ }
162
+ // Process arrays and objects
163
+ Object.values(obj).forEach((value)=>{
164
+ if (value && typeof value === 'object') {
165
+ stripAbis(value);
166
+ }
167
+ });
168
+ }
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
302
  export function tryGetCustomErrorName(err) {
303
303
  try {
304
304
  // 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": "0.87.2",
3
+ "version": "0.87.3-nightly.20250528",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": "./dest/index.js",
@@ -32,9 +32,9 @@
32
32
  "../package.common.json"
33
33
  ],
34
34
  "dependencies": {
35
- "@aztec/blob-lib": "0.87.2",
36
- "@aztec/foundation": "0.87.2",
37
- "@aztec/l1-artifacts": "0.87.2",
35
+ "@aztec/blob-lib": "0.87.3-nightly.20250528",
36
+ "@aztec/foundation": "0.87.3-nightly.20250528",
37
+ "@aztec/l1-artifacts": "0.87.3-nightly.20250528",
38
38
  "@viem/anvil": "^0.0.10",
39
39
  "dotenv": "^16.0.3",
40
40
  "tslib": "^2.4.0",
package/src/config.ts CHANGED
@@ -44,10 +44,10 @@ export type L1ContractsConfig = {
44
44
 
45
45
  export const DefaultL1ContractsConfig = {
46
46
  ethereumSlotDuration: 12,
47
- aztecSlotDuration: 24,
48
- aztecEpochDuration: 16,
47
+ aztecSlotDuration: 36,
48
+ aztecEpochDuration: 32,
49
49
  aztecTargetCommitteeSize: 48,
50
- aztecProofSubmissionWindow: 31, // you have a full epoch to submit a proof after the epoch to prove ends
50
+ aztecProofSubmissionWindow: 64, // you have a full epoch to submit a proof after the epoch to prove ends
51
51
  minimumStake: BigInt(100e18),
52
52
  slashingQuorum: 6,
53
53
  slashingRoundSize: 10,
@@ -5,17 +5,23 @@ import { RollupAbi } from '@aztec/l1-artifacts/RollupAbi';
5
5
  import { RollupStorage } from '@aztec/l1-artifacts/RollupStorage';
6
6
  import { SlasherAbi } from '@aztec/l1-artifacts/SlasherAbi';
7
7
 
8
- import { type Account, type GetContractReturnType, type Hex, getAddress, getContract } from 'viem';
8
+ import { type Account, type GetContractReturnType, type Hex, encodeFunctionData, getAddress, getContract } from 'viem';
9
9
 
10
10
  import { getPublicClient } from '../client.js';
11
11
  import type { DeployL1ContractsReturnType } from '../deploy_l1_contracts.js';
12
12
  import type { L1ContractAddresses } from '../l1_contract_addresses.js';
13
13
  import type { L1ReaderConfig } from '../l1_reader.js';
14
+ import type { L1TxUtils } from '../l1_tx_utils.js';
14
15
  import type { ViemClient } from '../types.js';
15
16
  import { formatViemError } from '../utils.js';
16
17
  import { SlashingProposerContract } from './slashing_proposer.js';
17
18
  import { checkBlockTag } from './utils.js';
18
19
 
20
+ export type ViemCommitteeAttestation = {
21
+ addr: `0x${string}`;
22
+ signature: ViemSignature;
23
+ };
24
+
19
25
  export type L1RollupContractAddresses = Pick<
20
26
  L1ContractAddresses,
21
27
  | 'rollupAddress'
@@ -26,13 +32,12 @@ export type L1RollupContractAddresses = Pick<
26
32
  | 'stakingAssetAddress'
27
33
  | 'rewardDistributorAddress'
28
34
  | 'slashFactoryAddress'
35
+ | 'gseAddress'
29
36
  >;
30
37
 
31
38
  export type EpochProofPublicInputArgs = {
32
39
  previousArchive: `0x${string}`;
33
40
  endArchive: `0x${string}`;
34
- endTimestamp: bigint;
35
- outHash: `0x${string}`;
36
41
  proverId: `0x${string}`;
37
42
  };
38
43
 
@@ -279,6 +284,7 @@ export class RollupContract {
279
284
  rewardDistributorAddress,
280
285
  feeJuiceAddress,
281
286
  stakingAssetAddress,
287
+ gseAddress,
282
288
  ] = (
283
289
  await Promise.all([
284
290
  this.rollup.read.getInbox(),
@@ -287,6 +293,7 @@ export class RollupContract {
287
293
  this.rollup.read.getRewardDistributor(),
288
294
  this.rollup.read.getFeeAsset(),
289
295
  this.rollup.read.getStakingAsset(),
296
+ this.rollup.read.getGSE(),
290
297
  ] as const)
291
298
  ).map(EthAddress.fromString);
292
299
 
@@ -298,6 +305,7 @@ export class RollupContract {
298
305
  feeJuiceAddress,
299
306
  stakingAssetAddress,
300
307
  rewardDistributorAddress,
308
+ gseAddress,
301
309
  };
302
310
  }
303
311
 
@@ -318,7 +326,7 @@ export class RollupContract {
318
326
  public async validateHeader(
319
327
  args: readonly [
320
328
  `0x${string}`,
321
- ViemSignature[],
329
+ ViemCommitteeAttestation[],
322
330
  `0x${string}`,
323
331
  bigint,
324
332
  `0x${string}`,
@@ -360,6 +368,7 @@ export class RollupContract {
360
368
  slotDuration = BigInt(slotDuration);
361
369
  }
362
370
  const timeOfNextL1Slot = (await this.client.getBlock()).timestamp + slotDuration;
371
+
363
372
  try {
364
373
  const {
365
374
  result: [slot, blockNumber],
@@ -429,11 +438,18 @@ export class RollupContract {
429
438
  return this.rollup.read.getAttesters();
430
439
  }
431
440
 
432
- getInfo(address: Hex | EthAddress) {
441
+ getAttesterView(address: Hex | EthAddress) {
442
+ if (address instanceof EthAddress) {
443
+ address = address.toString();
444
+ }
445
+ return this.rollup.read.getAttesterView([address]);
446
+ }
447
+
448
+ getStatus(address: Hex | EthAddress) {
433
449
  if (address instanceof EthAddress) {
434
450
  address = address.toString();
435
451
  }
436
- return this.rollup.read.getInfo([address]);
452
+ return this.rollup.read.getStatus([address]);
437
453
  }
438
454
 
439
455
  getBlobPublicInputsHash(blockNumber: bigint) {
@@ -450,4 +466,15 @@ export class RollupContract {
450
466
  }
451
467
  return this.rollup.read.getProposerForAttester([attester]);
452
468
  }
469
+
470
+ setupEpoch(l1TxUtils: L1TxUtils) {
471
+ return l1TxUtils.sendAndMonitorTransaction({
472
+ to: this.address,
473
+ data: encodeFunctionData({
474
+ abi: RollupAbi,
475
+ functionName: 'setupEpoch',
476
+ args: [],
477
+ }),
478
+ });
479
+ }
453
480
  }
@@ -1,19 +1,35 @@
1
1
  import { EthAddress } from '@aztec/foundation/eth-address';
2
+ import { retryUntil } from '@aztec/foundation/retry';
2
3
  import { SlashingProposerAbi } from '@aztec/l1-artifacts/SlashingProposerAbi';
3
4
 
4
- import { type GetContractReturnType, type Hex, getContract } from 'viem';
5
+ import EventEmitter from 'events';
6
+ import {
7
+ type EncodeFunctionDataParameters,
8
+ type GetContractReturnType,
9
+ type Hex,
10
+ encodeFunctionData,
11
+ getContract,
12
+ } from 'viem';
5
13
 
6
- import type { L1TxRequest } from '../l1_tx_utils.js';
14
+ import type { L1TxRequest, L1TxUtils } from '../l1_tx_utils.js';
7
15
  import type { ViemClient } from '../types.js';
16
+ import { FormattedViemError } from '../utils.js';
8
17
  import { type IEmpireBase, encodeVote } from './empire_base.js';
9
18
 
10
- export class SlashingProposerContract implements IEmpireBase {
19
+ export class ProposalAlreadyExecutedError extends Error {
20
+ constructor(round: bigint) {
21
+ super(`Proposal already executed: ${round}`);
22
+ }
23
+ }
24
+
25
+ export class SlashingProposerContract extends EventEmitter implements IEmpireBase {
11
26
  private readonly proposer: GetContractReturnType<typeof SlashingProposerAbi, ViemClient>;
12
27
 
13
28
  constructor(
14
29
  public readonly client: ViemClient,
15
30
  address: Hex,
16
31
  ) {
32
+ super();
17
33
  this.proposer = getContract({ address, abi: SlashingProposerAbi, client });
18
34
  }
19
35
 
@@ -45,10 +61,104 @@ export class SlashingProposerContract implements IEmpireBase {
45
61
  };
46
62
  }
47
63
 
64
+ public getProposalVotes(rollupAddress: Hex, round: bigint, proposal: Hex): Promise<bigint> {
65
+ return this.proposer.read.yeaCount([rollupAddress, round, proposal]);
66
+ }
67
+
48
68
  public createVoteRequest(payload: Hex): L1TxRequest {
49
69
  return {
50
70
  to: this.address.toString(),
51
71
  data: encodeVote(payload),
52
72
  };
53
73
  }
74
+
75
+ public listenToExecutableProposals(callback: (args: { proposal: `0x${string}`; round: bigint }) => unknown) {
76
+ return this.proposer.watchEvent.ProposalExecutable(
77
+ {},
78
+ {
79
+ onLogs: logs => {
80
+ for (const payload of logs) {
81
+ const args = payload.args;
82
+ if (args.proposal && args.round) {
83
+ // why compiler can't figure it out? no one knows
84
+ callback(args as { proposal: `0x${string}`; round: bigint });
85
+ }
86
+ }
87
+ },
88
+ },
89
+ );
90
+ }
91
+
92
+ public listenToProposalExecuted(callback: (args: { round: bigint; proposal: `0x${string}` }) => unknown) {
93
+ return this.proposer.watchEvent.ProposalExecuted(
94
+ {},
95
+ {
96
+ onLogs: logs => {
97
+ for (const payload of logs) {
98
+ const args = payload.args;
99
+ if (args.round && args.proposal) {
100
+ callback(args as { round: bigint; proposal: `0x${string}` });
101
+ }
102
+ }
103
+ },
104
+ },
105
+ );
106
+ }
107
+
108
+ public waitForRound(round: bigint, pollingIntervalSeconds: number = 1) {
109
+ return retryUntil(
110
+ async () => {
111
+ const currentRound = await this.proposer.read.getCurrentRound();
112
+ return currentRound >= round;
113
+ },
114
+ `Waiting for round ${round} to be reached`,
115
+ 0, // no timeout
116
+ pollingIntervalSeconds,
117
+ );
118
+ }
119
+
120
+ public async executeRound(txUtils: L1TxUtils, round: bigint | number): Promise<void> {
121
+ if (typeof round === 'number') {
122
+ round = BigInt(round);
123
+ }
124
+ const args: EncodeFunctionDataParameters<typeof SlashingProposerAbi, 'executeProposal'> = {
125
+ abi: SlashingProposerAbi,
126
+ functionName: 'executeProposal',
127
+ args: [round],
128
+ };
129
+ const data = encodeFunctionData(args);
130
+ const response = await txUtils
131
+ .sendAndMonitorTransaction(
132
+ {
133
+ to: this.address.toString(),
134
+ data,
135
+ },
136
+ {
137
+ // Gas estimation is more than 20% off for this tx.
138
+ gasLimitBufferPercentage: 100,
139
+ },
140
+ )
141
+ .catch(err => {
142
+ if (err instanceof FormattedViemError && err.message.includes('ProposalAlreadyExecuted')) {
143
+ throw new ProposalAlreadyExecutedError(round);
144
+ }
145
+ throw err;
146
+ });
147
+
148
+ if (response.receipt.status === 'reverted') {
149
+ const error = await txUtils.tryGetErrorFromRevertedTx(
150
+ data,
151
+ {
152
+ ...args,
153
+ address: this.address.toString(),
154
+ },
155
+ undefined,
156
+ [],
157
+ );
158
+ if (error?.includes('ProposalAlreadyExecuted')) {
159
+ throw new ProposalAlreadyExecutedError(round);
160
+ }
161
+ throw new Error(error ?? 'Unknown error');
162
+ }
163
+ }
54
164
  }