@aztec/ethereum 0.0.0-test.0 → 0.0.1-commit.21caa21

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 (232) hide show
  1. package/dest/account.d.ts +2 -0
  2. package/dest/account.d.ts.map +1 -0
  3. package/dest/account.js +4 -0
  4. package/dest/chain.d.ts +1 -1
  5. package/dest/client.d.ts +6 -4
  6. package/dest/client.d.ts.map +1 -1
  7. package/dest/client.js +16 -2
  8. package/dest/config.d.ts +111 -17
  9. package/dest/config.d.ts.map +1 -1
  10. package/dest/config.js +462 -22
  11. package/dest/constants.d.ts +1 -1
  12. package/dest/contracts/empire_base.d.ts +24 -8
  13. package/dest/contracts/empire_base.d.ts.map +1 -1
  14. package/dest/contracts/empire_base.js +75 -2
  15. package/dest/contracts/empire_slashing_proposer.d.ts +66 -0
  16. package/dest/contracts/empire_slashing_proposer.d.ts.map +1 -0
  17. package/dest/contracts/empire_slashing_proposer.js +200 -0
  18. package/dest/contracts/errors.d.ts +7 -0
  19. package/dest/contracts/errors.d.ts.map +1 -0
  20. package/dest/contracts/errors.js +12 -0
  21. package/dest/contracts/fee_asset_handler.d.ts +19 -0
  22. package/dest/contracts/fee_asset_handler.d.ts.map +1 -0
  23. package/dest/contracts/fee_asset_handler.js +57 -0
  24. package/dest/contracts/fee_juice.d.ts +6 -7
  25. package/dest/contracts/fee_juice.d.ts.map +1 -1
  26. package/dest/contracts/fee_juice.js +27 -20
  27. package/dest/contracts/governance.d.ts +43 -32
  28. package/dest/contracts/governance.d.ts.map +1 -1
  29. package/dest/contracts/governance.js +87 -84
  30. package/dest/contracts/governance_proposer.d.ts +16 -13
  31. package/dest/contracts/governance_proposer.d.ts.map +1 -1
  32. package/dest/contracts/governance_proposer.js +37 -17
  33. package/dest/contracts/gse.d.ts +32 -0
  34. package/dest/contracts/gse.d.ts.map +1 -0
  35. package/dest/contracts/gse.js +72 -0
  36. package/dest/contracts/inbox.d.ts +26 -0
  37. package/dest/contracts/inbox.d.ts.map +1 -0
  38. package/dest/contracts/inbox.js +45 -0
  39. package/dest/contracts/index.d.ts +9 -3
  40. package/dest/contracts/index.d.ts.map +1 -1
  41. package/dest/contracts/index.js +8 -2
  42. package/dest/contracts/multicall.d.ts +21 -0
  43. package/dest/contracts/multicall.d.ts.map +1 -0
  44. package/dest/contracts/multicall.js +156 -0
  45. package/dest/contracts/registry.d.ts +10 -5
  46. package/dest/contracts/registry.d.ts.map +1 -1
  47. package/dest/contracts/registry.js +44 -16
  48. package/dest/contracts/rollup.d.ts +204 -40
  49. package/dest/contracts/rollup.d.ts.map +1 -1
  50. package/dest/contracts/rollup.js +529 -79
  51. package/dest/contracts/slasher_contract.d.ts +44 -0
  52. package/dest/contracts/slasher_contract.d.ts.map +1 -0
  53. package/dest/contracts/slasher_contract.js +75 -0
  54. package/dest/contracts/tally_slashing_proposer.d.ts +139 -0
  55. package/dest/contracts/tally_slashing_proposer.d.ts.map +1 -0
  56. package/dest/contracts/tally_slashing_proposer.js +313 -0
  57. package/dest/contracts/utils.d.ts +3 -0
  58. package/dest/contracts/utils.d.ts.map +1 -0
  59. package/dest/contracts/utils.js +11 -0
  60. package/dest/deploy_l1_contracts.d.ts +577 -21114
  61. package/dest/deploy_l1_contracts.d.ts.map +1 -1
  62. package/dest/deploy_l1_contracts.js +1225 -421
  63. package/dest/eth-signer/eth-signer.d.ts +21 -0
  64. package/dest/eth-signer/eth-signer.d.ts.map +1 -0
  65. package/dest/eth-signer/eth-signer.js +5 -0
  66. package/dest/eth-signer/index.d.ts +2 -0
  67. package/dest/eth-signer/index.d.ts.map +1 -0
  68. package/dest/eth-signer/index.js +1 -0
  69. package/dest/index.d.ts +7 -3
  70. package/dest/index.d.ts.map +1 -1
  71. package/dest/index.js +6 -2
  72. package/dest/l1_artifacts.d.ts +77344 -0
  73. package/dest/l1_artifacts.d.ts.map +1 -0
  74. package/dest/l1_artifacts.js +166 -0
  75. package/dest/l1_contract_addresses.d.ts +24 -4
  76. package/dest/l1_contract_addresses.d.ts.map +1 -1
  77. package/dest/l1_contract_addresses.js +22 -18
  78. package/dest/l1_reader.d.ts +2 -2
  79. package/dest/l1_reader.d.ts.map +1 -1
  80. package/dest/l1_reader.js +8 -8
  81. package/dest/l1_tx_utils/config.d.ts +59 -0
  82. package/dest/l1_tx_utils/config.d.ts.map +1 -0
  83. package/dest/l1_tx_utils/config.js +82 -0
  84. package/dest/l1_tx_utils/constants.d.ts +6 -0
  85. package/dest/l1_tx_utils/constants.d.ts.map +1 -0
  86. package/dest/l1_tx_utils/constants.js +14 -0
  87. package/dest/l1_tx_utils/factory.d.ts +24 -0
  88. package/dest/l1_tx_utils/factory.d.ts.map +1 -0
  89. package/dest/l1_tx_utils/factory.js +12 -0
  90. package/dest/l1_tx_utils/index.d.ts +10 -0
  91. package/dest/l1_tx_utils/index.d.ts.map +1 -0
  92. package/dest/l1_tx_utils/index.js +10 -0
  93. package/dest/l1_tx_utils/interfaces.d.ts +76 -0
  94. package/dest/l1_tx_utils/interfaces.d.ts.map +1 -0
  95. package/dest/l1_tx_utils/interfaces.js +4 -0
  96. package/dest/l1_tx_utils/l1_tx_utils.d.ts +94 -0
  97. package/dest/l1_tx_utils/l1_tx_utils.d.ts.map +1 -0
  98. package/dest/l1_tx_utils/l1_tx_utils.js +610 -0
  99. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts +26 -0
  100. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.d.ts.map +1 -0
  101. package/dest/l1_tx_utils/l1_tx_utils_with_blobs.js +26 -0
  102. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts +94 -0
  103. package/dest/l1_tx_utils/readonly_l1_tx_utils.d.ts.map +1 -0
  104. package/dest/l1_tx_utils/readonly_l1_tx_utils.js +430 -0
  105. package/dest/l1_tx_utils/signer.d.ts +4 -0
  106. package/dest/l1_tx_utils/signer.d.ts.map +1 -0
  107. package/dest/l1_tx_utils/signer.js +16 -0
  108. package/dest/l1_tx_utils/types.d.ts +67 -0
  109. package/dest/l1_tx_utils/types.d.ts.map +1 -0
  110. package/dest/l1_tx_utils/types.js +26 -0
  111. package/dest/l1_tx_utils/utils.d.ts +4 -0
  112. package/dest/l1_tx_utils/utils.d.ts.map +1 -0
  113. package/dest/l1_tx_utils/utils.js +14 -0
  114. package/dest/l1_types.d.ts +6 -0
  115. package/dest/l1_types.d.ts.map +1 -0
  116. package/dest/l1_types.js +1 -0
  117. package/dest/publisher_manager.d.ts +15 -0
  118. package/dest/publisher_manager.d.ts.map +1 -0
  119. package/dest/publisher_manager.js +88 -0
  120. package/dest/queries.d.ts +4 -2
  121. package/dest/queries.d.ts.map +1 -1
  122. package/dest/queries.js +53 -12
  123. package/dest/test/chain_monitor.d.ts +73 -0
  124. package/dest/test/chain_monitor.d.ts.map +1 -0
  125. package/dest/test/chain_monitor.js +215 -0
  126. package/dest/test/delayed_tx_utils.d.ts +8 -3
  127. package/dest/test/delayed_tx_utils.d.ts.map +1 -1
  128. package/dest/test/delayed_tx_utils.js +13 -6
  129. package/dest/test/eth_cheat_codes.d.ts +217 -0
  130. package/dest/test/eth_cheat_codes.d.ts.map +1 -0
  131. package/dest/test/eth_cheat_codes.js +558 -0
  132. package/dest/test/eth_cheat_codes_with_state.d.ts +2 -2
  133. package/dest/test/eth_cheat_codes_with_state.d.ts.map +1 -1
  134. package/dest/test/eth_cheat_codes_with_state.js +1 -1
  135. package/dest/test/index.d.ts +4 -1
  136. package/dest/test/index.d.ts.map +1 -1
  137. package/dest/test/index.js +3 -0
  138. package/dest/test/rollup_cheat_codes.d.ts +87 -0
  139. package/dest/test/rollup_cheat_codes.d.ts.map +1 -0
  140. package/dest/test/rollup_cheat_codes.js +266 -0
  141. package/dest/test/start_anvil.d.ts +7 -1
  142. package/dest/test/start_anvil.d.ts.map +1 -1
  143. package/dest/test/start_anvil.js +16 -7
  144. package/dest/test/tx_delayer.d.ts +18 -7
  145. package/dest/test/tx_delayer.d.ts.map +1 -1
  146. package/dest/test/tx_delayer.js +95 -19
  147. package/dest/test/upgrade_utils.d.ts +6 -5
  148. package/dest/test/upgrade_utils.d.ts.map +1 -1
  149. package/dest/test/upgrade_utils.js +23 -16
  150. package/dest/types.d.ts +7 -8
  151. package/dest/types.d.ts.map +1 -1
  152. package/dest/types.js +3 -1
  153. package/dest/utils.d.ts +2 -1
  154. package/dest/utils.d.ts.map +1 -1
  155. package/dest/utils.js +43 -88
  156. package/dest/zkPassportVerifierAddress.d.ts +15 -0
  157. package/dest/zkPassportVerifierAddress.d.ts.map +1 -0
  158. package/dest/zkPassportVerifierAddress.js +11 -0
  159. package/package.json +28 -19
  160. package/src/account.ts +5 -0
  161. package/src/client.ts +42 -4
  162. package/src/config.ts +592 -31
  163. package/src/contracts/empire_base.ts +77 -7
  164. package/src/contracts/empire_slashing_proposer.ts +265 -0
  165. package/src/contracts/errors.ts +13 -0
  166. package/src/contracts/fee_asset_handler.ts +63 -0
  167. package/src/contracts/fee_juice.ts +29 -15
  168. package/src/contracts/governance.ts +80 -77
  169. package/src/contracts/governance_proposer.ts +66 -24
  170. package/src/contracts/gse.ts +88 -0
  171. package/src/contracts/inbox.ts +63 -0
  172. package/src/contracts/index.ts +8 -2
  173. package/src/contracts/multicall.ts +155 -0
  174. package/src/contracts/registry.ts +51 -26
  175. package/src/contracts/rollup.ts +596 -74
  176. package/src/contracts/slasher_contract.ts +89 -0
  177. package/src/contracts/tally_slashing_proposer.ts +316 -0
  178. package/src/contracts/utils.ts +14 -0
  179. package/src/deploy_l1_contracts.ts +1459 -538
  180. package/src/eth-signer/eth-signer.ts +25 -0
  181. package/src/eth-signer/index.ts +1 -0
  182. package/src/index.ts +6 -2
  183. package/src/l1_artifacts.ts +254 -0
  184. package/src/l1_contract_addresses.ts +32 -19
  185. package/src/l1_reader.ts +9 -9
  186. package/src/l1_tx_utils/README.md +177 -0
  187. package/src/l1_tx_utils/config.ts +143 -0
  188. package/src/l1_tx_utils/constants.ts +18 -0
  189. package/src/l1_tx_utils/factory.ts +64 -0
  190. package/src/l1_tx_utils/index.ts +12 -0
  191. package/src/l1_tx_utils/interfaces.ts +86 -0
  192. package/src/l1_tx_utils/l1_tx_utils.ts +718 -0
  193. package/src/l1_tx_utils/l1_tx_utils_with_blobs.ts +77 -0
  194. package/src/l1_tx_utils/readonly_l1_tx_utils.ts +558 -0
  195. package/src/l1_tx_utils/signer.ts +28 -0
  196. package/src/l1_tx_utils/types.ts +85 -0
  197. package/src/l1_tx_utils/utils.ts +16 -0
  198. package/src/l1_types.ts +6 -0
  199. package/src/publisher_manager.ts +106 -0
  200. package/src/queries.ts +73 -15
  201. package/src/test/chain_monitor.ts +243 -0
  202. package/src/test/delayed_tx_utils.ts +34 -6
  203. package/src/test/eth_cheat_codes.ts +588 -0
  204. package/src/test/eth_cheat_codes_with_state.ts +1 -1
  205. package/src/test/index.ts +3 -0
  206. package/src/test/rollup_cheat_codes.ts +307 -0
  207. package/src/test/start_anvil.ts +22 -5
  208. package/src/test/tx_delayer.ts +127 -26
  209. package/src/test/upgrade_utils.ts +30 -21
  210. package/src/types.ts +10 -8
  211. package/src/utils.ts +49 -90
  212. package/src/zkPassportVerifierAddress.ts +15 -0
  213. package/dest/contracts/forwarder.d.ts +0 -24
  214. package/dest/contracts/forwarder.d.ts.map +0 -1
  215. package/dest/contracts/forwarder.js +0 -101
  216. package/dest/contracts/slashing_proposer.d.ts +0 -21
  217. package/dest/contracts/slashing_proposer.d.ts.map +0 -1
  218. package/dest/contracts/slashing_proposer.js +0 -47
  219. package/dest/eth_cheat_codes.d.ts +0 -147
  220. package/dest/eth_cheat_codes.d.ts.map +0 -1
  221. package/dest/eth_cheat_codes.js +0 -303
  222. package/dest/l1_tx_utils.d.ts +0 -192
  223. package/dest/l1_tx_utils.d.ts.map +0 -1
  224. package/dest/l1_tx_utils.js +0 -641
  225. package/dest/l1_tx_utils_with_blobs.d.ts +0 -12
  226. package/dest/l1_tx_utils_with_blobs.d.ts.map +0 -1
  227. package/dest/l1_tx_utils_with_blobs.js +0 -64
  228. package/src/contracts/forwarder.ts +0 -132
  229. package/src/contracts/slashing_proposer.ts +0 -51
  230. package/src/eth_cheat_codes.ts +0 -314
  231. package/src/l1_tx_utils.ts +0 -847
  232. package/src/l1_tx_utils_with_blobs.ts +0 -86
