diff --git a/yarn-project/aztec.js/src/utils/portal_manager.ts b/yarn-project/aztec.js/src/utils/portal_manager.ts index 660a63687d2..c2434ac61d3 100644 --- a/yarn-project/aztec.js/src/utils/portal_manager.ts +++ b/yarn-project/aztec.js/src/utils/portal_manager.ts @@ -7,7 +7,7 @@ import { type SiblingPath, computeSecretHash, } from '@aztec/aztec.js'; -import { extractEvent } from '@aztec/ethereum'; +import { L1TxUtils, extractEvent } from '@aztec/ethereum'; import { sha256ToField } from '@aztec/foundation/crypto'; import { FeeJuicePortalAbi, OutboxAbi, TestERC20Abi, TokenPortalAbi } from '@aztec/l1-artifacts'; @@ -19,6 +19,7 @@ import { type HttpTransport, type PublicClient, type WalletClient, + encodeFunctionData, getContract, toFunctionSelector, } from 'viem'; @@ -59,6 +60,7 @@ export function generateClaimSecret(logger?: Logger): [Fr, Fr] { /** Helper for managing an ERC20 on L1. */ export class L1TokenManager { private contract: GetContractReturnType>; + private l1TxUtils: L1TxUtils; public constructor( /** Address of the ERC20 contract. */ @@ -72,6 +74,7 @@ export class L1TokenManager { abi: TestERC20Abi, client: this.walletClient, }); + this.l1TxUtils = new L1TxUtils(publicClient, walletClient, logger); } /** @@ -90,8 +93,13 @@ export class L1TokenManager { */ public async mint(amount: bigint, address: Hex, addressName?: string) { this.logger.info(`Minting ${amount} tokens for ${stringifyEthAddress(address, addressName)}`); - await this.publicClient.waitForTransactionReceipt({ - hash: await this.contract.write.mint([address, amount]), + await this.l1TxUtils.sendAndMonitorTransaction({ + to: this.contract.address, + data: encodeFunctionData({ + abi: this.contract.abi, + functionName: 'mint', + args: [address, amount], + }), }); } @@ -103,8 +111,13 @@ export class L1TokenManager { */ public async approve(amount: bigint, address: Hex, addressName = '') { this.logger.info(`Approving ${amount} tokens for ${stringifyEthAddress(address, addressName)}`); - await this.publicClient.waitForTransactionReceipt({ - hash: await this.contract.write.approve([address, amount]), + await this.l1TxUtils.sendAndMonitorTransaction({ + to: this.contract.address, + data: encodeFunctionData({ + abi: this.contract.abi, + functionName: 'approve', + args: [address, amount], + }), }); } } @@ -116,6 +129,7 @@ export class L1FeeJuicePortalManager { typeof FeeJuicePortalAbi, WalletClient >; + private readonly l1TxUtils: L1TxUtils; constructor( portalAddress: EthAddress, @@ -130,6 +144,7 @@ export class L1FeeJuicePortalManager { abi: FeeJuicePortalAbi, client: this.walletClient, }); + this.l1TxUtils = new L1TxUtils(publicClient, walletClient, logger); } /** Returns the associated token manager for the L1 ERC20. */ @@ -154,10 +169,13 @@ export class L1FeeJuicePortalManager { this.logger.info('Sending L1 Fee Juice to L2 to be claimed publicly'); const args = [to.toString(), amount, claimSecretHash.toString()] as const; - await this.contract.simulate.depositToAztecPublic(args); - - const txReceipt = await this.publicClient.waitForTransactionReceipt({ - hash: await this.contract.write.depositToAztecPublic(args), + const txReceipt = await this.l1TxUtils.sendAndMonitorTransaction({ + to: this.contract.address, + data: encodeFunctionData({ + abi: this.contract.abi, + functionName: 'depositToAztecPublic', + args, + }), }); const log = extractEvent( @@ -210,6 +228,7 @@ export class L1FeeJuicePortalManager { export class L1ToL2TokenPortalManager { protected readonly portal: GetContractReturnType>; protected readonly tokenManager: L1TokenManager; + protected readonly l1TxUtils: L1TxUtils; constructor( portalAddress: EthAddress, @@ -224,6 +243,7 @@ export class L1ToL2TokenPortalManager { abi: TokenPortalAbi, client: this.walletClient, }); + this.l1TxUtils = new L1TxUtils(publicClient, walletClient, logger); } /** Returns the token manager for the underlying L1 token. */ @@ -241,14 +261,15 @@ export class L1ToL2TokenPortalManager { const [claimSecret, claimSecretHash] = await this.bridgeSetup(amount, mint); this.logger.info('Sending L1 tokens to L2 to be claimed publicly'); - const { request } = await this.portal.simulate.depositToAztecPublic([ - to.toString(), - amount, - claimSecretHash.toString(), - ]); + const args = [to.toString(), amount, claimSecretHash.toString()] as const; - const txReceipt = await this.publicClient.waitForTransactionReceipt({ - hash: await this.walletClient.writeContract(request), + const txReceipt = await this.l1TxUtils.sendAndMonitorTransaction({ + to: this.portal.address, + data: encodeFunctionData({ + abi: this.portal.abi, + functionName: 'depositToAztecPublic', + args, + }), }); const log = extractEvent( @@ -286,10 +307,13 @@ export class L1ToL2TokenPortalManager { const [claimSecret, claimSecretHash] = await this.bridgeSetup(amount, mint); this.logger.info('Sending L1 tokens to L2 to be claimed privately'); - const { request } = await this.portal.simulate.depositToAztecPrivate([amount, claimSecretHash.toString()]); - - const txReceipt = await this.publicClient.waitForTransactionReceipt({ - hash: await this.walletClient.writeContract(request), + const txReceipt = await this.l1TxUtils.sendAndMonitorTransaction({ + to: this.portal.address, + data: encodeFunctionData({ + abi: this.portal.abi, + functionName: 'depositToAztecPrivate', + args: [amount, claimSecretHash.toString()], + }), }); const log = extractEvent( @@ -368,17 +392,23 @@ export class L1TokenPortalManager extends L1ToL2TokenPortalManager { throw new Error(`L1 to L2 message at block ${blockNumber} index ${messageIndex} has already been consumed`); } - // Call function on L1 contract to consume the message - const { request: withdrawRequest } = await this.portal.simulate.withdraw([ + const withdrawArgs = [ recipient.toString(), amount, false, BigInt(blockNumber), messageIndex, siblingPath.toBufferArray().map((buf: Buffer): Hex => `0x${buf.toString('hex')}`), - ]); - - await this.publicClient.waitForTransactionReceipt({ hash: await this.walletClient.writeContract(withdrawRequest) }); + ] as const; + // Call function on L1 contract to consume the message + await this.l1TxUtils.sendAndMonitorTransaction({ + to: this.portal.address, + data: encodeFunctionData({ + abi: this.portal.abi, + functionName: 'withdraw', + args: withdrawArgs, + }), + }); const isConsumedAfter = await this.outbox.read.hasMessageBeenConsumedAtBlockAndIndex([blockNumber, messageIndex]); if (!isConsumedAfter) { diff --git a/yarn-project/cli/src/cmds/infrastructure/sequencers.ts b/yarn-project/cli/src/cmds/infrastructure/sequencers.ts index cf5dbe7bdc1..74432781dab 100644 --- a/yarn-project/cli/src/cmds/infrastructure/sequencers.ts +++ b/yarn-project/cli/src/cmds/infrastructure/sequencers.ts @@ -1,9 +1,9 @@ import { createCompatibleClient } from '@aztec/aztec.js'; -import { createEthereumChain, getL1ContractsConfigEnvVars } from '@aztec/ethereum'; +import { L1TxUtils, createEthereumChain, getL1ContractsConfigEnvVars } from '@aztec/ethereum'; import { type LogFn, type Logger } from '@aztec/foundation/log'; import { RollupAbi, TestERC20Abi } from '@aztec/l1-artifacts'; -import { createPublicClient, createWalletClient, getContract, http } from 'viem'; +import { createPublicClient, createWalletClient, encodeFunctionData, getContract, http } from 'viem'; import { mnemonicToAccount } from 'viem/accounts'; export async function sequencers(opts: { @@ -48,6 +48,8 @@ export async function sequencers(opts: { const who = (maybeWho as `0x{string}`) ?? walletClient?.account.address.toString(); + const l1TxUtils = walletClient ? new L1TxUtils(publicClient, walletClient, debugLogger) : undefined; + if (command === 'list') { const sequencers = await rollup.read.getAttesters(); if (sequencers.length === 0) { @@ -59,8 +61,8 @@ export async function sequencers(opts: { } } } else if (command === 'add') { - if (!who || !writeableRollup || !walletClient) { - throw new Error(`Missing sequencer address`); + if (!who || !writeableRollup || !walletClient || !l1TxUtils) { + throw new Error(`Missing sequencer address or wallet configuration`); } log(`Adding ${who} as sequencer`); @@ -72,25 +74,55 @@ export async function sequencers(opts: { }); const config = getL1ContractsConfigEnvVars(); + const mintRequest = { + to: stakingAsset.address, + data: encodeFunctionData({ + abi: stakingAsset.abi, + functionName: 'mint', + args: [walletClient.account.address, config.minimumStake], + }), + }; + const approveRequest = { + to: stakingAsset.address, + data: encodeFunctionData({ + abi: stakingAsset.abi, + functionName: 'approve', + args: [rollup.address, config.minimumStake], + }), + }; + + await Promise.all([ + l1TxUtils.sendAndMonitorTransaction(mintRequest), + l1TxUtils.sendAndMonitorTransaction(approveRequest), + ]); - await Promise.all( - [ - await stakingAsset.write.mint([walletClient.account.address, config.minimumStake], {} as any), - await stakingAsset.write.approve([rollup.address, config.minimumStake], {} as any), - ].map(txHash => publicClient.waitForTransactionReceipt({ hash: txHash })), - ); + // send and monitor deposit transaction + const depositReceipt = await l1TxUtils.sendAndMonitorTransaction({ + to: writeableRollup.address, + data: encodeFunctionData({ + abi: writeableRollup.abi, + functionName: 'deposit', + args: [who, who, who, config.minimumStake], + }), + }); - const hash = await writeableRollup.write.deposit([who, who, who, config.minimumStake]); - await publicClient.waitForTransactionReceipt({ hash }); - log(`Added in tx ${hash}`); + log(`Added in tx ${depositReceipt.transactionHash}`); } else if (command === 'remove') { - if (!who || !writeableRollup) { - throw new Error(`Missing sequencer address`); + if (!who || !writeableRollup || !l1TxUtils) { + throw new Error(`Missing sequencer address or wallet configuration`); } log(`Removing ${who} as sequencer`); - const hash = await writeableRollup.write.initiateWithdraw([who, who]); - await publicClient.waitForTransactionReceipt({ hash }); - log(`Removed in tx ${hash}`); + + const receipt = await l1TxUtils.sendAndMonitorTransaction({ + to: writeableRollup.address, + data: encodeFunctionData({ + abi: writeableRollup.abi, + functionName: 'initiateWithdraw', + args: [who, who], + }), + }); + + log(`Removed in tx ${receipt.transactionHash}`); } else if (command === 'who-next') { const next = await rollup.read.getCurrentProposer(); log(`Sequencer expected to build is ${next}`);