1+ import {
2+ type L1TxConfig ,
3+ type L1TxUtils ,
4+ createL1TxUtils ,
5+ getL1TxUtilsConfigEnvVars ,
6+ } from '@aztec/ethereum/l1-tx-utils' ;
17import type { ExtendedViemWalletClient , ViemContract } from '@aztec/ethereum/types' ;
28import { extractEvent } from '@aztec/ethereum/utils' ;
39import type { EpochNumber } from '@aztec/foundation/branded-types' ;
@@ -16,7 +22,7 @@ import { computeL2ToL1MessageHash, computeSecretHash } from '@aztec/stdlib/hash'
1622import type { AztecNode } from '@aztec/stdlib/interfaces/client' ;
1723import { getL2ToL1MessageLeafId } from '@aztec/stdlib/messaging' ;
1824
19- import { type Hex , getContract , toFunctionSelector } from 'viem' ;
25+ import { type Hex , encodeFunctionData , getContract , toFunctionSelector } from 'viem' ;
2026
2127/** L1 to L2 message info to claim it on L2. */
2228export type L2Claim = {
@@ -51,10 +57,26 @@ export async function generateClaimSecret(logger?: Logger): Promise<[Fr, Fr]> {
5157 return [ secret , secretHash ] ;
5258}
5359
60+ // `Inbox.sendL2Message` (reached by every `depositToAztec*` call) inserts into a height-10
61+ // (`L1_TO_L2_MSG_SUBTREE_HEIGHT`) incremental frontier tree. The per-insert cost swings by up to ~10 hash levels
62+ // (which translates to ~40k gas) depending on where the global message index lands when the tx is mined: inserts that
63+ // complete a subtree cascade through cold SLOADs + an SSTORE vs cheap inserts that touch one slot. A point-in-time
64+ // `eth_estimateGas` taken at a cheap index therefore under-sizes a deposit that mines at a subtree boundary. That ~40k
65+ // swing exceeds the default 20% buffer on a ~100k deposit. That is why we raise the gas-limit buffer to 2x here.
66+ // Note: a larger operator override via L1_GAS_LIMIT_BUFFER_PERCENTAGE still wins.
67+ const INBOX_DEPOSIT_GAS_LIMIT_BUFFER_PERCENTAGE = 100 ;
68+
69+ /** Per-call gas config for `depositToAztec*`: the Inbox-deposit buffer floor, or a larger operator override. */
70+ function inboxDepositGasConfig ( ) : L1TxConfig {
71+ const configuredBuffer = getL1TxUtilsConfigEnvVars ( ) . gasLimitBufferPercentage ?? 0 ;
72+ return { gasLimitBufferPercentage : Math . max ( configuredBuffer , INBOX_DEPOSIT_GAS_LIMIT_BUFFER_PERCENTAGE ) } ;
73+ }
74+
5475/** Helper for managing an ERC20 on L1. */
5576export class L1TokenManager {
5677 private contract : ViemContract < typeof TestERC20Abi > ;
5778 private handler : ViemContract < typeof FeeAssetHandlerAbi > | undefined ;
79+ private readonly l1TxUtils : L1TxUtils ;
5880
5981 public constructor (
6082 /** Address of the ERC20 contract. */
@@ -76,6 +98,7 @@ export class L1TokenManager {
7698 client : this . extendedClient ,
7799 } ) ;
78100 }
101+ this . l1TxUtils = createL1TxUtils ( this . extendedClient , { logger } , getL1TxUtilsConfigEnvVars ( ) ) ;
79102 }
80103
81104 /** Returns the amount of tokens available to mint via the handler.
@@ -108,8 +131,10 @@ export class L1TokenManager {
108131 const mintAmount = await this . getMintAmount ( ) ;
109132 this . logger . info ( `Minting ${ mintAmount } tokens for ${ stringifyEthAddress ( address , addressName ) } ` ) ;
110133 // NOTE: the handler mints a fixed amount.
111- await this . extendedClient . waitForTransactionReceipt ( {
112- hash : await this . handler . write . mint ( [ address ] ) ,
134+ await this . l1TxUtils . sendAndMonitorTransaction ( {
135+ to : this . handler . address ,
136+ abi : FeeAssetHandlerAbi ,
137+ data : encodeFunctionData ( { abi : FeeAssetHandlerAbi , functionName : 'mint' , args : [ address ] } ) ,
113138 } ) ;
114139 }
115140
@@ -121,8 +146,10 @@ export class L1TokenManager {
121146 */
122147 public async approve ( amount : bigint , address : Hex , addressName = '' ) {
123148 this . logger . info ( `Approving ${ amount } tokens for ${ stringifyEthAddress ( address , addressName ) } ` ) ;
124- await this . extendedClient . waitForTransactionReceipt ( {
125- hash : await this . contract . write . approve ( [ address , amount ] ) ,
149+ await this . l1TxUtils . sendAndMonitorTransaction ( {
150+ to : this . contract . address ,
151+ abi : TestERC20Abi ,
152+ data : encodeFunctionData ( { abi : TestERC20Abi , functionName : 'approve' , args : [ address , amount ] } ) ,
126153 } ) ;
127154 }
128155}
@@ -131,6 +158,7 @@ export class L1TokenManager {
131158export class L1FeeJuicePortalManager {
132159 private readonly tokenManager : L1TokenManager ;
133160 private readonly contract : ViemContract < typeof FeeJuicePortalAbi > ;
161+ private readonly l1TxUtils : L1TxUtils ;
134162
135163 constructor (
136164 portalAddress : EthAddress ,
@@ -145,6 +173,7 @@ export class L1FeeJuicePortalManager {
145173 abi : FeeJuicePortalAbi ,
146174 client : extendedClient ,
147175 } ) ;
176+ this . l1TxUtils = createL1TxUtils ( extendedClient , { logger } , getL1TxUtilsConfigEnvVars ( ) ) ;
148177 }
149178
150179 /** Returns the associated token manager for the L1 ERC20. */
@@ -176,9 +205,14 @@ export class L1FeeJuicePortalManager {
176205
177206 await this . contract . simulate . depositToAztecPublic ( args ) ;
178207
179- const txReceipt = await this . extendedClient . waitForTransactionReceipt ( {
180- hash : await this . contract . write . depositToAztecPublic ( args ) ,
181- } ) ;
208+ const { receipt : txReceipt } = await this . l1TxUtils . sendAndMonitorTransaction (
209+ {
210+ to : this . contract . address ,
211+ abi : FeeJuicePortalAbi ,
212+ data : encodeFunctionData ( { abi : FeeJuicePortalAbi , functionName : 'depositToAztecPublic' , args } ) ,
213+ } ,
214+ inboxDepositGasConfig ( ) ,
215+ ) ;
182216
183217 this . logger . info ( 'Deposited to Aztec public successfully' , { txReceipt } ) ;
184218
@@ -249,6 +283,7 @@ export class L1FeeJuicePortalManager {
249283export class L1ToL2TokenPortalManager {
250284 protected readonly portal : ViemContract < typeof TokenPortalAbi > ;
251285 protected readonly tokenManager : L1TokenManager ;
286+ protected readonly l1TxUtils : L1TxUtils ;
252287
253288 constructor (
254289 portalAddress : EthAddress ,
@@ -263,6 +298,7 @@ export class L1ToL2TokenPortalManager {
263298 abi : TokenPortalAbi ,
264299 client : extendedClient ,
265300 } ) ;
301+ this . l1TxUtils = createL1TxUtils ( extendedClient , { logger } , getL1TxUtilsConfigEnvVars ( ) ) ;
266302 }
267303
268304 /** Returns the token manager for the underlying L1 token. */
@@ -280,15 +316,17 @@ export class L1ToL2TokenPortalManager {
280316 const [ claimSecret , claimSecretHash ] = await this . bridgeSetup ( amount , mint ) ;
281317
282318 this . logger . info ( 'Sending L1 tokens to L2 to be claimed publicly' ) ;
283- const { request } = await this . portal . simulate . depositToAztecPublic ( [
284- to . toString ( ) ,
285- amount ,
286- claimSecretHash . toString ( ) ,
287- ] ) ;
288-
289- const txReceipt = await this . extendedClient . waitForTransactionReceipt ( {
290- hash : await this . extendedClient . writeContract ( request ) ,
291- } ) ;
319+ const args = [ to . toString ( ) , amount , claimSecretHash . toString ( ) ] as const ;
320+ await this . portal . simulate . depositToAztecPublic ( args ) ;
321+
322+ const { receipt : txReceipt } = await this . l1TxUtils . sendAndMonitorTransaction (
323+ {
324+ to : this . portal . address ,
325+ abi : TokenPortalAbi ,
326+ data : encodeFunctionData ( { abi : TokenPortalAbi , functionName : 'depositToAztecPublic' , args } ) ,
327+ } ,
328+ inboxDepositGasConfig ( ) ,
329+ ) ;
292330
293331 const log = extractEvent (
294332 txReceipt . logs ,
@@ -334,11 +372,17 @@ export class L1ToL2TokenPortalManager {
334372 const [ claimSecret , claimSecretHash ] = await this . bridgeSetup ( amount , mint ) ;
335373
336374 this . logger . info ( 'Sending L1 tokens to L2 to be claimed privately' ) ;
337- const { request } = await this . portal . simulate . depositToAztecPrivate ( [ amount , claimSecretHash . toString ( ) ] ) ;
338-
339- const txReceipt = await this . extendedClient . waitForTransactionReceipt ( {
340- hash : await this . extendedClient . writeContract ( request ) ,
341- } ) ;
375+ const args = [ amount , claimSecretHash . toString ( ) ] as const ;
376+ await this . portal . simulate . depositToAztecPrivate ( args ) ;
377+
378+ const { receipt : txReceipt } = await this . l1TxUtils . sendAndMonitorTransaction (
379+ {
380+ to : this . portal . address ,
381+ abi : TokenPortalAbi ,
382+ data : encodeFunctionData ( { abi : TokenPortalAbi , functionName : 'depositToAztecPrivate' , args } ) ,
383+ } ,
384+ inboxDepositGasConfig ( ) ,
385+ ) ;
342386
343387 const log = extractEvent (
344388 txReceipt . logs ,
@@ -437,18 +481,21 @@ export class L1TokenPortalManager extends L1ToL2TokenPortalManager {
437481 }
438482
439483 // Call function on L1 contract to consume the message
440- const { request : withdrawRequest } = await this . portal . simulate . withdraw ( [
484+ const withdrawArgs = [
441485 recipient . toString ( ) ,
442486 amount ,
443487 false ,
444488 BigInt ( epochNumber ) ,
445489 BigInt ( numCheckpointsInEpoch ) ,
446490 messageIndex ,
447491 siblingPath . toBufferArray ( ) . map ( ( buf : Buffer ) : Hex => `0x${ buf . toString ( 'hex' ) } ` ) ,
448- ] ) ;
492+ ] as const ;
493+ await this . portal . simulate . withdraw ( withdrawArgs ) ;
449494
450- await this . extendedClient . waitForTransactionReceipt ( {
451- hash : await this . extendedClient . writeContract ( withdrawRequest ) ,
495+ await this . l1TxUtils . sendAndMonitorTransaction ( {
496+ to : this . portal . address ,
497+ abi : TokenPortalAbi ,
498+ data : encodeFunctionData ( { abi : TokenPortalAbi , functionName : 'withdraw' , args : withdrawArgs } ) ,
452499 } ) ;
453500
454501 const isConsumedAfter = await this . outbox . read . hasMessageBeenConsumedAtEpoch ( [ BigInt ( epochNumber ) , messageLeafId ] ) ;
0 commit comments