@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
@@ -1,1105 +0,0 @@
1
- import { compactArray, times } from '@aztec/foundation/collection';
2
- import {
3
- type ConfigMappingsType,
4
- bigintConfigHelper,
5
- booleanConfigHelper,
6
- getConfigFromMappings,
7
- getDefaultConfig,
8
- numberConfigHelper,
9
- } from '@aztec/foundation/config';
10
- import { EthAddress } from '@aztec/foundation/eth-address';
11
- import type { ViemTransactionSignature } from '@aztec/foundation/eth-signature';
12
- import { type Logger, createLogger } from '@aztec/foundation/log';
13
- import { makeBackoff, retry } from '@aztec/foundation/retry';
14
- import { sleep } from '@aztec/foundation/sleep';
15
- import { DateProvider } from '@aztec/foundation/timer';
16
- import { RollupAbi } from '@aztec/l1-artifacts/RollupAbi';
17
-
18
- import pickBy from 'lodash.pickby';
19
- import {
20
- type Abi,
21
- type Account,
22
- type Address,
23
- type BaseError,
24
- type BlockOverrides,
25
- type ContractFunctionExecutionError,
26
- type GetTransactionReturnType,
27
- type Hex,
28
- MethodNotFoundRpcError,
29
- MethodNotSupportedRpcError,
30
- type NonceManager,
31
- type PrepareTransactionRequestRequest,
32
- type StateOverride,
33
- type TransactionReceipt,
34
- type TransactionSerializable,
35
- type WalletClient,
36
- createNonceManager,
37
- decodeErrorResult,
38
- formatGwei,
39
- getContractError,
40
- hexToBytes,
41
- parseTransaction,
42
- serializeTransaction,
43
- } from 'viem';
44
- import { jsonRpc } from 'viem/nonce';
45
-
46
- import type { EthSigner } from './eth-signer/eth-signer.js';
47
- import type { ExtendedViemWalletClient, ViemClient } from './types.js';
48
- import { formatViemError } from './utils.js';
49
-
50
- // 1_000_000_000 Gwei = 1 ETH
51
- // 1_000_000_000 Wei = 1 Gwei
52
- // 1_000_000_000_000_000_000 Wei = 1 ETH
53
-
54
- const WEI_CONST = 1_000_000_000n;
55
-
56
- // @note using this large gas limit to avoid the issue of `gas limit too low` when estimating gas in reth
57
- const LARGE_GAS_LIMIT = 12_000_000n;
58
-
59
- // setting a minimum bump percentage to 10% due to geth's implementation
60
- // https://github.com/ethereum/go-ethereum/blob/e3d61e6db028c412f74bc4d4c7e117a9e29d0de0/core/txpool/legacypool/list.go#L298
61
- const MIN_REPLACEMENT_BUMP_PERCENTAGE = 10;
62
-
63
- // setting a minimum bump percentage to 100% due to geth's implementation
64
- // https://github.com/ethereum/go-ethereum/blob/e3d61e6db028c412f74bc4d4c7e117a9e29d0de0/core/txpool/blobpool/config.go#L34
65
- const MIN_BLOB_REPLACEMENT_BUMP_PERCENTAGE = 100;
66
-
67
- // Avg ethereum block time is ~12s
68
- const BLOCK_TIME_MS = 12_000;
69
-
70
- export interface L1TxUtilsConfig {
71
- /**
72
- * How much to increase calculated gas limit.
73
- */
74
- gasLimitBufferPercentage?: number;
75
- /**
76
- * Maximum gas price in gwei
77
- */
78
- maxGwei?: bigint;
79
- /**
80
- * Maximum blob fee per gas in gwei
81
- */
82
- maxBlobGwei?: bigint;
83
- /**
84
- * Priority fee bump percentage
85
- */
86
- priorityFeeBumpPercentage?: number;
87
- /**
88
- * How much to increase priority fee by each attempt (percentage)
89
- */
90
- priorityFeeRetryBumpPercentage?: number;
91
- /**
92
- * Fixed priority fee per gas in Gwei. Overrides any priority fee bump percentage config
93
- */
94
- fixedPriorityFeePerGas?: number;
95
- /**
96
- * Maximum number of speed-up attempts
97
- */
98
- maxAttempts?: number;
99
- /**
100
- * How often to check tx status
101
- */
102
- checkIntervalMs?: number;
103
- /**
104
- * How long before considering tx stalled
105
- */
106
- stallTimeMs?: number;
107
- /**
108
- * How long to wait for a tx to be mined before giving up
109
- */
110
- txTimeoutMs?: number;
111
- /**
112
- * How many attempts will be done to get a tx after it was sent?
113
- * First attempt is done at 1s, second at 2s, third at 3s, etc.
114
- */
115
- txPropagationMaxQueryAttempts?: number;
116
- /**
117
- * Whether to attempt to cancel a tx if it's not mined after txTimeoutMs
118
- */
119
- cancelTxOnTimeout?: boolean;
120
- }
121
-
122
- export const l1TxUtilsConfigMappings: ConfigMappingsType<L1TxUtilsConfig> = {
123
- gasLimitBufferPercentage: {
124
- description: 'How much to increase calculated gas limit by (percentage)',
125
- env: 'L1_GAS_LIMIT_BUFFER_PERCENTAGE',
126
- ...numberConfigHelper(20),
127
- },
128
- maxGwei: {
129
- description: 'Maximum gas price in gwei',
130
- env: 'L1_GAS_PRICE_MAX',
131
- ...bigintConfigHelper(500n),
132
- },
133
- maxBlobGwei: {
134
- description: 'Maximum blob fee per gas in gwei',
135
- env: 'L1_BLOB_FEE_PER_GAS_MAX',
136
- ...bigintConfigHelper(1_500n),
137
- },
138
- priorityFeeBumpPercentage: {
139
- description: 'How much to increase priority fee by each attempt (percentage)',
140
- env: 'L1_PRIORITY_FEE_BUMP_PERCENTAGE',
141
- ...numberConfigHelper(20),
142
- },
143
- priorityFeeRetryBumpPercentage: {
144
- description: 'How much to increase priority fee by each retry attempt (percentage)',
145
- env: 'L1_PRIORITY_FEE_RETRY_BUMP_PERCENTAGE',
146
- ...numberConfigHelper(50),
147
- },
148
- fixedPriorityFeePerGas: {
149
- description: 'Fixed priority fee per gas in Gwei. Overrides any priority fee bump percentage',
150
- env: 'L1_FIXED_PRIORITY_FEE_PER_GAS',
151
- ...numberConfigHelper(0),
152
- },
153
- maxAttempts: {
154
- description: 'Maximum number of speed-up attempts',
155
- env: 'L1_TX_MONITOR_MAX_ATTEMPTS',
156
- ...numberConfigHelper(3),
157
- },
158
- checkIntervalMs: {
159
- description: 'How often to check tx status',
160
- env: 'L1_TX_MONITOR_CHECK_INTERVAL_MS',
161
- ...numberConfigHelper(1_000),
162
- },
163
- stallTimeMs: {
164
- description: 'How long before considering tx stalled',
165
- env: 'L1_TX_MONITOR_STALL_TIME_MS',
166
- ...numberConfigHelper(24_000), // 24s, 2 ethereum slots
167
- },
168
- txTimeoutMs: {
169
- description: 'How long to wait for a tx to be mined before giving up. Set to 0 to disable.',
170
- env: 'L1_TX_MONITOR_TX_TIMEOUT_MS',
171
- ...numberConfigHelper(120_000), // 2 mins
172
- },
173
- txPropagationMaxQueryAttempts: {
174
- description: 'How many attempts will be done to get a tx after it was sent',
175
- env: 'L1_TX_PROPAGATION_MAX_QUERY_ATTEMPTS',
176
- ...numberConfigHelper(3),
177
- },
178
- cancelTxOnTimeout: {
179
- description: "Whether to attempt to cancel a tx if it's not mined after txTimeoutMs",
180
- env: 'L1_TX_MONITOR_CANCEL_TX_ON_TIMEOUT',
181
- ...booleanConfigHelper(true),
182
- },
183
- };
184
-
185
- export const defaultL1TxUtilsConfig = getDefaultConfig<L1TxUtilsConfig>(l1TxUtilsConfigMappings);
186
-
187
- export function getL1TxUtilsConfigEnvVars(): L1TxUtilsConfig {
188
- return getConfigFromMappings(l1TxUtilsConfigMappings);
189
- }
190
-
191
- export interface L1TxRequest {
192
- to: Address | null;
193
- data?: Hex;
194
- value?: bigint;
195
- abi?: Abi;
196
- }
197
-
198
- export type L1GasConfig = Partial<L1TxUtilsConfig> & { gasLimit?: bigint; txTimeoutAt?: Date };
199
-
200
- export interface L1BlobInputs {
201
- blobs: Uint8Array[];
202
- kzg: any;
203
- maxFeePerBlobGas?: bigint;
204
- }
205
-
206
- export interface GasPrice {
207
- maxFeePerGas: bigint;
208
- maxPriorityFeePerGas: bigint;
209
- maxFeePerBlobGas?: bigint;
210
- }
211
-
212
- export type TransactionStats = {
213
- /** Address of the sender. */
214
- sender: string;
215
- /** Hash of the transaction. */
216
- transactionHash: string;
217
- /** Size in bytes of the tx calldata */
218
- calldataSize: number;
219
- /** Gas required to pay for the calldata inclusion (depends on size and number of zeros) */
220
- calldataGas: number;
221
- };
222
-
223
- export enum TxUtilsState {
224
- IDLE,
225
- SENT,
226
- SPEED_UP,
227
- CANCELLED,
228
- NOT_MINED,
229
- MINED,
230
- }
231
-
232
- export class ReadOnlyL1TxUtils {
233
- public readonly config: L1TxUtilsConfig;
234
- protected interrupted = false;
235
-
236
- constructor(
237
- public client: ViemClient,
238
- protected logger: Logger = createLogger('ReadOnlyL1TxUtils'),
239
- public readonly dateProvider: DateProvider,
240
- config?: Partial<L1TxUtilsConfig>,
241
- protected debugMaxGasLimit: boolean = false,
242
- ) {
243
- this.config = {
244
- ...defaultL1TxUtilsConfig,
245
- ...(config || {}),
246
- };
247
- }
248
-
249
- public interrupt() {
250
- this.interrupted = true;
251
- }
252
-
253
- public restart() {
254
- this.interrupted = false;
255
- }
256
-
257
- public getBlock() {
258
- return this.client.getBlock();
259
- }
260
-
261
- public getBlockNumber() {
262
- return this.client.getBlockNumber();
263
- }
264
-
265
- /**
266
- * Gets the current gas price with bounds checking
267
- */
268
- public async getGasPrice(
269
- _gasConfig?: L1TxUtilsConfig,
270
- isBlobTx: boolean = false,
271
- attempt: number = 0,
272
- previousGasPrice?: typeof attempt extends 0 ? never : GasPrice,
273
- ): Promise<GasPrice> {
274
- const gasConfig = { ...this.config, ..._gasConfig };
275
- const block = await this.client.getBlock({ blockTag: 'latest' });
276
- const baseFee = block.baseFeePerGas ?? 0n;
277
-
278
- // Get blob base fee if available
279
- let blobBaseFee = 0n;
280
- if (isBlobTx) {
281
- try {
282
- blobBaseFee = await this.client.getBlobBaseFee();
283
- this.logger?.debug('L1 Blob base fee:', { blobBaseFee: formatGwei(blobBaseFee) });
284
- } catch {
285
- this.logger?.warn('Failed to get L1 blob base fee', attempt);
286
- }
287
- }
288
-
289
- let priorityFee: bigint;
290
- if (gasConfig.fixedPriorityFeePerGas) {
291
- this.logger?.debug('Using fixed priority fee per L1 gas', {
292
- fixedPriorityFeePerGas: gasConfig.fixedPriorityFeePerGas,
293
- });
294
- // try to maintain precision up to 1000000 wei
295
- priorityFee = BigInt(gasConfig.fixedPriorityFeePerGas * 1_000_000) * (WEI_CONST / 1_000_000n);
296
- } else {
297
- // Get initial priority fee from the network
298
- priorityFee = await this.client.estimateMaxPriorityFeePerGas();
299
- }
300
- let maxFeePerGas = baseFee;
301
-
302
- let maxFeePerBlobGas = blobBaseFee;
303
-
304
- // Bump base fee so it's valid for next blocks if it stalls
305
- const numBlocks = Math.ceil(gasConfig.stallTimeMs! / BLOCK_TIME_MS);
306
- for (let i = 0; i < numBlocks; i++) {
307
- // each block can go up 12.5% from previous baseFee
308
- maxFeePerGas = (maxFeePerGas * (1_000n + 125n)) / 1_000n;
309
- // same for blob gas fee
310
- maxFeePerBlobGas = (maxFeePerBlobGas * (1_000n + 125n)) / 1_000n;
311
- }
312
- if (attempt > 0) {
313
- const configBump =
314
- gasConfig.priorityFeeRetryBumpPercentage ?? defaultL1TxUtilsConfig.priorityFeeRetryBumpPercentage!;
315
-
316
- // if this is a blob tx, we have to use the blob bump percentage
317
- const minBumpPercentage = isBlobTx ? MIN_BLOB_REPLACEMENT_BUMP_PERCENTAGE : MIN_REPLACEMENT_BUMP_PERCENTAGE;
318
-
319
- const bumpPercentage = configBump > minBumpPercentage ? configBump : minBumpPercentage;
320
- // Calculate minimum required fees based on previous attempt
321
- // multiply by 100 & divide by 100 to maintain some precision
322
- const minPriorityFee =
323
- (previousGasPrice!.maxPriorityFeePerGas * (100_00n + BigInt(bumpPercentage * 1_00))) / 100_00n;
324
- const minMaxFee = (previousGasPrice!.maxFeePerGas * (100_00n + BigInt(bumpPercentage * 1_00))) / 100_00n;
325
-
326
- // Add priority fee to maxFeePerGas
327
- maxFeePerGas += priorityFee;
328
-
329
- // Use maximum between current network values and minimum required values
330
- priorityFee = priorityFee > minPriorityFee ? priorityFee : minPriorityFee;
331
- maxFeePerGas = maxFeePerGas > minMaxFee ? maxFeePerGas : minMaxFee;
332
- } else {
333
- // first attempt, just bump priority fee, unless it's a fixed config
334
- // multiply by 100 & divide by 100 to maintain some precision
335
- if (!gasConfig.fixedPriorityFeePerGas) {
336
- priorityFee = (priorityFee * (100_00n + BigInt((gasConfig.priorityFeeBumpPercentage || 0) * 1_00))) / 100_00n;
337
- }
338
- maxFeePerGas += priorityFee;
339
- }
340
-
341
- // Ensure we don't exceed maxGwei
342
- const maxGweiInWei = gasConfig.maxGwei! * WEI_CONST;
343
- maxFeePerGas = maxFeePerGas > maxGweiInWei ? maxGweiInWei : maxFeePerGas;
344
-
345
- // Ensure we don't exceed maxBlobGwei
346
- if (maxFeePerBlobGas) {
347
- const maxBlobGweiInWei = gasConfig.maxBlobGwei! * WEI_CONST;
348
- maxFeePerBlobGas = maxFeePerBlobGas > maxBlobGweiInWei ? maxBlobGweiInWei : maxFeePerBlobGas;
349
- }
350
-
351
- // Ensure priority fee doesn't exceed max fee
352
- const maxPriorityFeePerGas = priorityFee > maxFeePerGas ? maxFeePerGas : priorityFee;
353
-
354
- if (attempt > 0 && previousGasPrice?.maxFeePerBlobGas) {
355
- const bumpPercentage =
356
- gasConfig.priorityFeeRetryBumpPercentage! > MIN_BLOB_REPLACEMENT_BUMP_PERCENTAGE
357
- ? gasConfig.priorityFeeRetryBumpPercentage!
358
- : MIN_BLOB_REPLACEMENT_BUMP_PERCENTAGE;
359
-
360
- // calculate min blob fee based on previous attempt
361
- const minBlobFee = (previousGasPrice.maxFeePerBlobGas * (100_00n + BigInt(bumpPercentage * 1_00))) / 100_00n;
362
-
363
- // use max between current network values and min required values
364
- maxFeePerBlobGas = maxFeePerBlobGas > minBlobFee ? maxFeePerBlobGas : minBlobFee;
365
- }
366
-
367
- this.logger?.debug(`Computed L1 gas price`, {
368
- attempt,
369
- baseFee: formatGwei(baseFee),
370
- maxFeePerGas: formatGwei(maxFeePerGas),
371
- maxPriorityFeePerGas: formatGwei(maxPriorityFeePerGas),
372
- ...(maxFeePerBlobGas && { maxFeePerBlobGas: formatGwei(maxFeePerBlobGas) }),
373
- });
374
-
375
- return {
376
- maxFeePerGas,
377
- maxPriorityFeePerGas,
378
- ...(maxFeePerBlobGas && { maxFeePerBlobGas: maxFeePerBlobGas }),
379
- };
380
- }
381
-
382
- /**
383
- * Estimates gas and adds buffer
384
- */
385
- public async estimateGas(
386
- account: Account | Hex,
387
- request: L1TxRequest,
388
- _gasConfig?: L1TxUtilsConfig,
389
- _blobInputs?: L1BlobInputs,
390
- ): Promise<bigint> {
391
- const gasConfig = { ...this.config, ..._gasConfig };
392
- let initialEstimate = 0n;
393
- if (_blobInputs) {
394
- // @note requests with blobs also require maxFeePerBlobGas to be set
395
- const gasPrice = await this.getGasPrice(gasConfig, true, 0);
396
- initialEstimate = await this.client.estimateGas({
397
- account,
398
- ...request,
399
- ..._blobInputs,
400
- maxFeePerBlobGas: gasPrice.maxFeePerBlobGas!,
401
- gas: LARGE_GAS_LIMIT,
402
- });
403
-
404
- this.logger?.debug(`L1 gas used in estimateGas by blob tx: ${initialEstimate}`);
405
- } else {
406
- initialEstimate = await this.client.estimateGas({ account, ...request, gas: LARGE_GAS_LIMIT });
407
- this.logger?.debug(`L1 gas used in estimateGas by non-blob tx: ${initialEstimate}`);
408
- }
409
-
410
- // Add buffer based on either fixed amount or percentage
411
- const withBuffer = this.bumpGasLimit(initialEstimate, gasConfig);
412
-
413
- return withBuffer;
414
- }
415
-
416
- async getTransactionStats(txHash: string): Promise<TransactionStats | undefined> {
417
- const tx = await this.client.getTransaction({ hash: txHash as Hex });
418
- if (!tx) {
419
- return undefined;
420
- }
421
- const calldata = hexToBytes(tx.input);
422
- return {
423
- sender: tx.from.toString(),
424
- transactionHash: tx.hash,
425
- calldataSize: calldata.length,
426
- calldataGas: getCalldataGasUsage(calldata),
427
- };
428
- }
429
-
430
- public async tryGetErrorFromRevertedTx(
431
- data: Hex,
432
- args: {
433
- args: readonly any[];
434
- functionName: string;
435
- abi: Abi;
436
- address: Hex;
437
- },
438
- blobInputs: (L1BlobInputs & { maxFeePerBlobGas: bigint }) | undefined,
439
- stateOverride: StateOverride = [],
440
- ) {
441
- try {
442
- await this.client.simulateContract({
443
- ...args,
444
- account: this.client.account,
445
- stateOverride,
446
- });
447
- this.logger?.trace('Simulated blob tx', { blobInputs });
448
- // If the above passes, we have a blob error. We cannot simulate blob txs, and failed txs no longer throw errors.
449
- // Strangely, the only way to throw the revert reason as an error and provide blobs is prepareTransactionRequest.
450
- // See: https://github.com/wevm/viem/issues/2075
451
- // This throws a EstimateGasExecutionError with the custom error information:
452
- const request = blobInputs
453
- ? {
454
- account: this.client.account,
455
- to: args.address,
456
- data,
457
- blobs: blobInputs.blobs,
458
- kzg: blobInputs.kzg,
459
- maxFeePerBlobGas: blobInputs.maxFeePerBlobGas,
460
- }
461
- : {
462
- account: this.client.account,
463
- to: args.address,
464
- data,
465
- };
466
- this.logger?.trace('Preparing tx', { request });
467
- await this.client.prepareTransactionRequest(request);
468
- this.logger?.trace('Prepared tx');
469
- return undefined;
470
- } catch (simulationErr: any) {
471
- // If we don't have a ContractFunctionExecutionError, we have a blob related error => use getContractError to get the error msg.
472
- const contractErr =
473
- simulationErr.name === 'ContractFunctionExecutionError'
474
- ? simulationErr
475
- : getContractError(simulationErr as BaseError, {
476
- args: [],
477
- abi: args.abi,
478
- functionName: args.functionName,
479
- address: args.address,
480
- });
481
- if (contractErr.name === 'ContractFunctionExecutionError') {
482
- const execErr = contractErr as ContractFunctionExecutionError;
483
- return tryGetCustomErrorNameContractFunction(execErr);
484
- }
485
- this.logger?.error(`Error getting error from simulation`, simulationErr);
486
- }
487
- }
488
-
489
- public async simulate(
490
- request: L1TxRequest & { gas?: bigint; from?: Hex },
491
- blockOverrides: BlockOverrides<bigint, number> = {},
492
- stateOverrides: StateOverride = [],
493
- abi: Abi = RollupAbi,
494
- _gasConfig?: L1TxUtilsConfig & { fallbackGasEstimate?: bigint },
495
- ): Promise<{ gasUsed: bigint; result: `0x${string}` }> {
496
- const gasConfig = { ...this.config, ..._gasConfig };
497
-
498
- const call: any = {
499
- to: request.to!,
500
- data: request.data,
501
- ...(request.from && { from: request.from }),
502
- };
503
-
504
- return await this._simulate(call, blockOverrides, stateOverrides, gasConfig, abi);
505
- }
506
-
507
- protected async _simulate(
508
- call: any,
509
- blockOverrides: BlockOverrides<bigint, number> = {},
510
- stateOverrides: StateOverride = [],
511
- gasConfig: L1TxUtilsConfig & { fallbackGasEstimate?: bigint },
512
- abi: Abi,
513
- ) {
514
- try {
515
- const result = await this.client.simulateBlocks({
516
- validation: false,
517
- blocks: [
518
- {
519
- blockOverrides,
520
- stateOverrides,
521
- calls: [call],
522
- },
523
- ],
524
- });
525
-
526
- if (result[0].calls[0].status === 'failure') {
527
- this.logger?.error('L1 transaction simulation failed', result[0].calls[0].error);
528
- const decodedError = decodeErrorResult({ abi, data: result[0].calls[0].data });
529
-
530
- throw new Error(
531
- `L1 transaction simulation failed with error ${decodedError.errorName}(${decodedError.args?.join(',')})`,
532
- );
533
- }
534
- this.logger?.debug(`L1 transaction simulation succeeded`, { ...result[0].calls[0] });
535
- return { gasUsed: result[0].gasUsed, result: result[0].calls[0].data as `0x${string}` };
536
- } catch (err) {
537
- if (err instanceof MethodNotFoundRpcError || err instanceof MethodNotSupportedRpcError) {
538
- if (gasConfig.fallbackGasEstimate) {
539
- this.logger?.warn(
540
- `Node does not support eth_simulateV1 API. Using fallback gas estimate: ${gasConfig.fallbackGasEstimate}`,
541
- );
542
- return { gasUsed: gasConfig.fallbackGasEstimate, result: '0x' as `0x${string}` };
543
- }
544
- this.logger?.error('Node does not support eth_simulateV1 API');
545
- }
546
- throw err;
547
- }
548
- }
549
-
550
- public bumpGasLimit(gasLimit: bigint, _gasConfig?: L1TxUtilsConfig): bigint {
551
- const gasConfig = { ...this.config, ..._gasConfig };
552
- const bumpedGasLimit = gasLimit + (gasLimit * BigInt((gasConfig?.gasLimitBufferPercentage || 0) * 1_00)) / 100_00n;
553
-
554
- const cleanGasConfig = pickBy(gasConfig, (_, key) => key in l1TxUtilsConfigMappings);
555
- this.logger?.debug('Bumping gas limit', { gasLimit, gasConfig: cleanGasConfig, bumpedGasLimit });
556
- return bumpedGasLimit;
557
- }
558
- }
559
-
560
- export type SigningCallback = (
561
- transaction: TransactionSerializable,
562
- signingAddress: EthAddress,
563
- ) => Promise<ViemTransactionSignature>;
564
-
565
- export class L1TxUtils extends ReadOnlyL1TxUtils {
566
- private txUtilsState: TxUtilsState = TxUtilsState.IDLE;
567
- private lastMinedBlockNumber: bigint | undefined = undefined;
568
- private nonceManager: NonceManager;
569
-
570
- constructor(
571
- public override client: ViemClient,
572
- public address: EthAddress,
573
- private signer: SigningCallback,
574
- protected override logger: Logger = createLogger('L1TxUtils'),
575
- dateProvider: DateProvider = new DateProvider(),
576
- config?: Partial<L1TxUtilsConfig>,
577
- debugMaxGasLimit: boolean = false,
578
- ) {
579
- super(client, logger, dateProvider, config, debugMaxGasLimit);
580
- this.nonceManager = createNonceManager({ source: jsonRpc() });
581
- }
582
-
583
- public get state() {
584
- return this.txUtilsState;
585
- }
586
-
587
- public get lastMinedAtBlockNumber() {
588
- return this.lastMinedBlockNumber;
589
- }
590
-
591
- private set lastMinedAtBlockNumber(blockNumber: bigint | undefined) {
592
- this.lastMinedBlockNumber = blockNumber;
593
- }
594
-
595
- private set state(state: TxUtilsState) {
596
- this.txUtilsState = state;
597
- this.logger?.debug(
598
- `L1TxUtils state changed to ${TxUtilsState[state]} for sender: ${this.getSenderAddress().toString()}`,
599
- );
600
- }
601
-
602
- public getSenderAddress() {
603
- return this.address;
604
- }
605
-
606
- public getSenderBalance(): Promise<bigint> {
607
- return this.client.getBalance({
608
- address: this.getSenderAddress().toString(),
609
- });
610
- }
611
-
612
- private async signTransaction(txRequest: TransactionSerializable): Promise<`0x${string}`> {
613
- const signature = await this.signer(txRequest, this.getSenderAddress());
614
- return serializeTransaction(txRequest, signature);
615
- }
616
-
617
- protected async prepareSignedTransaction(txData: PrepareTransactionRequestRequest) {
618
- const txRequest = await this.client.prepareTransactionRequest(txData);
619
- return await this.signTransaction(txRequest as TransactionSerializable);
620
- }
621
-
622
- /**
623
- * Sends a transaction with gas estimation and pricing
624
- * @param request - The transaction request (to, data, value)
625
- * @param gasConfig - Optional gas configuration
626
- * @returns The transaction hash and parameters used
627
- */
628
- public async sendTransaction(
629
- request: L1TxRequest,
630
- _gasConfig?: L1GasConfig,
631
- blobInputs?: L1BlobInputs,
632
- stateChange: TxUtilsState = TxUtilsState.SENT,
633
- ): Promise<{ txHash: Hex; gasLimit: bigint; gasPrice: GasPrice }> {
634
- try {
635
- const gasConfig = { ...this.config, ..._gasConfig };
636
- const account = this.getSenderAddress().toString();
637
-
638
- let gasLimit: bigint;
639
- if (this.debugMaxGasLimit) {
640
- gasLimit = LARGE_GAS_LIMIT;
641
- } else if (gasConfig.gasLimit) {
642
- gasLimit = gasConfig.gasLimit;
643
- } else {
644
- gasLimit = await this.estimateGas(account, request, gasConfig);
645
- }
646
- this.logger?.debug(`Gas limit for request is ${gasLimit}`, { gasLimit, ...request });
647
-
648
- const gasPrice = await this.getGasPrice(gasConfig, !!blobInputs);
649
-
650
- if (gasConfig.txTimeoutAt && this.dateProvider.now() > gasConfig.txTimeoutAt.getTime()) {
651
- throw new Error('Transaction timed out before sending');
652
- }
653
-
654
- const nonce = await this.nonceManager.consume({
655
- client: this.client,
656
- address: account,
657
- chainId: this.client.chain.id,
658
- });
659
-
660
- let txHash: Hex;
661
- if (blobInputs) {
662
- const txData = {
663
- ...request,
664
- ...blobInputs,
665
- gas: gasLimit,
666
- maxFeePerGas: gasPrice.maxFeePerGas,
667
- maxPriorityFeePerGas: gasPrice.maxPriorityFeePerGas,
668
- maxFeePerBlobGas: gasPrice.maxFeePerBlobGas!,
669
- nonce,
670
- };
671
-
672
- const signedRequest = await this.prepareSignedTransaction(txData);
673
- txHash = await this.client.sendRawTransaction({ serializedTransaction: signedRequest });
674
- } else {
675
- const txData = {
676
- ...request,
677
- gas: gasLimit,
678
- maxFeePerGas: gasPrice.maxFeePerGas,
679
- maxPriorityFeePerGas: gasPrice.maxPriorityFeePerGas,
680
- nonce,
681
- };
682
- const signedRequest = await this.prepareSignedTransaction(txData);
683
- txHash = await this.client.sendRawTransaction({ serializedTransaction: signedRequest });
684
- }
685
- this.state = stateChange;
686
- const cleanGasConfig = pickBy(gasConfig, (_, key) => key in l1TxUtilsConfigMappings);
687
- this.logger?.info(`Sent L1 transaction ${txHash}`, {
688
- gasLimit,
689
- maxFeePerGas: formatGwei(gasPrice.maxFeePerGas),
690
- maxPriorityFeePerGas: formatGwei(gasPrice.maxPriorityFeePerGas),
691
- gasConfig: cleanGasConfig,
692
- ...(gasPrice.maxFeePerBlobGas && { maxFeePerBlobGas: formatGwei(gasPrice.maxFeePerBlobGas) }),
693
- });
694
-
695
- return { txHash, gasLimit, gasPrice };
696
- } catch (err: any) {
697
- const viemError = formatViemError(err, request.abi);
698
- this.logger?.error(`Failed to send L1 transaction`, viemError.message, {
699
- metaMessages: viemError.metaMessages,
700
- });
701
- throw viemError;
702
- }
703
- }
704
-
705
- /**
706
- * Monitors a transaction until completion, handling speed-ups if needed
707
- * @param request - Original transaction request (needed for speed-ups)
708
- * @param initialTxHash - Hash of the initial transaction
709
- * @param params - Parameters used in the initial transaction
710
- * @param gasConfig - Optional gas configuration
711
- */
712
- public async monitorTransaction(
713
- request: L1TxRequest,
714
- initialTxHash: Hex,
715
- params: { gasLimit: bigint },
716
- _gasConfig?: Partial<L1TxUtilsConfig> & { txTimeoutAt?: Date },
717
- _blobInputs?: L1BlobInputs,
718
- isCancelTx: boolean = false,
719
- ): Promise<TransactionReceipt> {
720
- const isBlobTx = !!_blobInputs;
721
- const gasConfig = { ...this.config, ..._gasConfig };
722
- const account = this.getSenderAddress().toString();
723
-
724
- const blobInputs = _blobInputs || {};
725
- const makeGetTransactionBackoff = () =>
726
- makeBackoff(times(gasConfig.txPropagationMaxQueryAttempts ?? 3, i => i + 1));
727
-
728
- // Retry a few times, in case the tx is not yet propagated.
729
- const tx = await retry<GetTransactionReturnType>(
730
- () => this.client.getTransaction({ hash: initialTxHash }),
731
- `Getting L1 transaction ${initialTxHash}`,
732
- makeGetTransactionBackoff(),
733
- this.logger,
734
- true,
735
- );
736
-
737
- if (!tx) {
738
- throw new Error(`Failed to get L1 transaction ${initialTxHash} to monitor`);
739
- }
740
-
741
- if (tx?.nonce === undefined || tx?.nonce === null) {
742
- throw new Error(`Failed to get L1 transaction ${initialTxHash} nonce`);
743
- }
744
- const nonce = tx.nonce;
745
-
746
- const txHashes = new Set<Hex>([initialTxHash]);
747
- let currentTxHash = initialTxHash;
748
- let attempts = 0;
749
- let lastAttemptSent = this.dateProvider.now();
750
- let lastGasPrice: GasPrice = {
751
- maxFeePerGas: tx.maxFeePerGas!,
752
- maxPriorityFeePerGas: tx.maxPriorityFeePerGas!,
753
- maxFeePerBlobGas: tx.maxFeePerBlobGas!,
754
- };
755
- const initialTxTime = lastAttemptSent;
756
-
757
- let txTimedOut = false;
758
- let latestBlockTimestamp: bigint | undefined;
759
-
760
- // We check against the latestBlockTimestamp as opposed to the current time to avoid a race condition where
761
- // the tx is mined in a block with the same timestamp as txTimeoutAt, but our execution node has not yet processed it,
762
- // or the loop here has not yet checked the tx before that timeout.
763
- const isTimedOut = () =>
764
- (gasConfig.txTimeoutAt &&
765
- latestBlockTimestamp !== undefined &&
766
- Number(latestBlockTimestamp) * 1000 >= gasConfig.txTimeoutAt.getTime()) ||
767
- (gasConfig.txTimeoutMs !== undefined && this.dateProvider.now() - initialTxTime > gasConfig.txTimeoutMs) ||
768
- this.interrupted ||
769
- false;
770
-
771
- while (!txTimedOut) {
772
- try {
773
- ({ timestamp: latestBlockTimestamp } = await this.client.getBlock({
774
- blockTag: 'latest',
775
- includeTransactions: false,
776
- }));
777
-
778
- const currentNonce = await this.client.getTransactionCount({ address: account });
779
- if (currentNonce > nonce) {
780
- for (const hash of txHashes) {
781
- try {
782
- const receipt = await this.client.getTransactionReceipt({ hash });
783
- if (receipt) {
784
- if (receipt.status === 'reverted') {
785
- this.logger?.error(`L1 transaction ${hash} reverted`, receipt);
786
- } else {
787
- this.logger?.debug(`L1 transaction ${hash} mined`);
788
- }
789
- this.state = TxUtilsState.MINED;
790
- this.lastMinedAtBlockNumber = receipt.blockNumber;
791
- return receipt;
792
- }
793
- } catch (err) {
794
- if (err instanceof Error && err.message.includes('reverted')) {
795
- throw formatViemError(err);
796
- }
797
- }
798
- }
799
- }
800
-
801
- this.logger?.trace(`Tx timeout check for ${currentTxHash}: ${isTimedOut()}`, {
802
- latestBlockTimestamp: Number(latestBlockTimestamp) * 1000,
803
- lastAttemptSent,
804
- initialTxTime,
805
- now: this.dateProvider.now(),
806
- txTimeoutAt: gasConfig.txTimeoutAt?.getTime(),
807
- txTimeoutMs: gasConfig.txTimeoutMs,
808
- txStallTime: gasConfig.stallTimeMs,
809
- });
810
-
811
- // Retry a few times, in case the tx is not yet propagated.
812
- const tx = await retry<GetTransactionReturnType>(
813
- () => this.client.getTransaction({ hash: currentTxHash }),
814
- `Getting L1 transaction ${currentTxHash}`,
815
- makeGetTransactionBackoff(),
816
- this.logger,
817
- true,
818
- );
819
- const timePassed = this.dateProvider.now() - lastAttemptSent;
820
-
821
- if (tx && timePassed < gasConfig.stallTimeMs!) {
822
- this.logger?.debug(`L1 transaction ${currentTxHash} pending. Time passed: ${timePassed}ms.`);
823
-
824
- // Check timeout before continuing
825
- txTimedOut = isTimedOut();
826
- if (txTimedOut) {
827
- break;
828
- }
829
-
830
- await sleep(gasConfig.checkIntervalMs!);
831
- continue;
832
- }
833
-
834
- if (timePassed > gasConfig.stallTimeMs! && attempts < gasConfig.maxAttempts!) {
835
- attempts++;
836
- const newGasPrice = await this.getGasPrice(
837
- gasConfig,
838
- isBlobTx,
839
- attempts,
840
- tx.maxFeePerGas && tx.maxPriorityFeePerGas
841
- ? {
842
- maxFeePerGas: tx.maxFeePerGas,
843
- maxPriorityFeePerGas: tx.maxPriorityFeePerGas,
844
- maxFeePerBlobGas: tx.maxFeePerBlobGas,
845
- }
846
- : undefined,
847
- );
848
- lastGasPrice = newGasPrice;
849
-
850
- this.logger?.debug(
851
- `L1 transaction ${currentTxHash} appears stuck. Attempting speed-up ${attempts}/${gasConfig.maxAttempts} ` +
852
- `with new priority fee ${formatGwei(newGasPrice.maxPriorityFeePerGas)} gwei`,
853
- {
854
- maxFeePerGas: formatGwei(newGasPrice.maxFeePerGas),
855
- maxPriorityFeePerGas: formatGwei(newGasPrice.maxPriorityFeePerGas),
856
- ...(newGasPrice.maxFeePerBlobGas && { maxFeePerBlobGas: formatGwei(newGasPrice.maxFeePerBlobGas) }),
857
- },
858
- );
859
-
860
- const txData = {
861
- ...request,
862
- ...blobInputs,
863
- nonce,
864
- gas: params.gasLimit,
865
- maxFeePerGas: newGasPrice.maxFeePerGas,
866
- maxPriorityFeePerGas: newGasPrice.maxPriorityFeePerGas,
867
- };
868
- const signedRequest = await this.prepareSignedTransaction(txData);
869
- const newHash = await this.client.sendRawTransaction({ serializedTransaction: signedRequest });
870
- if (!isCancelTx) {
871
- this.state = TxUtilsState.SPEED_UP;
872
- }
873
-
874
- const cleanGasConfig = pickBy(gasConfig, (_, key) => key in l1TxUtilsConfigMappings);
875
- this.logger?.verbose(`Sent L1 speed-up tx ${newHash}, replacing ${currentTxHash}`, {
876
- gasLimit: params.gasLimit,
877
- maxFeePerGas: formatGwei(newGasPrice.maxFeePerGas),
878
- maxPriorityFeePerGas: formatGwei(newGasPrice.maxPriorityFeePerGas),
879
- gasConfig: cleanGasConfig,
880
- ...(newGasPrice.maxFeePerBlobGas && { maxFeePerBlobGas: formatGwei(newGasPrice.maxFeePerBlobGas) }),
881
- });
882
-
883
- currentTxHash = newHash;
884
-
885
- txHashes.add(currentTxHash);
886
- lastAttemptSent = this.dateProvider.now();
887
- }
888
- await sleep(gasConfig.checkIntervalMs!);
889
- } catch (err: any) {
890
- const viemError = formatViemError(err);
891
- this.logger?.warn(`Error monitoring L1 transaction ${currentTxHash}:`, viemError.message);
892
- if (viemError.message?.includes('reverted')) {
893
- throw viemError;
894
- }
895
- await sleep(gasConfig.checkIntervalMs!);
896
- }
897
- // Check if tx has timed out.
898
- txTimedOut = isTimedOut();
899
- }
900
-
901
- // The transaction has timed out. If it's a cancellation then we are giving up on it.
902
- // Otherwise we may attempt to cancel it if configured to do so.
903
- if (isCancelTx) {
904
- this.state = TxUtilsState.NOT_MINED;
905
- } else if (gasConfig.cancelTxOnTimeout) {
906
- // Fire cancellation without awaiting to avoid blocking the main thread
907
- this.attemptTxCancellation(currentTxHash, nonce, isBlobTx, lastGasPrice, attempts).catch(err => {
908
- const viemError = formatViemError(err);
909
- this.logger?.error(`Failed to send cancellation for timed out tx ${currentTxHash}:`, viemError.message, {
910
- metaMessages: viemError.metaMessages,
911
- });
912
- });
913
- }
914
-
915
- this.logger?.error(`L1 transaction ${currentTxHash} timed out`, {
916
- txHash: currentTxHash,
917
- txTimeoutAt: gasConfig.txTimeoutAt,
918
- txTimeoutMs: gasConfig.txTimeoutMs,
919
- txInitialTime: initialTxTime,
920
- now: this.dateProvider.now(),
921
- attempts,
922
- isInterrupted: this.interrupted,
923
- ...tx,
924
- });
925
-
926
- throw new Error(`L1 transaction ${currentTxHash} timed out`);
927
- }
928
-
929
- /**
930
- * Sends a transaction and monitors it until completion
931
- * @param request - The transaction request (to, data, value)
932
- * @param gasConfig - Optional gas configuration
933
- * @returns The receipt of the successful transaction
934
- */
935
- public async sendAndMonitorTransaction(
936
- request: L1TxRequest,
937
- gasConfig?: L1GasConfig,
938
- blobInputs?: L1BlobInputs,
939
- ): Promise<{ receipt: TransactionReceipt; gasPrice: GasPrice }> {
940
- const { txHash, gasLimit, gasPrice } = await this.sendTransaction(request, gasConfig, blobInputs);
941
- const receipt = await this.monitorTransaction(request, txHash, { gasLimit }, gasConfig, blobInputs);
942
- return { receipt, gasPrice };
943
- }
944
-
945
- public override async simulate(
946
- request: L1TxRequest & { gas?: bigint; from?: Hex },
947
- _blockOverrides: BlockOverrides<bigint, number> = {},
948
- stateOverrides: StateOverride = [],
949
- abi: Abi = RollupAbi,
950
- _gasConfig?: L1TxUtilsConfig & { fallbackGasEstimate?: bigint; ignoreBlockGasLimit?: boolean },
951
- ): Promise<{ gasUsed: bigint; result: `0x${string}` }> {
952
- const blockOverrides = { ..._blockOverrides };
953
- const gasConfig = { ...this.config, ..._gasConfig };
954
- const gasPrice = await this.getGasPrice(gasConfig, false);
955
-
956
- const call: any = {
957
- to: request.to!,
958
- data: request.data,
959
- from: request.from ?? this.getSenderAddress().toString(),
960
- maxFeePerGas: gasPrice.maxFeePerGas,
961
- maxPriorityFeePerGas: gasPrice.maxPriorityFeePerGas,
962
- gas: request.gas ?? LARGE_GAS_LIMIT,
963
- };
964
-
965
- if (!request.gas && !gasConfig.ignoreBlockGasLimit) {
966
- // LARGE_GAS_LIMIT is set as call.gas, increase block gasLimit
967
- blockOverrides.gasLimit = LARGE_GAS_LIMIT * 2n;
968
- }
969
-
970
- return this._simulate(call, blockOverrides, stateOverrides, gasConfig, abi);
971
- }
972
-
973
- /**
974
- * Attempts to cancel a transaction by sending a 0-value tx to self with same nonce but higher gas prices
975
- * @param nonce - The nonce of the transaction to cancel
976
- * @param previousGasPrice - The gas price of the previous transaction
977
- * @param attempts - The number of attempts to cancel the transaction
978
- * @returns The hash of the cancellation transaction
979
- */
980
- protected async attemptTxCancellation(
981
- currentTxHash: Hex,
982
- nonce: number,
983
- isBlobTx = false,
984
- previousGasPrice?: GasPrice,
985
- attempts = 0,
986
- ) {
987
- if (isBlobTx) {
988
- throw new Error('Cannot cancel blob transactions, please use L1TxUtilsWithBlobsClass');
989
- }
990
-
991
- // Get gas price with higher priority fee for cancellation
992
- const cancelGasPrice = await this.getGasPrice(
993
- {
994
- ...this.config,
995
- // Use high bump for cancellation to ensure it replaces the original tx
996
- priorityFeeRetryBumpPercentage: 150, // 150% bump should be enough to replace any tx
997
- },
998
- isBlobTx,
999
- attempts + 1,
1000
- previousGasPrice,
1001
- );
1002
-
1003
- this.logger?.debug(`Attempting to cancel L1 transaction ${currentTxHash} with nonce ${nonce}`, {
1004
- maxFeePerGas: formatGwei(cancelGasPrice.maxFeePerGas),
1005
- maxPriorityFeePerGas: formatGwei(cancelGasPrice.maxPriorityFeePerGas),
1006
- });
1007
- const request = {
1008
- to: this.getSenderAddress().toString(),
1009
- value: 0n,
1010
- };
1011
-
1012
- // Send 0-value tx to self with higher gas price
1013
- const txData = {
1014
- ...request,
1015
- nonce,
1016
- gas: 21_000n, // Standard ETH transfer gas
1017
- maxFeePerGas: cancelGasPrice.maxFeePerGas,
1018
- maxPriorityFeePerGas: cancelGasPrice.maxPriorityFeePerGas,
1019
- };
1020
- const signedRequest = await this.prepareSignedTransaction(txData);
1021
- const cancelTxHash = await this.client.sendRawTransaction({ serializedTransaction: signedRequest });
1022
-
1023
- this.state = TxUtilsState.CANCELLED;
1024
-
1025
- this.logger?.debug(`Sent cancellation tx ${cancelTxHash} for timed out tx ${currentTxHash}`, { nonce });
1026
-
1027
- const receipt = await this.monitorTransaction(
1028
- request,
1029
- cancelTxHash,
1030
- { gasLimit: 21_000n },
1031
- undefined,
1032
- undefined,
1033
- true,
1034
- );
1035
-
1036
- return receipt.transactionHash;
1037
- }
1038
- }
1039
-
1040
- export function createViemSigner(client: WalletClient) {
1041
- const signer: SigningCallback = async (
1042
- tx: TransactionSerializable,
1043
- _address: EthAddress,
1044
- ): Promise<ViemTransactionSignature> => {
1045
- const signedTx = await client.signTransaction(tx as any);
1046
-
1047
- const parsed = parseTransaction(signedTx);
1048
-
1049
- if (!parsed.r || !parsed.s || (parsed.yParity !== 0 && parsed.yParity !== 1)) {
1050
- throw new Error('Failed to extract signature from viem signed transaction');
1051
- }
1052
-
1053
- return {
1054
- r: parsed.r,
1055
- s: parsed.s,
1056
- yParity: parsed.yParity,
1057
- };
1058
- };
1059
- return signer;
1060
- }
1061
-
1062
- export function createL1TxUtilsFromViemWallet(
1063
- client: ExtendedViemWalletClient,
1064
- logger: Logger = createLogger('L1TxUtils'),
1065
- dateProvider: DateProvider = new DateProvider(),
1066
- config?: Partial<L1TxUtilsConfig>,
1067
- debugMaxGasLimit: boolean = false,
1068
- ) {
1069
- return new L1TxUtils(
1070
- client,
1071
- EthAddress.fromString(client.account.address),
1072
- createViemSigner(client),
1073
- logger,
1074
- dateProvider,
1075
- config,
1076
- debugMaxGasLimit,
1077
- );
1078
- }
1079
-
1080
- export function createL1TxUtilsFromEthSigner(
1081
- client: ViemClient,
1082
- signer: EthSigner,
1083
- logger: Logger = createLogger('L1TxUtils'),
1084
- dateProvider: DateProvider = new DateProvider(),
1085
- config?: Partial<L1TxUtilsConfig>,
1086
- debugMaxGasLimit: boolean = false,
1087
- ) {
1088
- const callback: SigningCallback = async (transaction: TransactionSerializable, _signingAddress) => {
1089
- return (await signer.signTransaction(transaction)).toViemTransactionSignature();
1090
- };
1091
- return new L1TxUtils(client, signer.address, callback, logger, dateProvider, config, debugMaxGasLimit);
1092
- }
1093
-
1094
- export function tryGetCustomErrorNameContractFunction(err: ContractFunctionExecutionError) {
1095
- return compactArray([err.shortMessage, ...(err.metaMessages ?? []).slice(0, 2).map(s => s.trim())]).join(' ');
1096
- }
1097
-
1098
- /*
1099
- * Returns cost of calldata usage in Ethereum.
1100
- * @param data - Calldata.
1101
- * @returns 4 for each zero byte, 16 for each nonzero.
1102
- */
1103
- export function getCalldataGasUsage(data: Uint8Array) {
1104
- return data.filter(byte => byte === 0).length * 4 + data.filter(byte => byte !== 0).length * 16;
1105
- }