@aztec/ethereum 2.0.3 → 2.1.0-rc.2

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 (124) hide show
  1. package/dest/config.d.ts +10 -5
  2. package/dest/config.d.ts.map +1 -1
  3. package/dest/config.js +27 -39
  4. package/dest/contracts/empire_base.d.ts +1 -1
  5. package/dest/contracts/empire_base.d.ts.map +1 -1
  6. package/dest/contracts/empire_slashing_proposer.d.ts +1 -1
  7. package/dest/contracts/empire_slashing_proposer.d.ts.map +1 -1
  8. package/dest/contracts/fee_asset_handler.d.ts +3 -3
  9. package/dest/contracts/fee_asset_handler.d.ts.map +1 -1
  10. package/dest/contracts/governance.js +7 -3
  11. package/dest/contracts/governance_proposer.d.ts +1 -2
  12. package/dest/contracts/governance_proposer.d.ts.map +1 -1
  13. package/dest/contracts/governance_proposer.js +1 -2
  14. package/dest/contracts/multicall.d.ts +3 -5
  15. package/dest/contracts/multicall.d.ts.map +1 -1
  16. package/dest/contracts/multicall.js +6 -4
  17. package/dest/contracts/rollup.d.ts +13 -14
  18. package/dest/contracts/rollup.d.ts.map +1 -1
  19. package/dest/contracts/rollup.js +25 -67
  20. package/dest/contracts/slasher_contract.d.ts +10 -0
  21. package/dest/contracts/slasher_contract.d.ts.map +1 -1
  22. package/dest/contracts/slasher_contract.js +18 -0
  23. package/dest/deploy_l1_contracts.d.ts +18 -4
  24. package/dest/deploy_l1_contracts.d.ts.map +1 -1
  25. package/dest/deploy_l1_contracts.js +316 -159
  26. package/dest/index.d.ts +1 -1
  27. package/dest/index.d.ts.map +1 -1
  28. package/dest/index.js +1 -1
  29. package/dest/l1_artifacts.d.ts +8019 -5948
  30. package/dest/l1_artifacts.d.ts.map +1 -1
  31. package/dest/l1_artifacts.js +6 -1
  32. package/dest/l1_contract_addresses.d.ts +5 -1
  33. package/dest/l1_contract_addresses.d.ts.map +1 -1
  34. package/dest/l1_contract_addresses.js +2 -1
  35. package/dest/l1_tx_utils/config.d.ts +59 -0
  36. package/dest/l1_tx_utils/config.d.ts.map +1 -0
  37. package/dest/l1_tx_utils/config.js +73 -0
  38. package/dest/l1_tx_utils/constants.d.ts +6 -0
  39. package/dest/l1_tx_utils/constants.d.ts.map +1 -0
  40. package/dest/l1_tx_utils/constants.js +14 -0
  41. package/dest/l1_tx_utils/factory.d.ts +24 -0
  42. package/dest/l1_tx_utils/factory.d.ts.map +1 -0
  43. package/dest/l1_tx_utils/factory.js +12 -0
  44. package/dest/l1_tx_utils/index.d.ts +10 -0
  45. package/dest/l1_tx_utils/index.d.ts.map +1 -0
  46. package/dest/l1_tx_utils/index.js +10 -0
  47. package/dest/l1_tx_utils/interfaces.d.ts +76 -0
  48. package/dest/l1_tx_utils/interfaces.d.ts.map +1 -0
  49. package/dest/l1_tx_utils/interfaces.js +4 -0
  50. package/dest/l1_tx_utils/l1_tx_utils.d.ts +95 -0
  51. package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -0
  52. package/dest/l1_tx_utils/l1_tx_utils.js +610 -0
  53. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts +26 -0
  54. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts.map +1 -0
  55. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.js +26 -0
  56. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +81 -0
  57. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -0
  58. package/dest/l1_tx_utils/readonly_l1_tx_utils.js +294 -0
  59. package/dest/l1_tx_utils/signer.d.ts +4 -0
  60. package/dest/l1_tx_utils/signer.d.ts.map +1 -0
  61. package/dest/l1_tx_utils/signer.js +16 -0
  62. package/dest/l1_tx_utils/types.d.ts +67 -0
  63. package/dest/l1_tx_utils/types.d.ts.map +1 -0
  64. package/dest/l1_tx_utils/types.js +26 -0
  65. package/dest/l1_tx_utils/utils.d.ts +4 -0
  66. package/dest/l1_tx_utils/utils.d.ts.map +1 -0
  67. package/dest/l1_tx_utils/utils.js +14 -0
  68. package/dest/publisher_manager.d.ts +7 -2
  69. package/dest/publisher_manager.d.ts.map +1 -1
  70. package/dest/publisher_manager.js +36 -8
  71. package/dest/queries.d.ts.map +1 -1
  72. package/dest/queries.js +8 -2
  73. package/dest/test/chain_monitor.js +2 -2
  74. package/dest/test/delayed_tx_utils.d.ts +2 -2
  75. package/dest/test/delayed_tx_utils.d.ts.map +1 -1
  76. package/dest/test/delayed_tx_utils.js +2 -2
  77. package/dest/test/eth_cheat_codes.d.ts +18 -1
  78. package/dest/test/eth_cheat_codes.d.ts.map +1 -1
  79. package/dest/test/eth_cheat_codes.js +101 -22
  80. package/dest/test/rollup_cheat_codes.d.ts +3 -1
  81. package/dest/test/rollup_cheat_codes.d.ts.map +1 -1
  82. package/dest/test/rollup_cheat_codes.js +2 -2
  83. package/dest/test/upgrade_utils.js +1 -1
  84. package/package.json +6 -6
  85. package/src/config.ts +34 -44
  86. package/src/contracts/empire_base.ts +1 -1
  87. package/src/contracts/empire_slashing_proposer.ts +1 -1
  88. package/src/contracts/fee_asset_handler.ts +1 -1
  89. package/src/contracts/governance.ts +3 -3
  90. package/src/contracts/governance_proposer.ts +3 -4
  91. package/src/contracts/multicall.ts +12 -10
  92. package/src/contracts/rollup.ts +31 -86
  93. package/src/contracts/slasher_contract.ts +22 -0
  94. package/src/deploy_l1_contracts.ts +351 -178
  95. package/src/index.ts +1 -1
  96. package/src/l1_artifacts.ts +8 -0
  97. package/src/l1_contract_addresses.ts +3 -1
  98. package/src/l1_tx_utils/README.md +177 -0
  99. package/src/l1_tx_utils/config.ts +140 -0
  100. package/src/l1_tx_utils/constants.ts +18 -0
  101. package/src/l1_tx_utils/factory.ts +64 -0
  102. package/src/l1_tx_utils/index.ts +12 -0
  103. package/src/l1_tx_utils/interfaces.ts +86 -0
  104. package/src/l1_tx_utils/l1_tx_utils.ts +718 -0
  105. package/src/l1_tx_utils/l1_tx_utils_with_blobs.ts +77 -0
  106. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +372 -0
  107. package/src/l1_tx_utils/signer.ts +28 -0
  108. package/src/l1_tx_utils/types.ts +85 -0
  109. package/src/l1_tx_utils/utils.ts +16 -0
  110. package/src/publisher_manager.ts +51 -9
  111. package/src/queries.ts +10 -1
  112. package/src/test/chain_monitor.ts +2 -2
  113. package/src/test/delayed_tx_utils.ts +2 -2
  114. package/src/test/eth_cheat_codes.ts +120 -20
  115. package/src/test/rollup_cheat_codes.ts +5 -2
  116. package/src/test/upgrade_utils.ts +1 -1
  117. package/dest/l1_tx_utils.d.ts +0 -252
  118. package/dest/l1_tx_utils.d.ts.map +0 -1
  119. package/dest/l1_tx_utils.js +0 -834
  120. package/dest/l1_tx_utils_with_blobs.d.ts +0 -20
  121. package/dest/l1_tx_utils_with_blobs.d.ts.map +0 -1
  122. package/dest/l1_tx_utils_with_blobs.js +0 -87
  123. package/src/l1_tx_utils.ts +0 -1124
  124. package/src/l1_tx_utils_with_blobs.ts +0 -150