@@ -0,0 +1,77 @@
1
+ import { Blob } from '@aztec/blob-lib';
2
+ import { EthAddress } from '@aztec/foundation/eth-address';
3
+ import type { Logger } from '@aztec/foundation/log';
4
+ import { DateProvider } from '@aztec/foundation/timer';
5
+
6
+ import type { TransactionSerializable } from 'viem';
7
+
8
+ import type { EthSigner } from '../eth-signer/eth-signer.js';
9
+ import type { ExtendedViemWalletClient, ViemClient } from '../types.js';
10
+ import type { L1TxUtilsConfig } from './config.js';
11
+ import type { IL1TxMetrics, IL1TxStore } from './interfaces.js';
12
+ import { L1TxUtils } from './l1_tx_utils.js';
13
+ import { createViemSigner } from './signer.js';
14
+ import type { L1BlobInputs, SigningCallback } from './types.js';
15
+
16
+ /** Extends L1TxUtils with the capability to cancel blobs. This needs to be a separate class so we don't require a dependency on blob-lib unnecessarily. */
17
+ export class L1TxUtilsWithBlobs extends L1TxUtils {
18
+ /** Makes empty blob inputs for the cancellation tx. */
19
+ protected override makeEmptyBlobInputs(maxFeePerBlobGas: bigint): Required<L1BlobInputs> {
20
+ const blobData = new Uint8Array(131072).fill(0);
21
+ const kzg = Blob.getViemKzgInstance();
22
+ return { blobs: [blobData], kzg, maxFeePerBlobGas };
23
+ }
24
+ }
25
+
26
+ export function createL1TxUtilsWithBlobsFromViemWallet(
27
+ client: ExtendedViemWalletClient,
28
+ deps: {
29
+ logger?: Logger;
30
+ dateProvider?: DateProvider;
31
+ store?: IL1TxStore;
32
+ metrics?: IL1TxMetrics;
33
+ } = {},
34
+ config: Partial<L1TxUtilsConfig> = {},
35
+ debugMaxGasLimit: boolean = false,
36
+ ) {
37
+ return new L1TxUtilsWithBlobs(
38
+ client,
39
+ EthAddress.fromString(client.account.address),
40
+ createViemSigner(client),
41
+ deps.logger,
42
+ deps.dateProvider,
43
+ config,
44
+ debugMaxGasLimit,
45
+ deps.store,
46
+ deps.metrics,
47
+ );
48
+ }
49
+
50
+ export function createL1TxUtilsWithBlobsFromEthSigner(
51
+ client: ViemClient,
52
+ signer: EthSigner,
53
+ deps: {
54
+ logger?: Logger;
55
+ dateProvider?: DateProvider;
56
+ store?: IL1TxStore;
57
+ metrics?: IL1TxMetrics;
58
+ } = {},
59
+ config: Partial<L1TxUtilsConfig> = {},
60
+ debugMaxGasLimit: boolean = false,
61
+ ) {
62
+ const callback: SigningCallback = async (transaction: TransactionSerializable, _signingAddress) => {
63
+ return (await signer.signTransaction(transaction)).toViemTransactionSignature();
64
+ };
65
+
66
+ return new L1TxUtilsWithBlobs(
67
+ client,
68
+ signer.address,
69
+ callback,
70
+ deps.logger,
71
+ deps.dateProvider,
72
+ config,
73
+ debugMaxGasLimit,
74
+ deps.store,
75
+ deps.metrics,
76
+ );
77
+ }
@@ -0,0 +1,558 @@
1
+ import { getKeys, median, merge, pick, times } from '@aztec/foundation/collection';
2
+ import { type Logger, createLogger } from '@aztec/foundation/log';
3
+ import { makeBackoff, retry } from '@aztec/foundation/retry';
4
+ import { DateProvider } from '@aztec/foundation/timer';
5
+ import { RollupAbi } from '@aztec/l1-artifacts/RollupAbi';
6
+
7
+ import pickBy from 'lodash.pickby';
8
+ import {
9
+ type Abi,
10
+ type Account,
11
+ type BaseError,
12
+ type BlockOverrides,
13
+ type ContractFunctionExecutionError,
14
+ type Hex,
15
+ MethodNotFoundRpcError,
16
+ MethodNotSupportedRpcError,
17
+ type StateOverride,
18
+ decodeErrorResult,
19
+ formatGwei,
20
+ getContractError,
21
+ hexToBytes,
22
+ } from 'viem';
23
+
24
+ import type { ViemClient } from '../types.js';
25
+ import { type L1TxUtilsConfig, defaultL1TxUtilsConfig, l1TxUtilsConfigMappings } from './config.js';
26
+ import {
27
+ BLOCK_TIME_MS,
28
+ LARGE_GAS_LIMIT,
29
+ MIN_BLOB_REPLACEMENT_BUMP_PERCENTAGE,
30
+ MIN_REPLACEMENT_BUMP_PERCENTAGE,
31
+ WEI_CONST,
32
+ } from './constants.js';
33
+ import type { GasPrice, L1BlobInputs, L1TxRequest, TransactionStats } from './types.js';
34
+ import { getCalldataGasUsage, tryGetCustomErrorNameContractFunction } from './utils.js';
35
+
36
+ const HISTORICAL_BLOCK_COUNT = 5;
37
+
38
+ export class ReadOnlyL1TxUtils {
39
+ public config: Required<L1TxUtilsConfig>;
40
+ protected interrupted = false;
41
+
42
+ constructor(
43
+ public client: ViemClient,
44
+ protected logger: Logger = createLogger('ethereum:readonly-l1-utils'),
45
+ public readonly dateProvider: DateProvider,
46
+ config?: Partial<L1TxUtilsConfig>,
47
+ protected debugMaxGasLimit: boolean = false,
48
+ ) {
49
+ this.config = merge(defaultL1TxUtilsConfig, pick(config || {}, ...getKeys(l1TxUtilsConfigMappings)));
50
+ }
51
+
52
+ public interrupt() {
53
+ this.interrupted = true;
54
+ }
55
+
56
+ public restart() {
57
+ this.interrupted = false;
58
+ }
59
+
60
+ public getBlock() {
61
+ return this.client.getBlock();
62
+ }
63
+
64
+ public getBlockNumber() {
65
+ return this.client.getBlockNumber();
66
+ }
67
+
68
+ /**
69
+ * Analyzes pending transactions and recent fee history to determine a competitive priority fee.
70
+ * Falls back to network estimate if data is unavailable or fails.
71
+ * @param networkEstimateResult - Result from estimateMaxPriorityFeePerGas RPC call
72
+ * @param pendingBlockResult - Result from getBlock with pending tag RPC call
73
+ * @param feeHistoryResult - Result from getFeeHistory RPC call
74
+ * @returns A competitive priority fee based on pending txs and recent block history
75
+ */
76
+ protected getCompetitivePriorityFee(
77
+ networkEstimateResult: PromiseSettledResult<bigint | null>,
78
+ pendingBlockResult: PromiseSettledResult<Awaited<ReturnType<ViemClient['getBlock']>> | null>,
79
+ feeHistoryResult: PromiseSettledResult<Awaited<ReturnType<ViemClient['getFeeHistory']>> | null>,
80
+ ): bigint {
81
+ const networkEstimate =
82
+ networkEstimateResult.status === 'fulfilled' && typeof networkEstimateResult.value === 'bigint'
83
+ ? networkEstimateResult.value
84
+ : 0n;
85
+ let competitiveFee = networkEstimate;
86
+
87
+ if (
88
+ pendingBlockResult.status === 'fulfilled' &&
89
+ pendingBlockResult.value !== null &&
90
+ pendingBlockResult.value.transactions &&
91
+ pendingBlockResult.value.transactions.length > 0
92
+ ) {
93
+ const pendingBlock = pendingBlockResult.value;
94
+ // Extract priority fees from pending transactions
95
+ const pendingFees = pendingBlock.transactions
96
+ .map(tx => {
97
+ // Transaction can be just a hash string, so we need to check if it's an object
98
+ if (typeof tx === 'string') {
99
+ return 0n;
100
+ }
101
+ const fee = tx.maxPriorityFeePerGas || 0n;
102
+ // Debug: Log suspicious fees
103
+ if (fee > 100n * WEI_CONST) {
104
+ this.logger?.warn('Suspicious high priority fee in pending tx', {
105
+ txHash: tx.hash,
106
+ maxPriorityFeePerGas: formatGwei(fee),
107
+ maxFeePerGas: formatGwei(tx.maxFeePerGas || 0n),
108
+ maxFeePerBlobGas: tx.maxFeePerBlobGas ? formatGwei(tx.maxFeePerBlobGas) : 'N/A',
109
+ });
110
+ }
111
+ return fee;
112
+ })
113
+ .filter((fee: bigint) => fee > 0n);
114
+
115
+ if (pendingFees.length > 0) {
116
+ // Use 75th percentile of pending fees to be competitive
117
+ const sortedPendingFees = [...pendingFees].sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
118
+ const percentile75Index = Math.floor((sortedPendingFees.length - 1) * 0.75);
119
+ const pendingCompetitiveFee = sortedPendingFees[percentile75Index];
120
+
121
+ if (pendingCompetitiveFee > competitiveFee) {
122
+ competitiveFee = pendingCompetitiveFee;
123
+ }
124
+
125
+ this.logger?.debug('Analyzed pending transactions for competitive pricing', {
126
+ pendingTxCount: pendingFees.length,
127
+ pendingP75: formatGwei(pendingCompetitiveFee),
128
+ });
129
+ }
130
+ }
131
+ if (
132
+ feeHistoryResult.status === 'fulfilled' &&
133
+ feeHistoryResult.value !== null &&
134
+ feeHistoryResult.value.reward &&
135
+ feeHistoryResult.value.reward.length > 0
136
+ ) {
137
+ const feeHistory = feeHistoryResult.value;
138
+ // Extract 75th percentile fees from each block
139
+ const percentile75Fees = feeHistory.reward!.map(rewards => rewards[0] || 0n).filter(fee => fee > 0n);
140
+
141
+ if (percentile75Fees.length > 0) {
142
+ // Calculate median of the 75th percentile fees across blocks
143
+ const medianHistoricalFee = median(percentile75Fees) ?? 0n;
144
+
145
+ // Debug: Log suspicious fees from history
146
+ if (medianHistoricalFee > 100n * WEI_CONST) {
147
+ this.logger?.warn('Suspicious high fee in history', {
148
+ historicalMedian: formatGwei(medianHistoricalFee),
149
+ allP75Fees: percentile75Fees.map(f => formatGwei(f)),
150
+ });
151
+ }
152
+
153
+ if (medianHistoricalFee > competitiveFee) {
154
+ competitiveFee = medianHistoricalFee;
155
+ }
156
+
157
+ this.logger?.debug('Analyzed fee history for competitive pricing', {
158
+ historicalMedian: formatGwei(medianHistoricalFee),
159
+ });
160
+ }
161
+ }
162
+
163
+ // Sanity check: cap competitive fee at 100x network estimate to avoid using unrealistic fees
164
+ // (e.g., Anvil returns inflated historical fees that don't reflect actual network conditions)
165
+ const maxReasonableFee = networkEstimate * 100n;
166
+ if (competitiveFee > maxReasonableFee) {
167
+ this.logger?.warn('Competitive fee exceeds sanity cap, using capped value', {
168
+ competitiveFee: formatGwei(competitiveFee),
169
+ networkEstimate: formatGwei(networkEstimate),
170
+ cappedTo: formatGwei(maxReasonableFee),
171
+ });
172
+ competitiveFee = maxReasonableFee;
173
+ }
174
+
175
+ // Log final decision
176
+ if (competitiveFee > networkEstimate) {
177
+ this.logger?.debug('Using competitive fee from market analysis', {
178
+ networkEstimate: formatGwei(networkEstimate),
179
+ competitive: formatGwei(competitiveFee),
180
+ });
181
+ }
182
+
183
+ return competitiveFee;
184
+ }
185
+
186
+ /**
187
+ * Gets the current gas price with bounds checking
188
+ */
189
+ public async getGasPrice(
190
+ gasConfigOverrides?: L1TxUtilsConfig,
191
+ isBlobTx: boolean = false,
192
+ attempt: number = 0,
193
+ previousGasPrice?: typeof attempt extends 0 ? never : GasPrice,
194
+ ): Promise<GasPrice> {
195
+ const gasConfig = merge(this.config, gasConfigOverrides);
196
+
197
+ // Make all RPC calls in parallel upfront with retry logic
198
+ const latestBlockPromise = this.tryTwice(
199
+ () => this.client.getBlock({ blockTag: 'latest' }),
200
+ 'Getting latest block',
201
+ );
202
+ const networkEstimatePromise = gasConfig.fixedPriorityFeePerGas
203
+ ? null
204
+ : this.tryTwice(() => this.client.estimateMaxPriorityFeePerGas(), 'Estimating max priority fee per gas');
205
+ const pendingBlockPromise = gasConfig.fixedPriorityFeePerGas
206
+ ? null
207
+ : this.tryTwice(
208
+ () => this.client.getBlock({ blockTag: 'pending', includeTransactions: true }),
209
+ 'Getting pending block',
210
+ );
211
+ const feeHistoryPromise = gasConfig.fixedPriorityFeePerGas
212
+ ? null
213
+ : this.tryTwice(
214
+ () => this.client.getFeeHistory({ blockCount: HISTORICAL_BLOCK_COUNT, rewardPercentiles: [75] }),
215
+ 'Getting fee history',
216
+ );
217
+ const blobBaseFeePromise = isBlobTx
218
+ ? this.tryTwice(() => this.client.getBlobBaseFee(), 'Getting blob base fee')
219
+ : null;
220
+
221
+ const [latestBlockResult, networkEstimateResult, pendingBlockResult, feeHistoryResult, blobBaseFeeResult] =
222
+ await Promise.allSettled([
223
+ latestBlockPromise,
224
+ networkEstimatePromise ?? Promise.resolve(0n),
225
+ pendingBlockPromise ?? Promise.resolve(null),
226
+ feeHistoryPromise ?? Promise.resolve(null),
227
+ blobBaseFeePromise ?? Promise.resolve(0n),
228
+ ]);
229
+
230
+ // Extract results
231
+ const baseFee =
232
+ latestBlockResult.status === 'fulfilled' &&
233
+ typeof latestBlockResult.value === 'object' &&
234
+ latestBlockResult.value.baseFeePerGas
235
+ ? latestBlockResult.value.baseFeePerGas
236
+ : 0n;
237
+
238
+ // Get blob base fee if available
239
+ let blobBaseFee = 0n;
240
+ if (isBlobTx && blobBaseFeeResult.status === 'fulfilled' && typeof blobBaseFeeResult.value === 'bigint') {
241
+ blobBaseFee = blobBaseFeeResult.value;
242
+ } else if (isBlobTx) {
243
+ this.logger?.warn('Failed to get L1 blob base fee', attempt);
244
+ }
245
+
246
+ let priorityFee: bigint;
247
+ if (gasConfig.fixedPriorityFeePerGas) {
248
+ this.logger?.debug('Using fixed priority fee per L1 gas', {
249
+ fixedPriorityFeePerGas: gasConfig.fixedPriorityFeePerGas,
250
+ });
251
+ priorityFee = BigInt(Math.trunc(gasConfig.fixedPriorityFeePerGas * Number(WEI_CONST)));
252
+ } else {
253
+ // Get competitive priority fee (includes network estimate + analysis)
254
+ priorityFee = this.getCompetitivePriorityFee(networkEstimateResult, pendingBlockResult, feeHistoryResult);
255
+ }
256
+ let maxFeePerGas = baseFee;
257
+
258
+ let maxFeePerBlobGas = blobBaseFee;
259
+
260
+ // Bump base fee so it's valid for next blocks if it stalls
261
+ const numBlocks = Math.ceil(gasConfig.stallTimeMs! / BLOCK_TIME_MS);
262
+ for (let i = 0; i < numBlocks; i++) {
263
+ // each block can go up 12.5% from previous baseFee
264
+ maxFeePerGas = (maxFeePerGas * (1_000n + 125n)) / 1_000n;
265
+ // same for blob gas fee
266
+ maxFeePerBlobGas = (maxFeePerBlobGas * (1_000n + 125n)) / 1_000n;
267
+ }
268
+
269
+ if (attempt > 0) {
270
+ const configBump =
271
+ gasConfig.priorityFeeRetryBumpPercentage ?? defaultL1TxUtilsConfig.priorityFeeRetryBumpPercentage!;
272
+
273
+ // if this is a blob tx, we have to use the blob bump percentage
274
+ const minBumpPercentage = isBlobTx ? MIN_BLOB_REPLACEMENT_BUMP_PERCENTAGE : MIN_REPLACEMENT_BUMP_PERCENTAGE;
275
+
276
+ const bumpPercentage = configBump > minBumpPercentage ? configBump : minBumpPercentage;
277
+ // Calculate minimum required fees based on previous attempt
278
+ // multiply by 100 & divide by 100 to maintain some precision
279
+ const minPriorityFee =
280
+ (previousGasPrice!.maxPriorityFeePerGas * (100_00n + BigInt(bumpPercentage * 1_00))) / 100_00n;
281
+ const minMaxFee = (previousGasPrice!.maxFeePerGas * (100_00n + BigInt(bumpPercentage * 1_00))) / 100_00n;
282
+
283
+ let competitivePriorityFee = priorityFee;
284
+ if (!gasConfig.fixedPriorityFeePerGas) {
285
+ // Apply bump percentage to competitive fee
286
+ competitivePriorityFee = (priorityFee * (100_00n + BigInt(configBump * 1_00))) / 100_00n;
287
+
288
+ this.logger?.debug(`Speed-up attempt ${attempt}: using competitive fee strategy`, {
289
+ networkEstimate: formatGwei(priorityFee),
290
+ competitiveFee: formatGwei(competitivePriorityFee),
291
+ minRequired: formatGwei(minPriorityFee),
292
+ bumpPercentage: configBump,
293
+ });
294
+ }
295
+
296
+ // Use maximum between competitive fee and minimum required bump
297
+ const finalPriorityFee = competitivePriorityFee > minPriorityFee ? competitivePriorityFee : minPriorityFee;
298
+ const feeSource = finalPriorityFee === competitivePriorityFee ? 'competitive' : 'minimum-bump';
299
+
300
+ priorityFee = finalPriorityFee;
301
+ // Add the final priority fee to maxFeePerGas
302
+ maxFeePerGas += finalPriorityFee;
303
+ maxFeePerGas = maxFeePerGas > minMaxFee ? maxFeePerGas : minMaxFee;
304
+
305
+ if (!gasConfig.fixedPriorityFeePerGas) {
306
+ this.logger?.debug(`Speed-up fee decision: using ${feeSource} fee`, {
307
+ finalPriorityFee: formatGwei(finalPriorityFee),
308
+ });
309
+ }
310
+ } else {
311
+ // First attempt: apply configured bump percentage to competitive fee
312
+ // multiply by 100 & divide by 100 to maintain some precision
313
+ if (!gasConfig.fixedPriorityFeePerGas) {
314
+ priorityFee = (priorityFee * (100_00n + BigInt((gasConfig.priorityFeeBumpPercentage || 0) * 1_00))) / 100_00n;
315
+ this.logger?.debug('Initial transaction: using competitive fee from market analysis', {
316
+ networkEstimate: formatGwei(priorityFee),
317
+ });
318
+ }
319
+ maxFeePerGas += priorityFee;
320
+ }
321
+
322
+ // maxGwei and maxBlobGwei are hard limits
323
+ const effectiveMaxGwei = BigInt(Math.trunc(gasConfig.maxGwei! * Number(WEI_CONST)));
324
+ const effectiveMaxBlobGwei = BigInt(Math.trunc(gasConfig.maxBlobGwei! * Number(WEI_CONST)));
325
+
326
+ // Ensure we don't exceed maxGwei
327
+ if (effectiveMaxGwei > 0n) {
328
+ maxFeePerGas = maxFeePerGas > effectiveMaxGwei ? effectiveMaxGwei : maxFeePerGas;
329
+ }
330
+
331
+ // Ensure we don't exceed maxBlobGwei
332
+ if (maxFeePerBlobGas && effectiveMaxBlobGwei > 0n) {
333
+ maxFeePerBlobGas = maxFeePerBlobGas > effectiveMaxBlobGwei ? effectiveMaxBlobGwei : maxFeePerBlobGas;
334
+ }
335
+
336
+ // Ensure priority fee doesn't exceed max fee
337
+ const maxPriorityFeePerGas = priorityFee > maxFeePerGas ? maxFeePerGas : priorityFee;
338
+
339
+ if (attempt > 0 && previousGasPrice?.maxFeePerBlobGas) {
340
+ const bumpPercentage =
341
+ gasConfig.priorityFeeRetryBumpPercentage! > MIN_BLOB_REPLACEMENT_BUMP_PERCENTAGE
342
+ ? gasConfig.priorityFeeRetryBumpPercentage!
343
+ : MIN_BLOB_REPLACEMENT_BUMP_PERCENTAGE;
344
+
345
+ // calculate min blob fee based on previous attempt
346
+ const minBlobFee = (previousGasPrice.maxFeePerBlobGas * (100_00n + BigInt(bumpPercentage * 1_00))) / 100_00n;
347
+
348
+ // use max between current network values and min required values
349
+ maxFeePerBlobGas = maxFeePerBlobGas > minBlobFee ? maxFeePerBlobGas : minBlobFee;
350
+ }
351
+
352
+ this.logger?.trace(
353
+ `Computed L1 gas price max fee ${formatGwei(maxFeePerGas)} and max priority fee ${formatGwei(maxPriorityFeePerGas)}`,
354
+ {
355
+ attempt,
356
+ baseFee: formatGwei(baseFee),
357
+ maxFeePerGas: formatGwei(maxFeePerGas),
358
+ maxPriorityFeePerGas: formatGwei(maxPriorityFeePerGas),
359
+ blobBaseFee: formatGwei(blobBaseFee),
360
+ maxFeePerBlobGas: formatGwei(maxFeePerBlobGas),
361
+ },
362
+ );
363
+
364
+ return {
365
+ maxFeePerGas,
366
+ maxPriorityFeePerGas,
367
+ ...(maxFeePerBlobGas && { maxFeePerBlobGas: maxFeePerBlobGas }),
368
+ };
369
+ }
370
+
371
+ /**
372
+ * Estimates gas and adds buffer
373
+ */
374
+ public async estimateGas(
375
+ account: Account | Hex,
376
+ request: L1TxRequest,
377
+ _gasConfig?: L1TxUtilsConfig,
378
+ _blobInputs?: L1BlobInputs,
379
+ ): Promise<bigint> {
380
+ const gasConfig = { ...this.config, ..._gasConfig };
381
+ let initialEstimate = 0n;
382
+ if (_blobInputs) {
383
+ // @note requests with blobs also require maxFeePerBlobGas to be set
384
+ const gasPrice = await this.getGasPrice(gasConfig, true, 0);
385
+ initialEstimate = await this.client.estimateGas({
386
+ account,
387
+ ...request,
388
+ ..._blobInputs,
389
+ maxFeePerBlobGas: gasPrice.maxFeePerBlobGas!,
390
+ gas: LARGE_GAS_LIMIT,
391
+ });
392
+
393
+ this.logger?.trace(`Estimated gas for blob tx: ${initialEstimate}`);
394
+ } else {
395
+ initialEstimate = await this.client.estimateGas({ account, ...request, gas: LARGE_GAS_LIMIT });
396
+ this.logger?.trace(`Estimated gas for non-blob tx: ${initialEstimate}`);
397
+ }
398
+
399
+ // Add buffer based on either fixed amount or percentage
400
+ const withBuffer = this.bumpGasLimit(initialEstimate, gasConfig);
401
+
402
+ return withBuffer;
403
+ }
404
+
405
+ async getTransactionStats(txHash: string): Promise<TransactionStats | undefined> {
406
+ const tx = await this.client.getTransaction({ hash: txHash as Hex });
407
+ if (!tx) {
408
+ return undefined;
409
+ }
410
+ const calldata = hexToBytes(tx.input);
411
+ return {
412
+ sender: tx.from.toString(),
413
+ transactionHash: tx.hash,
414
+ calldataSize: calldata.length,
415
+ calldataGas: getCalldataGasUsage(calldata),
416
+ };
417
+ }
418
+
419
+ public async tryGetErrorFromRevertedTx(
420
+ data: Hex,
421
+ args: {
422
+ args: readonly any[];
423
+ functionName: string;
424
+ abi: Abi;
425
+ address: Hex;
426
+ },
427
+ blobInputs: (L1BlobInputs & { maxFeePerBlobGas: bigint }) | undefined,
428
+ stateOverride: StateOverride = [],
429
+ ) {
430
+ try {
431
+ await this.client.simulateContract({
432
+ ...args,
433
+ account: this.client.account,
434
+ stateOverride,
435
+ });
436
+ this.logger?.trace('Simulated blob tx', { blobInputs });
437
+ // If the above passes, we have a blob error. We cannot simulate blob txs, and failed txs no longer throw errors.
438
+ // Strangely, the only way to throw the revert reason as an error and provide blobs is prepareTransactionRequest.
439
+ // See: https://github.com/wevm/viem/issues/2075
440
+ // This throws a EstimateGasExecutionError with the custom error information:
441
+ const request = blobInputs
442
+ ? {
443
+ account: this.client.account,
444
+ to: args.address,
445
+ data,
446
+ blobs: blobInputs.blobs,
447
+ kzg: blobInputs.kzg,
448
+ maxFeePerBlobGas: blobInputs.maxFeePerBlobGas,
449
+ }
450
+ : {
451
+ account: this.client.account,
452
+ to: args.address,
453
+ data,
454
+ };
455
+ this.logger?.trace('Preparing tx', { request });
456
+ await this.client.prepareTransactionRequest(request);
457
+ this.logger?.trace('Prepared tx');
458
+ return undefined;
459
+ } catch (simulationErr: any) {
460
+ // If we don't have a ContractFunctionExecutionError, we have a blob related error => use getContractError to get the error msg.
461
+ const contractErr =
462
+ simulationErr.name === 'ContractFunctionExecutionError'
463
+ ? simulationErr
464
+ : getContractError(simulationErr as BaseError, {
465
+ args: [],
466
+ abi: args.abi,
467
+ functionName: args.functionName,
468
+ address: args.address,
469
+ });
470
+ if (contractErr.name === 'ContractFunctionExecutionError') {
471
+ const execErr = contractErr as ContractFunctionExecutionError;
472
+ return tryGetCustomErrorNameContractFunction(execErr);
473
+ }
474
+ this.logger?.error(`Error getting error from simulation`, simulationErr);
475
+ }
476
+ }
477
+
478
+ public async simulate(
479
+ request: L1TxRequest & { gas?: bigint; from?: Hex },
480
+ blockOverrides: BlockOverrides<bigint, number> = {},
481
+ stateOverrides: StateOverride = [],
482
+ abi: Abi = RollupAbi,
483
+ _gasConfig?: L1TxUtilsConfig & { fallbackGasEstimate?: bigint },
484
+ ): Promise<{ gasUsed: bigint; result: `0x${string}` }> {
485
+ const gasConfig = { ...this.config, ..._gasConfig };
486
+
487
+ const call: any = {
488
+ to: request.to!,
489
+ data: request.data,
490
+ ...(request.from && { from: request.from }),
491
+ };
492
+
493
+ return await this._simulate(call, blockOverrides, stateOverrides, gasConfig, abi);
494
+ }
495
+
496
+ protected async _simulate(
497
+ call: any,
498
+ blockOverrides: BlockOverrides<bigint, number> = {},
499
+ stateOverrides: StateOverride = [],
500
+ gasConfig: L1TxUtilsConfig & { fallbackGasEstimate?: bigint },
501
+ abi: Abi,
502
+ ) {
503
+ try {
504
+ const result = await this.client.simulateBlocks({
505
+ validation: false,
506
+ blocks: [
507
+ {
508
+ blockOverrides,
509
+ stateOverrides,
510
+ calls: [call],
511
+ },
512
+ ],
513
+ });
514
+
515
+ if (result[0].calls[0].status === 'failure') {
516
+ this.logger?.error('L1 transaction simulation failed', result[0].calls[0].error);
517
+ const decodedError = decodeErrorResult({ abi, data: result[0].calls[0].data });
518
+
519
+ throw new Error(
520
+ `L1 transaction simulation failed with error ${decodedError.errorName}(${decodedError.args?.join(',')})`,
521
+ );
522
+ }
523
+ this.logger?.debug(`L1 transaction simulation succeeded`, { ...result[0].calls[0] });
524
+ return { gasUsed: result[0].gasUsed, result: result[0].calls[0].data as `0x${string}` };
525
+ } catch (err) {
526
+ if (err instanceof MethodNotFoundRpcError || err instanceof MethodNotSupportedRpcError) {
527
+ if (gasConfig.fallbackGasEstimate) {
528
+ this.logger?.warn(
529
+ `Node does not support eth_simulateV1 API. Using fallback gas estimate: ${gasConfig.fallbackGasEstimate}`,
530
+ );
531
+ return { gasUsed: gasConfig.fallbackGasEstimate, result: '0x' as `0x${string}` };
532
+ }
533
+ this.logger?.error('Node does not support eth_simulateV1 API');
534
+ }
535
+ throw err;
536
+ }
537
+ }
538
+
539
+ public bumpGasLimit(gasLimit: bigint, _gasConfig?: L1TxUtilsConfig): bigint {
540
+ const gasConfig = { ...this.config, ..._gasConfig };
541
+ const bumpedGasLimit = gasLimit + (gasLimit * BigInt((gasConfig?.gasLimitBufferPercentage || 0) * 1_00)) / 100_00n;
542
+
543
+ const cleanGasConfig = pickBy(gasConfig, (_, key) => key in l1TxUtilsConfigMappings);
544
+ this.logger?.trace(`Bumping gas limit from ${gasLimit} to ${bumpedGasLimit}`, {
545
+ gasLimit,
546
+ gasConfig: cleanGasConfig,
547
+ bumpedGasLimit,
548
+ });
549
+ return bumpedGasLimit;
550
+ }
551
+
552
+ /**
553
+ * Helper function to retry RPC calls twice
554
+ */
555
+ private tryTwice<T>(fn: () => Promise<T>, description: string): Promise<T> {
556
+ return retry<T>(fn, description, makeBackoff(times(2, () => 0)), this.logger, true);
557
+ }
558
+ }
@@ -0,0 +1,28 @@
1
+ import { EthAddress } from '@aztec/foundation/eth-address';
2
+ import type { ViemTransactionSignature } from '@aztec/foundation/eth-signature';
3
+
4
+ import { type TransactionSerializable, type WalletClient, parseTransaction } from 'viem';
5
+
6
+ import type { SigningCallback } from './types.js';
7
+
8
+ export function createViemSigner(client: WalletClient) {
9
+ const signer: SigningCallback = async (
10
+ tx: TransactionSerializable,
11
+ _address: EthAddress,
12
+ ): Promise<ViemTransactionSignature> => {
13
+ const signedTx = await client.signTransaction(tx as any);
14
+
15
+ const parsed = parseTransaction(signedTx);
16
+
17
+ if (!parsed.r || !parsed.s || (parsed.yParity !== 0 && parsed.yParity !== 1)) {
18
+ throw new Error('Failed to extract signature from viem signed transaction');
19
+ }
20
+
21
+ return {
22
+ r: parsed.r,
23
+ s: parsed.s,
24
+ yParity: parsed.yParity,
25
+ };
26
+ };
27
+ return signer;
28
+ }