@@ -1,834 +0,0 @@
1
- import { compactArray, times } from '@aztec/foundation/collection';
2
- import { bigintConfigHelper, booleanConfigHelper, getConfigFromMappings, getDefaultConfig, numberConfigHelper } from '@aztec/foundation/config';
3
- import { EthAddress } from '@aztec/foundation/eth-address';
4
- import { createLogger } from '@aztec/foundation/log';
5
- import { makeBackoff, retry } from '@aztec/foundation/retry';
6
- import { sleep } from '@aztec/foundation/sleep';
7
- import { DateProvider } from '@aztec/foundation/timer';
8
- import { RollupAbi } from '@aztec/l1-artifacts/RollupAbi';
9
- import pickBy from 'lodash.pickby';
10
- import { MethodNotFoundRpcError, MethodNotSupportedRpcError, createNonceManager, decodeErrorResult, formatGwei, getContractError, hexToBytes, parseTransaction, serializeTransaction } from 'viem';
11
- import { jsonRpc } from 'viem/nonce';
12
- import { formatViemError } from './utils.js';
13
- // 1_000_000_000 Gwei = 1 ETH
14
- // 1_000_000_000 Wei = 1 Gwei
15
- // 1_000_000_000_000_000_000 Wei = 1 ETH
16
- const WEI_CONST = 1_000_000_000n;
17
- // @note using this large gas limit to avoid the issue of `gas limit too low` when estimating gas in reth
18
- const LARGE_GAS_LIMIT = 12_000_000n;
19
- // setting a minimum bump percentage to 10% due to geth's implementation
20
- // https://github.com/ethereum/go-ethereum/blob/e3d61e6db028c412f74bc4d4c7e117a9e29d0de0/core/txpool/legacypool/list.go#L298
21
- const MIN_REPLACEMENT_BUMP_PERCENTAGE = 10;
22
- // setting a minimum bump percentage to 100% due to geth's implementation
23
- // https://github.com/ethereum/go-ethereum/blob/e3d61e6db028c412f74bc4d4c7e117a9e29d0de0/core/txpool/blobpool/config.go#L34
24
- const MIN_BLOB_REPLACEMENT_BUMP_PERCENTAGE = 100;
25
- // Avg ethereum block time is ~12s
26
- const BLOCK_TIME_MS = 12_000;
27
- export const l1TxUtilsConfigMappings = {
28
- gasLimitBufferPercentage: {
29
- description: 'How much to increase calculated gas limit by (percentage)',
30
- env: 'L1_GAS_LIMIT_BUFFER_PERCENTAGE',
31
- ...numberConfigHelper(20)
32
- },
33
- maxGwei: {
34
- description: 'Maximum gas price in gwei',
35
- env: 'L1_GAS_PRICE_MAX',
36
- ...bigintConfigHelper(500n)
37
- },
38
- maxBlobGwei: {
39
- description: 'Maximum blob fee per gas in gwei',
40
- env: 'L1_BLOB_FEE_PER_GAS_MAX',
41
- ...bigintConfigHelper(1_500n)
42
- },
43
- priorityFeeBumpPercentage: {
44
- description: 'How much to increase priority fee by each attempt (percentage)',
45
- env: 'L1_PRIORITY_FEE_BUMP_PERCENTAGE',
46
- ...numberConfigHelper(20)
47
- },
48
- priorityFeeRetryBumpPercentage: {
49
- description: 'How much to increase priority fee by each retry attempt (percentage)',
50
- env: 'L1_PRIORITY_FEE_RETRY_BUMP_PERCENTAGE',
51
- ...numberConfigHelper(50)
52
- },
53
- fixedPriorityFeePerGas: {
54
- description: 'Fixed priority fee per gas in Gwei. Overrides any priority fee bump percentage',
55
- env: 'L1_FIXED_PRIORITY_FEE_PER_GAS',
56
- ...numberConfigHelper(0)
57
- },
58
- maxAttempts: {
59
- description: 'Maximum number of speed-up attempts',
60
- env: 'L1_TX_MONITOR_MAX_ATTEMPTS',
61
- ...numberConfigHelper(3)
62
- },
63
- checkIntervalMs: {
64
- description: 'How often to check tx status',
65
- env: 'L1_TX_MONITOR_CHECK_INTERVAL_MS',
66
- ...numberConfigHelper(1_000)
67
- },
68
- stallTimeMs: {
69
- description: 'How long before considering tx stalled',
70
- env: 'L1_TX_MONITOR_STALL_TIME_MS',
71
- ...numberConfigHelper(24_000)
72
- },
73
- txTimeoutMs: {
74
- description: 'How long to wait for a tx to be mined before giving up. Set to 0 to disable.',
75
- env: 'L1_TX_MONITOR_TX_TIMEOUT_MS',
76
- ...numberConfigHelper(120_000)
77
- },
78
- txPropagationMaxQueryAttempts: {
79
- description: 'How many attempts will be done to get a tx after it was sent',
80
- env: 'L1_TX_PROPAGATION_MAX_QUERY_ATTEMPTS',
81
- ...numberConfigHelper(3)
82
- },
83
- cancelTxOnTimeout: {
84
- description: "Whether to attempt to cancel a tx if it's not mined after txTimeoutMs",
85
- env: 'L1_TX_MONITOR_CANCEL_TX_ON_TIMEOUT',
86
- ...booleanConfigHelper(true)
87
- }
88
- };
89
- export const defaultL1TxUtilsConfig = getDefaultConfig(l1TxUtilsConfigMappings);
90
- export function getL1TxUtilsConfigEnvVars() {
91
- return getConfigFromMappings(l1TxUtilsConfigMappings);
92
- }
93
- export var TxUtilsState = /*#__PURE__*/ function(TxUtilsState) {
94
- TxUtilsState[TxUtilsState["IDLE"] = 0] = "IDLE";
95
- TxUtilsState[TxUtilsState["SENT"] = 1] = "SENT";
96
- TxUtilsState[TxUtilsState["SPEED_UP"] = 2] = "SPEED_UP";
97
- TxUtilsState[TxUtilsState["CANCELLED"] = 3] = "CANCELLED";
98
- TxUtilsState[TxUtilsState["NOT_MINED"] = 4] = "NOT_MINED";
99
- TxUtilsState[TxUtilsState["MINED"] = 5] = "MINED";
100
- return TxUtilsState;
101
- }({});
102
- export class ReadOnlyL1TxUtils {
103
- client;
104
- logger;
105
- dateProvider;
106
- debugMaxGasLimit;
107
- config;
108
- interrupted;
109
- constructor(client, logger = createLogger('ReadOnlyL1TxUtils'), dateProvider, config, debugMaxGasLimit = false){
110
- this.client = client;
111
- this.logger = logger;
112
- this.dateProvider = dateProvider;
113
- this.debugMaxGasLimit = debugMaxGasLimit;
114
- this.interrupted = false;
115
- this.config = {
116
- ...defaultL1TxUtilsConfig,
117
- ...config || {}
118
- };
119
- }
120
- interrupt() {
121
- this.interrupted = true;
122
- }
123
- restart() {
124
- this.interrupted = false;
125
- }
126
- getBlock() {
127
- return this.client.getBlock();
128
- }
129
- getBlockNumber() {
130
- return this.client.getBlockNumber();
131
- }
132
- /**
133
- * Gets the current gas price with bounds checking
134
- */ async getGasPrice(_gasConfig, isBlobTx = false, attempt = 0, previousGasPrice) {
135
- const gasConfig = {
136
- ...this.config,
137
- ..._gasConfig
138
- };
139
- const block = await this.client.getBlock({
140
- blockTag: 'latest'
141
- });
142
- const baseFee = block.baseFeePerGas ?? 0n;
143
- // Get blob base fee if available
144
- let blobBaseFee = 0n;
145
- if (isBlobTx) {
146
- try {
147
- blobBaseFee = await retry(()=>this.client.getBlobBaseFee(), 'Getting L1 blob base fee', makeBackoff(times(2, ()=>1)), this.logger, true);
148
- this.logger?.debug('L1 Blob base fee:', {
149
- blobBaseFee: formatGwei(blobBaseFee)
150
- });
151
- } catch {
152
- this.logger?.warn('Failed to get L1 blob base fee', attempt);
153
- }
154
- }
155
- let priorityFee;
156
- if (gasConfig.fixedPriorityFeePerGas) {
157
- this.logger?.debug('Using fixed priority fee per L1 gas', {
158
- fixedPriorityFeePerGas: gasConfig.fixedPriorityFeePerGas
159
- });
160
- // try to maintain precision up to 1000000 wei
161
- priorityFee = BigInt(gasConfig.fixedPriorityFeePerGas * 1_000_000) * (WEI_CONST / 1_000_000n);
162
- } else {
163
- // Get initial priority fee from the network
164
- priorityFee = await this.client.estimateMaxPriorityFeePerGas();
165
- }
166
- let maxFeePerGas = baseFee;
167
- let maxFeePerBlobGas = blobBaseFee;
168
- // Bump base fee so it's valid for next blocks if it stalls
169
- const numBlocks = Math.ceil(gasConfig.stallTimeMs / BLOCK_TIME_MS);
170
- for(let i = 0; i < numBlocks; i++){
171
- // each block can go up 12.5% from previous baseFee
172
- maxFeePerGas = maxFeePerGas * (1_000n + 125n) / 1_000n;
173
- // same for blob gas fee
174
- maxFeePerBlobGas = maxFeePerBlobGas * (1_000n + 125n) / 1_000n;
175
- }
176
- if (attempt > 0) {
177
- const configBump = gasConfig.priorityFeeRetryBumpPercentage ?? defaultL1TxUtilsConfig.priorityFeeRetryBumpPercentage;
178
- // if this is a blob tx, we have to use the blob bump percentage
179
- const minBumpPercentage = isBlobTx ? MIN_BLOB_REPLACEMENT_BUMP_PERCENTAGE : MIN_REPLACEMENT_BUMP_PERCENTAGE;
180
- const bumpPercentage = configBump > minBumpPercentage ? configBump : minBumpPercentage;
181
- // Calculate minimum required fees based on previous attempt
182
- // multiply by 100 & divide by 100 to maintain some precision
183
- const minPriorityFee = previousGasPrice.maxPriorityFeePerGas * (100_00n + BigInt(bumpPercentage * 1_00)) / 100_00n;
184
- const minMaxFee = previousGasPrice.maxFeePerGas * (100_00n + BigInt(bumpPercentage * 1_00)) / 100_00n;
185
- // Add priority fee to maxFeePerGas
186
- maxFeePerGas += priorityFee;
187
- // Use maximum between current network values and minimum required values
188
- priorityFee = priorityFee > minPriorityFee ? priorityFee : minPriorityFee;
189
- maxFeePerGas = maxFeePerGas > minMaxFee ? maxFeePerGas : minMaxFee;
190
- } else {
191
- // first attempt, just bump priority fee, unless it's a fixed config
192
- // multiply by 100 & divide by 100 to maintain some precision
193
- if (!gasConfig.fixedPriorityFeePerGas) {
194
- priorityFee = priorityFee * (100_00n + BigInt((gasConfig.priorityFeeBumpPercentage || 0) * 1_00)) / 100_00n;
195
- }
196
- maxFeePerGas += priorityFee;
197
- }
198
- // Ensure we don't exceed maxGwei
199
- const maxGweiInWei = gasConfig.maxGwei * WEI_CONST;
200
- maxFeePerGas = maxFeePerGas > maxGweiInWei ? maxGweiInWei : maxFeePerGas;
201
- // Ensure we don't exceed maxBlobGwei
202
- if (maxFeePerBlobGas) {
203
- const maxBlobGweiInWei = gasConfig.maxBlobGwei * WEI_CONST;
204
- maxFeePerBlobGas = maxFeePerBlobGas > maxBlobGweiInWei ? maxBlobGweiInWei : maxFeePerBlobGas;
205
- }
206
- // Ensure priority fee doesn't exceed max fee
207
- const maxPriorityFeePerGas = priorityFee > maxFeePerGas ? maxFeePerGas : priorityFee;
208
- if (attempt > 0 && previousGasPrice?.maxFeePerBlobGas) {
209
- const bumpPercentage = gasConfig.priorityFeeRetryBumpPercentage > MIN_BLOB_REPLACEMENT_BUMP_PERCENTAGE ? gasConfig.priorityFeeRetryBumpPercentage : MIN_BLOB_REPLACEMENT_BUMP_PERCENTAGE;
210
- // calculate min blob fee based on previous attempt
211
- const minBlobFee = previousGasPrice.maxFeePerBlobGas * (100_00n + BigInt(bumpPercentage * 1_00)) / 100_00n;
212
- // use max between current network values and min required values
213
- maxFeePerBlobGas = maxFeePerBlobGas > minBlobFee ? maxFeePerBlobGas : minBlobFee;
214
- }
215
- this.logger?.debug(`Computed L1 gas price`, {
216
- attempt,
217
- baseFee: formatGwei(baseFee),
218
- maxFeePerGas: formatGwei(maxFeePerGas),
219
- maxPriorityFeePerGas: formatGwei(maxPriorityFeePerGas),
220
- ...maxFeePerBlobGas && {
221
- maxFeePerBlobGas: formatGwei(maxFeePerBlobGas)
222
- }
223
- });
224
- return {
225
- maxFeePerGas,
226
- maxPriorityFeePerGas,
227
- ...maxFeePerBlobGas && {
228
- maxFeePerBlobGas: maxFeePerBlobGas
229
- }
230
- };
231
- }
232
- /**
233
- * Estimates gas and adds buffer
234
- */ async estimateGas(account, request, _gasConfig, _blobInputs) {
235
- const gasConfig = {
236
- ...this.config,
237
- ..._gasConfig
238
- };
239
- let initialEstimate = 0n;
240
- if (_blobInputs) {
241
- // @note requests with blobs also require maxFeePerBlobGas to be set
242
- const gasPrice = await this.getGasPrice(gasConfig, true, 0);
243
- initialEstimate = await this.client.estimateGas({
244
- account,
245
- ...request,
246
- ..._blobInputs,
247
- maxFeePerBlobGas: gasPrice.maxFeePerBlobGas,
248
- gas: LARGE_GAS_LIMIT
249
- });
250
- this.logger?.debug(`L1 gas used in estimateGas by blob tx: ${initialEstimate}`);
251
- } else {
252
- initialEstimate = await this.client.estimateGas({
253
- account,
254
- ...request,
255
- gas: LARGE_GAS_LIMIT
256
- });
257
- this.logger?.debug(`L1 gas used in estimateGas by non-blob tx: ${initialEstimate}`);
258
- }
259
- // Add buffer based on either fixed amount or percentage
260
- const withBuffer = this.bumpGasLimit(initialEstimate, gasConfig);
261
- return withBuffer;
262
- }
263
- async getTransactionStats(txHash) {
264
- const tx = await this.client.getTransaction({
265
- hash: txHash
266
- });
267
- if (!tx) {
268
- return undefined;
269
- }
270
- const calldata = hexToBytes(tx.input);
271
- return {
272
- sender: tx.from.toString(),
273
- transactionHash: tx.hash,
274
- calldataSize: calldata.length,
275
- calldataGas: getCalldataGasUsage(calldata)
276
- };
277
- }
278
- async tryGetErrorFromRevertedTx(data, args, blobInputs, stateOverride = []) {
279
- try {
280
- await this.client.simulateContract({
281
- ...args,
282
- account: this.client.account,
283
- stateOverride
284
- });
285
- this.logger?.trace('Simulated blob tx', {
286
- blobInputs
287
- });
288
- // If the above passes, we have a blob error. We cannot simulate blob txs, and failed txs no longer throw errors.
289
- // Strangely, the only way to throw the revert reason as an error and provide blobs is prepareTransactionRequest.
290
- // See: https://github.com/wevm/viem/issues/2075
291
- // This throws a EstimateGasExecutionError with the custom error information:
292
- const request = blobInputs ? {
293
- account: this.client.account,
294
- to: args.address,
295
- data,
296
- blobs: blobInputs.blobs,
297
- kzg: blobInputs.kzg,
298
- maxFeePerBlobGas: blobInputs.maxFeePerBlobGas
299
- } : {
300
- account: this.client.account,
301
- to: args.address,
302
- data
303
- };
304
- this.logger?.trace('Preparing tx', {
305
- request
306
- });
307
- await this.client.prepareTransactionRequest(request);
308
- this.logger?.trace('Prepared tx');
309
- return undefined;
310
- } catch (simulationErr) {
311
- // If we don't have a ContractFunctionExecutionError, we have a blob related error => use getContractError to get the error msg.
312
- const contractErr = simulationErr.name === 'ContractFunctionExecutionError' ? simulationErr : getContractError(simulationErr, {
313
- args: [],
314
- abi: args.abi,
315
- functionName: args.functionName,
316
- address: args.address
317
- });
318
- if (contractErr.name === 'ContractFunctionExecutionError') {
319
- const execErr = contractErr;
320
- return tryGetCustomErrorNameContractFunction(execErr);
321
- }
322
- this.logger?.error(`Error getting error from simulation`, simulationErr);
323
- }
324
- }
325
- async simulate(request, blockOverrides = {}, stateOverrides = [], abi = RollupAbi, _gasConfig) {
326
- const gasConfig = {
327
- ...this.config,
328
- ..._gasConfig
329
- };
330
- const call = {
331
- to: request.to,
332
- data: request.data,
333
- ...request.from && {
334
- from: request.from
335
- }
336
- };
337
- return await this._simulate(call, blockOverrides, stateOverrides, gasConfig, abi);
338
- }
339
- async _simulate(call, blockOverrides = {}, stateOverrides = [], gasConfig, abi) {
340
- try {
341
- const result = await this.client.simulateBlocks({
342
- validation: false,
343
- blocks: [
344
- {
345
- blockOverrides,
346
- stateOverrides,
347
- calls: [
348
- call
349
- ]
350
- }
351
- ]
352
- });
353
- if (result[0].calls[0].status === 'failure') {
354
- this.logger?.error('L1 transaction simulation failed', result[0].calls[0].error);
355
- const decodedError = decodeErrorResult({
356
- abi,
357
- data: result[0].calls[0].data
358
- });
359
- throw new Error(`L1 transaction simulation failed with error ${decodedError.errorName}(${decodedError.args?.join(',')})`);
360
- }
361
- this.logger?.debug(`L1 transaction simulation succeeded`, {
362
- ...result[0].calls[0]
363
- });
364
- return {
365
- gasUsed: result[0].gasUsed,
366
- result: result[0].calls[0].data
367
- };
368
- } catch (err) {
369
- if (err instanceof MethodNotFoundRpcError || err instanceof MethodNotSupportedRpcError) {
370
- if (gasConfig.fallbackGasEstimate) {
371
- this.logger?.warn(`Node does not support eth_simulateV1 API. Using fallback gas estimate: ${gasConfig.fallbackGasEstimate}`);
372
- return {
373
- gasUsed: gasConfig.fallbackGasEstimate,
374
- result: '0x'
375
- };
376
- }
377
- this.logger?.error('Node does not support eth_simulateV1 API');
378
- }
379
- throw err;
380
- }
381
- }
382
- bumpGasLimit(gasLimit, _gasConfig) {
383
- const gasConfig = {
384
- ...this.config,
385
- ..._gasConfig
386
- };
387
- const bumpedGasLimit = gasLimit + gasLimit * BigInt((gasConfig?.gasLimitBufferPercentage || 0) * 1_00) / 100_00n;
388
- const cleanGasConfig = pickBy(gasConfig, (_, key)=>key in l1TxUtilsConfigMappings);
389
- this.logger?.debug('Bumping gas limit', {
390
- gasLimit,
391
- gasConfig: cleanGasConfig,
392
- bumpedGasLimit
393
- });
394
- return bumpedGasLimit;
395
- }
396
- }
397
- export class L1TxUtils extends ReadOnlyL1TxUtils {
398
- client;
399
- address;
400
- signer;
401
- logger;
402
- txUtilsState;
403
- lastMinedBlockNumber;
404
- nonceManager;
405
- constructor(client, address, signer, logger = createLogger('L1TxUtils'), dateProvider = new DateProvider(), config, debugMaxGasLimit = false){
406
- super(client, logger, dateProvider, config, debugMaxGasLimit), this.client = client, this.address = address, this.signer = signer, this.logger = logger, this.txUtilsState = 0, this.lastMinedBlockNumber = undefined;
407
- this.nonceManager = createNonceManager({
408
- source: jsonRpc()
409
- });
410
- }
411
- get state() {
412
- return this.txUtilsState;
413
- }
414
- get lastMinedAtBlockNumber() {
415
- return this.lastMinedBlockNumber;
416
- }
417
- set lastMinedAtBlockNumber(blockNumber) {
418
- this.lastMinedBlockNumber = blockNumber;
419
- }
420
- set state(state) {
421
- this.txUtilsState = state;
422
- this.logger?.debug(`L1TxUtils state changed to ${TxUtilsState[state]} for sender: ${this.getSenderAddress().toString()}`);
423
- }
424
- getSenderAddress() {
425
- return this.address;
426
- }
427
- getSenderBalance() {
428
- return this.client.getBalance({
429
- address: this.getSenderAddress().toString()
430
- });
431
- }
432
- async signTransaction(txRequest) {
433
- const signature = await this.signer(txRequest, this.getSenderAddress());
434
- return serializeTransaction(txRequest, signature);
435
- }
436
- async prepareSignedTransaction(txData) {
437
- const txRequest = await this.client.prepareTransactionRequest(txData);
438
- return await this.signTransaction(txRequest);
439
- }
440
- /**
441
- * Sends a transaction with gas estimation and pricing
442
- * @param request - The transaction request (to, data, value)
443
- * @param gasConfig - Optional gas configuration
444
- * @returns The transaction hash and parameters used
445
- */ async sendTransaction(request, _gasConfig, blobInputs, stateChange = 1) {
446
- try {
447
- const gasConfig = {
448
- ...this.config,
449
- ..._gasConfig
450
- };
451
- const account = this.getSenderAddress().toString();
452
- let gasLimit;
453
- if (this.debugMaxGasLimit) {
454
- gasLimit = LARGE_GAS_LIMIT;
455
- } else if (gasConfig.gasLimit) {
456
- gasLimit = gasConfig.gasLimit;
457
- } else {
458
- gasLimit = await this.estimateGas(account, request, gasConfig);
459
- }
460
- this.logger?.debug(`Gas limit for request is ${gasLimit}`, {
461
- gasLimit,
462
- ...request
463
- });
464
- const gasPrice = await this.getGasPrice(gasConfig, !!blobInputs);
465
- if (gasConfig.txTimeoutAt && this.dateProvider.now() > gasConfig.txTimeoutAt.getTime()) {
466
- throw new Error('Transaction timed out before sending');
467
- }
468
- const nonce = await this.nonceManager.consume({
469
- client: this.client,
470
- address: account,
471
- chainId: this.client.chain.id
472
- });
473
- let txHash;
474
- if (blobInputs) {
475
- const txData = {
476
- ...request,
477
- ...blobInputs,
478
- gas: gasLimit,
479
- maxFeePerGas: gasPrice.maxFeePerGas,
480
- maxPriorityFeePerGas: gasPrice.maxPriorityFeePerGas,
481
- maxFeePerBlobGas: gasPrice.maxFeePerBlobGas,
482
- nonce
483
- };
484
- const signedRequest = await this.prepareSignedTransaction(txData);
485
- txHash = await this.client.sendRawTransaction({
486
- serializedTransaction: signedRequest
487
- });
488
- } else {
489
- const txData = {
490
- ...request,
491
- gas: gasLimit,
492
- maxFeePerGas: gasPrice.maxFeePerGas,
493
- maxPriorityFeePerGas: gasPrice.maxPriorityFeePerGas,
494
- nonce
495
- };
496
- const signedRequest = await this.prepareSignedTransaction(txData);
497
- txHash = await this.client.sendRawTransaction({
498
- serializedTransaction: signedRequest
499
- });
500
- }
501
- this.state = stateChange;
502
- const cleanGasConfig = pickBy(gasConfig, (_, key)=>key in l1TxUtilsConfigMappings);
503
- this.logger?.info(`Sent L1 transaction ${txHash}`, {
504
- gasLimit,
505
- maxFeePerGas: formatGwei(gasPrice.maxFeePerGas),
506
- maxPriorityFeePerGas: formatGwei(gasPrice.maxPriorityFeePerGas),
507
- gasConfig: cleanGasConfig,
508
- ...gasPrice.maxFeePerBlobGas && {
509
- maxFeePerBlobGas: formatGwei(gasPrice.maxFeePerBlobGas)
510
- }
511
- });
512
- return {
513
- txHash,
514
- gasLimit,
515
- gasPrice
516
- };
517
- } catch (err) {
518
- const viemError = formatViemError(err, request.abi);
519
- this.logger?.error(`Failed to send L1 transaction`, viemError.message, {
520
- metaMessages: viemError.metaMessages
521
- });
522
- throw viemError;
523
- }
524
- }
525
- /**
526
- * Monitors a transaction until completion, handling speed-ups if needed
527
- * @param request - Original transaction request (needed for speed-ups)
528
- * @param initialTxHash - Hash of the initial transaction
529
- * @param allVersions - Hashes of all transactions submitted under the same nonce (any of them could mine)
530
- * @param params - Parameters used in the initial transaction
531
- * @param gasConfig - Optional gas configuration
532
- */ async monitorTransaction(request, initialTxHash, allVersions, params, _gasConfig, _blobInputs, isCancelTx = false) {
533
- const isBlobTx = !!_blobInputs;
534
- const gasConfig = {
535
- ...this.config,
536
- ..._gasConfig
537
- };
538
- const account = this.getSenderAddress().toString();
539
- const blobInputs = _blobInputs || {};
540
- const makeGetTransactionBackoff = ()=>makeBackoff(times(gasConfig.txPropagationMaxQueryAttempts ?? 3, (i)=>i + 1));
541
- // Retry a few times, in case the tx is not yet propagated.
542
- const tx = await retry(()=>this.client.getTransaction({
543
- hash: initialTxHash
544
- }), `Getting L1 transaction ${initialTxHash}`, makeGetTransactionBackoff(), this.logger, true);
545
- if (!tx) {
546
- throw new Error(`Failed to get L1 transaction ${initialTxHash} to monitor`);
547
- }
548
- if (tx?.nonce === undefined || tx?.nonce === null) {
549
- throw new Error(`Failed to get L1 transaction ${initialTxHash} nonce`);
550
- }
551
- const nonce = tx.nonce;
552
- allVersions.add(initialTxHash);
553
- let currentTxHash = initialTxHash;
554
- let attempts = 0;
555
- let lastAttemptSent = this.dateProvider.now();
556
- let lastGasPrice = {
557
- maxFeePerGas: tx.maxFeePerGas,
558
- maxPriorityFeePerGas: tx.maxPriorityFeePerGas,
559
- maxFeePerBlobGas: tx.maxFeePerBlobGas
560
- };
561
- const initialTxTime = lastAttemptSent;
562
- let txTimedOut = false;
563
- let latestBlockTimestamp;
564
- // We check against the latestBlockTimestamp as opposed to the current time to avoid a race condition where
565
- // the tx is mined in a block with the same timestamp as txTimeoutAt, but our execution node has not yet processed it,
566
- // or the loop here has not yet checked the tx before that timeout.
567
- const isTimedOut = ()=>gasConfig.txTimeoutAt && latestBlockTimestamp !== undefined && Number(latestBlockTimestamp) * 1000 >= gasConfig.txTimeoutAt.getTime() || gasConfig.txTimeoutMs !== undefined && this.dateProvider.now() - initialTxTime > gasConfig.txTimeoutMs || this.interrupted || false;
568
- while(!txTimedOut){
569
- try {
570
- ({ timestamp: latestBlockTimestamp } = await this.client.getBlock({
571
- blockTag: 'latest',
572
- includeTransactions: false
573
- }));
574
- const currentNonce = await this.client.getTransactionCount({
575
- address: account
576
- });
577
- // If the current nonce on our account is greater than our transaction's nonce then a tx with the same nonce has been mined.
578
- if (currentNonce > nonce) {
579
- for (const hash of allVersions){
580
- try {
581
- const receipt = await this.client.getTransactionReceipt({
582
- hash
583
- });
584
- if (receipt) {
585
- if (receipt.status === 'reverted') {
586
- this.logger?.error(`L1 transaction ${hash} reverted`, receipt);
587
- } else {
588
- this.logger?.debug(`L1 transaction ${hash} mined`);
589
- }
590
- this.state = 5;
591
- this.lastMinedAtBlockNumber = receipt.blockNumber;
592
- return receipt;
593
- }
594
- } catch (err) {
595
- if (err instanceof Error && err.message.includes('reverted')) {
596
- throw formatViemError(err);
597
- }
598
- }
599
- }
600
- // If we get here then we have checked all of our tx versions and not found anything.
601
- // We should consider the nonce as MINED
602
- this.state = 5;
603
- throw new Error(`Nonce ${nonce} is MINED but not by one of our expected transactions`);
604
- }
605
- this.logger?.trace(`Tx timeout check for ${currentTxHash}: ${isTimedOut()}`, {
606
- latestBlockTimestamp: Number(latestBlockTimestamp) * 1000,
607
- lastAttemptSent,
608
- initialTxTime,
609
- now: this.dateProvider.now(),
610
- txTimeoutAt: gasConfig.txTimeoutAt?.getTime(),
611
- txTimeoutMs: gasConfig.txTimeoutMs,
612
- txStallTime: gasConfig.stallTimeMs
613
- });
614
- // Retry a few times, in case the tx is not yet propagated.
615
- const tx = await retry(()=>this.client.getTransaction({
616
- hash: currentTxHash
617
- }), `Getting L1 transaction ${currentTxHash}`, makeGetTransactionBackoff(), this.logger, true);
618
- const timePassed = this.dateProvider.now() - lastAttemptSent;
619
- if (tx && timePassed < gasConfig.stallTimeMs) {
620
- this.logger?.debug(`L1 transaction ${currentTxHash} pending. Time passed: ${timePassed}ms.`);
621
- // Check timeout before continuing
622
- txTimedOut = isTimedOut();
623
- if (txTimedOut) {
624
- break;
625
- }
626
- await sleep(gasConfig.checkIntervalMs);
627
- continue;
628
- }
629
- if (timePassed > gasConfig.stallTimeMs && attempts < gasConfig.maxAttempts) {
630
- attempts++;
631
- const newGasPrice = await this.getGasPrice(gasConfig, isBlobTx, attempts, tx.maxFeePerGas && tx.maxPriorityFeePerGas ? {
632
- maxFeePerGas: tx.maxFeePerGas,
633
- maxPriorityFeePerGas: tx.maxPriorityFeePerGas,
634
- maxFeePerBlobGas: tx.maxFeePerBlobGas
635
- } : undefined);
636
- lastGasPrice = newGasPrice;
637
- this.logger?.debug(`L1 transaction ${currentTxHash} appears stuck. Attempting speed-up ${attempts}/${gasConfig.maxAttempts} ` + `with new priority fee ${formatGwei(newGasPrice.maxPriorityFeePerGas)} gwei`, {
638
- maxFeePerGas: formatGwei(newGasPrice.maxFeePerGas),
639
- maxPriorityFeePerGas: formatGwei(newGasPrice.maxPriorityFeePerGas),
640
- ...newGasPrice.maxFeePerBlobGas && {
641
- maxFeePerBlobGas: formatGwei(newGasPrice.maxFeePerBlobGas)
642
- }
643
- });
644
- const txData = {
645
- ...request,
646
- ...blobInputs,
647
- nonce,
648
- gas: params.gasLimit,
649
- maxFeePerGas: newGasPrice.maxFeePerGas,
650
- maxPriorityFeePerGas: newGasPrice.maxPriorityFeePerGas
651
- };
652
- if (isBlobTx && newGasPrice.maxFeePerBlobGas) {
653
- txData.maxFeePerBlobGas = newGasPrice.maxFeePerBlobGas;
654
- }
655
- const signedRequest = await this.prepareSignedTransaction(txData);
656
- const newHash = await this.client.sendRawTransaction({
657
- serializedTransaction: signedRequest
658
- });
659
- if (!isCancelTx) {
660
- this.state = 2;
661
- }
662
- const cleanGasConfig = pickBy(gasConfig, (_, key)=>key in l1TxUtilsConfigMappings);
663
- this.logger?.verbose(`Sent L1 speed-up tx ${newHash}, replacing ${currentTxHash}`, {
664
- gasLimit: params.gasLimit,
665
- maxFeePerGas: formatGwei(newGasPrice.maxFeePerGas),
666
- maxPriorityFeePerGas: formatGwei(newGasPrice.maxPriorityFeePerGas),
667
- gasConfig: cleanGasConfig,
668
- ...newGasPrice.maxFeePerBlobGas && {
669
- maxFeePerBlobGas: formatGwei(newGasPrice.maxFeePerBlobGas)
670
- }
671
- });
672
- currentTxHash = newHash;
673
- allVersions.add(currentTxHash);
674
- lastAttemptSent = this.dateProvider.now();
675
- }
676
- await sleep(gasConfig.checkIntervalMs);
677
- } catch (err) {
678
- const viemError = formatViemError(err);
679
- this.logger?.warn(`Error monitoring L1 transaction ${currentTxHash}:`, viemError.message);
680
- if (viemError.message?.includes('reverted')) {
681
- throw viemError;
682
- }
683
- await sleep(gasConfig.checkIntervalMs);
684
- }
685
- // Check if tx has timed out.
686
- txTimedOut = isTimedOut();
687
- }
688
- // The transaction has timed out. If it's a cancellation then we are giving up on it.
689
- // Otherwise we may attempt to cancel it if configured to do so.
690
- if (isCancelTx) {
691
- this.state = 4;
692
- } else if (gasConfig.cancelTxOnTimeout) {
693
- // Fire cancellation without awaiting to avoid blocking the main thread
694
- this.attemptTxCancellation(currentTxHash, nonce, allVersions, isBlobTx, lastGasPrice, attempts).catch((err)=>{
695
- const viemError = formatViemError(err);
696
- this.logger?.error(`Failed to send cancellation for timed out tx ${currentTxHash}:`, viemError.message, {
697
- metaMessages: viemError.metaMessages
698
- });
699
- });
700
- }
701
- this.logger?.error(`L1 transaction ${currentTxHash} timed out`, {
702
- txHash: currentTxHash,
703
- txTimeoutAt: gasConfig.txTimeoutAt,
704
- txTimeoutMs: gasConfig.txTimeoutMs,
705
- txInitialTime: initialTxTime,
706
- now: this.dateProvider.now(),
707
- attempts,
708
- isInterrupted: this.interrupted,
709
- ...tx
710
- });
711
- throw new Error(`L1 transaction ${currentTxHash} timed out`);
712
- }
713
- /**
714
- * Sends a transaction and monitors it until completion
715
- * @param request - The transaction request (to, data, value)
716
- * @param gasConfig - Optional gas configuration
717
- * @returns The receipt of the successful transaction
718
- */ async sendAndMonitorTransaction(request, gasConfig, blobInputs) {
719
- const { txHash, gasLimit, gasPrice } = await this.sendTransaction(request, gasConfig, blobInputs);
720
- const receipt = await this.monitorTransaction(request, txHash, new Set(), {
721
- gasLimit
722
- }, gasConfig, blobInputs);
723
- return {
724
- receipt,
725
- gasPrice
726
- };
727
- }
728
- async simulate(request, _blockOverrides = {}, stateOverrides = [], abi = RollupAbi, _gasConfig) {
729
- const blockOverrides = {
730
- ..._blockOverrides
731
- };
732
- const gasConfig = {
733
- ...this.config,
734
- ..._gasConfig
735
- };
736
- const gasPrice = await this.getGasPrice(gasConfig, false);
737
- const call = {
738
- to: request.to,
739
- data: request.data,
740
- from: request.from ?? this.getSenderAddress().toString(),
741
- maxFeePerGas: gasPrice.maxFeePerGas,
742
- maxPriorityFeePerGas: gasPrice.maxPriorityFeePerGas,
743
- gas: request.gas ?? LARGE_GAS_LIMIT
744
- };
745
- if (!request.gas && !gasConfig.ignoreBlockGasLimit) {
746
- // LARGE_GAS_LIMIT is set as call.gas, increase block gasLimit
747
- blockOverrides.gasLimit = LARGE_GAS_LIMIT * 2n;
748
- }
749
- return this._simulate(call, blockOverrides, stateOverrides, gasConfig, abi);
750
- }
751
- /**
752
- * Attempts to cancel a transaction by sending a 0-value tx to self with same nonce but higher gas prices
753
- * @param nonce - The nonce of the transaction to cancel
754
- * @param allVersions - Hashes of all transactions submitted under the same nonce (any of them could mine)
755
- * @param previousGasPrice - The gas price of the previous transaction
756
- * @param attempts - The number of attempts to cancel the transaction
757
- * @returns The hash of the cancellation transaction
758
- */ async attemptTxCancellation(currentTxHash, nonce, allVersions, isBlobTx = false, previousGasPrice, attempts = 0) {
759
- if (isBlobTx) {
760
- throw new Error('Cannot cancel blob transactions, please use L1TxUtilsWithBlobsClass');
761
- }
762
- // Get gas price with higher priority fee for cancellation
763
- const cancelGasPrice = await this.getGasPrice({
764
- ...this.config,
765
- // Use high bump for cancellation to ensure it replaces the original tx
766
- priorityFeeRetryBumpPercentage: 150
767
- }, isBlobTx, attempts + 1, previousGasPrice);
768
- this.logger?.info(`Attempting to cancel L1 transaction ${currentTxHash} with nonce ${nonce}`, {
769
- maxFeePerGas: formatGwei(cancelGasPrice.maxFeePerGas),
770
- maxPriorityFeePerGas: formatGwei(cancelGasPrice.maxPriorityFeePerGas)
771
- });
772
- const request = {
773
- to: this.getSenderAddress().toString(),
774
- value: 0n
775
- };
776
- // Send 0-value tx to self with higher gas price
777
- const txData = {
778
- ...request,
779
- nonce,
780
- gas: 21_000n,
781
- maxFeePerGas: cancelGasPrice.maxFeePerGas,
782
- maxPriorityFeePerGas: cancelGasPrice.maxPriorityFeePerGas
783
- };
784
- const signedRequest = await this.prepareSignedTransaction(txData);
785
- const cancelTxHash = await this.client.sendRawTransaction({
786
- serializedTransaction: signedRequest
787
- });
788
- this.state = 3;
789
- this.logger?.info(`Sent cancellation tx ${cancelTxHash} for timed out tx ${currentTxHash}`, {
790
- nonce
791
- });
792
- const receipt = await this.monitorTransaction(request, cancelTxHash, allVersions, {
793
- gasLimit: 21_000n
794
- }, undefined, undefined, true);
795
- return receipt.transactionHash;
796
- }
797
- }
798
- export function createViemSigner(client) {
799
- const signer = async (tx, _address)=>{
800
- const signedTx = await client.signTransaction(tx);
801
- const parsed = parseTransaction(signedTx);
802
- if (!parsed.r || !parsed.s || parsed.yParity !== 0 && parsed.yParity !== 1) {
803
- throw new Error('Failed to extract signature from viem signed transaction');
804
- }
805
- return {
806
- r: parsed.r,
807
- s: parsed.s,
808
- yParity: parsed.yParity
809
- };
810
- };
811
- return signer;
812
- }
813
- export function createL1TxUtilsFromViemWallet(client, logger = createLogger('L1TxUtils'), dateProvider = new DateProvider(), config, debugMaxGasLimit = false) {
814
- return new L1TxUtils(client, EthAddress.fromString(client.account.address), createViemSigner(client), logger, dateProvider, config, debugMaxGasLimit);
815
- }
816
- export function createL1TxUtilsFromEthSigner(client, signer, logger = createLogger('L1TxUtils'), dateProvider = new DateProvider(), config, debugMaxGasLimit = false) {
817
- const callback = async (transaction, _signingAddress)=>{
818
- return (await signer.signTransaction(transaction)).toViemTransactionSignature();
819
- };
820
- return new L1TxUtils(client, signer.address, callback, logger, dateProvider, config, debugMaxGasLimit);
821
- }
822
- export function tryGetCustomErrorNameContractFunction(err) {
823
- return compactArray([
824
- err.shortMessage,
825
- ...(err.metaMessages ?? []).slice(0, 2).map((s)=>s.trim())
826
- ]).join(' ');
827
- }
828
- /*
829
- * Returns cost of calldata usage in Ethereum.
830
- * @param data - Calldata.
831
- * @returns 4 for each zero byte, 16 for each nonzero.
832
- */ export function getCalldataGasUsage(data) {
833
- return data.filter((byte)=>byte === 0).length * 4 + data.filter((byte)=>byte !== 0).length * 16;
834
- }