From 3f13db11ce96577eb217fcc6f99556c31a980a35 Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Wed, 24 Jul 2024 12:12:11 +0000 Subject: [PATCH 1/3] feat: bridge erc20 tokens --- yarn-project/cli/src/cmds/l1/bridge_erc20.ts | 35 ++++ .../{bridge_l1_gas.ts => bridge_fee_juice.ts} | 13 +- yarn-project/cli/src/cmds/l1/index.ts | 43 ++++- yarn-project/cli/src/gas_portal.ts | 162 ------------------ yarn-project/cli/src/portal_manager.ts | 160 +++++++++++++++++ .../ethereum/src/deploy_l1_contracts.ts | 12 +- 6 files changed, 248 insertions(+), 177 deletions(-) create mode 100644 yarn-project/cli/src/cmds/l1/bridge_erc20.ts rename yarn-project/cli/src/cmds/l1/{bridge_l1_gas.ts => bridge_fee_juice.ts} (74%) delete mode 100644 yarn-project/cli/src/gas_portal.ts create mode 100644 yarn-project/cli/src/portal_manager.ts diff --git a/yarn-project/cli/src/cmds/l1/bridge_erc20.ts b/yarn-project/cli/src/cmds/l1/bridge_erc20.ts new file mode 100644 index 00000000000..a62c3e71439 --- /dev/null +++ b/yarn-project/cli/src/cmds/l1/bridge_erc20.ts @@ -0,0 +1,35 @@ +import { type AztecAddress, type EthAddress } from '@aztec/circuits.js'; +import { createEthereumChain, createL1Clients } from '@aztec/ethereum'; +import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; + +import { ERC20PortalManager } from '../../portal_manager.js'; + +export async function bridgeERC20( + amount: bigint, + recipient: AztecAddress, + l1RpcUrl: string, + chainId: number, + privateKey: string | undefined, + mnemonic: string, + tokenAddress: EthAddress, + portalAddress: EthAddress, + mint: boolean, + log: LogFn, + debugLogger: DebugLogger, +) { + // Prepare L1 client + const chain = createEthereumChain(l1RpcUrl, chainId); + const { publicClient, walletClient } = createL1Clients(chain.rpcUrl, privateKey ?? mnemonic, chain.chainInfo); + + // Setup portal manager + const portal = await ERC20PortalManager.create(tokenAddress, portalAddress, publicClient, walletClient, debugLogger); + const { secret } = await portal.prepareTokensOnL1(amount, amount, recipient, mint); + + if (mint) { + log(`Minted ${amount} tokens on L1 and pushed to L2 portal`); + } else { + log(`Bridged ${amount} tokens to L2 portal`); + } + log(`claimAmount=${amount},claimSecret=${secret}\n`); + log(`Note: You need to wait for two L2 blocks before pulling them from the L2 side`); +} diff --git a/yarn-project/cli/src/cmds/l1/bridge_l1_gas.ts b/yarn-project/cli/src/cmds/l1/bridge_fee_juice.ts similarity index 74% rename from yarn-project/cli/src/cmds/l1/bridge_l1_gas.ts rename to yarn-project/cli/src/cmds/l1/bridge_fee_juice.ts index 58037a8be98..d86b6e67ee6 100644 --- a/yarn-project/cli/src/cmds/l1/bridge_l1_gas.ts +++ b/yarn-project/cli/src/cmds/l1/bridge_fee_juice.ts @@ -3,7 +3,7 @@ import { createEthereumChain, createL1Clients } from '@aztec/ethereum'; import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; import { createCompatibleClient } from '../../client.js'; -import { GasPortalManagerFactory } from '../../gas_portal.js'; +import { FeeJuicePortalManager } from '../../portal_manager.js'; export async function bridgeL1Gas( amount: bigint, @@ -12,6 +12,7 @@ export async function bridgeL1Gas( l1RpcUrl: string, chainId: number, mnemonic: string, + mint: boolean, log: LogFn, debugLogger: DebugLogger, ) { @@ -23,14 +24,8 @@ export async function bridgeL1Gas( const client = await createCompatibleClient(rpcUrl, debugLogger); // Setup portal manager - const manager = await GasPortalManagerFactory.create({ - pxeService: client, - publicClient: publicClient, - walletClient: walletClient, - logger: debugLogger, - }); - - const { secret } = await manager.prepareTokensOnL1(amount, amount, recipient, false); + const portal = await FeeJuicePortalManager.create(client, publicClient, walletClient, debugLogger); + const { secret } = await portal.prepareTokensOnL1(amount, amount, recipient, mint); log(`Minted ${amount} gas tokens on L1 and pushed to L2 portal`); log(`claimAmount=${amount},claimSecret=${secret}\n`); diff --git a/yarn-project/cli/src/cmds/l1/index.ts b/yarn-project/cli/src/cmds/l1/index.ts index 9df45179194..6cce96cebe5 100644 --- a/yarn-project/cli/src/cmds/l1/index.ts +++ b/yarn-project/cli/src/cmds/l1/index.ts @@ -86,7 +86,7 @@ export function injectCommands(program: Command, log: LogFn, debugLogger: DebugL }); program - .command('bridge-l1-gas') + .command('bridge-fee-juice') .description('Mints L1 gas tokens and pushes them to L2.') .argument('', 'The amount of gas tokens to mint and bridge.', parseBigint) .argument('', 'Aztec address of the recipient.', parseAztecAddress) @@ -100,10 +100,11 @@ export function injectCommands(program: Command, log: LogFn, debugLogger: DebugL 'The mnemonic to use for deriving the Ethereum address that will mint and bridge', 'test test test test test test test test test test test junk', ) + .option('--mint', 'Mint the tokens on L1', false) .addOption(pxeOption) .addOption(l1ChainIdOption) .action(async (amount, recipient, options) => { - const { bridgeL1Gas } = await import('./bridge_l1_gas.js'); + const { bridgeL1Gas } = await import('./bridge_fee_juice.js'); await bridgeL1Gas( amount, recipient, @@ -111,6 +112,44 @@ export function injectCommands(program: Command, log: LogFn, debugLogger: DebugL options.l1RpcUrl, options.l1ChainId, options.mnemonic, + options.mint, + log, + debugLogger, + ); + }); + + program + .command('bridge-erc20') + .description('Bridges ERC20 tokens to L2.') + .argument('', 'The amount of gas tokens to mint and bridge.', parseBigint) + .argument('', 'Aztec address of the recipient.', parseAztecAddress) + .requiredOption( + '--l1-rpc-url ', + 'Url of the ethereum host. Chain identifiers localhost and testnet can be used', + ETHEREUM_HOST, + ) + .option( + '-m, --mnemonic ', + 'The mnemonic to use for deriving the Ethereum address that will mint and bridge', + 'test test test test test test test test test test test junk', + ) + .option('--mint', 'Mint the tokens on L1', false) + .addOption(l1ChainIdOption) + .requiredOption('-t, --token ', 'The address of the token to bridge', parseEthereumAddress) + .requiredOption('-p, --portal ', 'The address of the portal contract', parseEthereumAddress) + .option('-k, --private-key ', 'The private key to use for deployment', PRIVATE_KEY) + .action(async (amount, recipient, options) => { + const { bridgeERC20 } = await import('./bridge_erc20.js'); + await bridgeERC20( + amount, + recipient, + options.l1RpcUrl, + options.l1ChainId, + options.privateKey, + options.mnemonic, + options.token, + options.portal, + options.mint, log, debugLogger, ); diff --git a/yarn-project/cli/src/gas_portal.ts b/yarn-project/cli/src/gas_portal.ts deleted file mode 100644 index 01c749ce914..00000000000 --- a/yarn-project/cli/src/gas_portal.ts +++ /dev/null @@ -1,162 +0,0 @@ -// REFACTOR: This file has been shamelessly copied from yarn-project/end-to-end/src/shared/gas_portal_test_harness.ts -// We should make this a shared utility in the aztec.js package. -import { type AztecAddress, type DebugLogger, EthAddress, Fr, type PXE, computeSecretHash } from '@aztec/aztec.js'; -import { GasPortalAbi, OutboxAbi, PortalERC20Abi } from '@aztec/l1-artifacts'; - -import { - type Account, - type Chain, - type GetContractReturnType, - type HttpTransport, - type PublicClient, - type WalletClient, - getContract, -} from 'viem'; - -export interface GasPortalManagerFactoryConfig { - pxeService: PXE; - publicClient: PublicClient; - walletClient: WalletClient; - logger: DebugLogger; -} - -export class GasPortalManagerFactory { - private constructor(private config: GasPortalManagerFactoryConfig) {} - - private async createReal() { - const { pxeService, publicClient, walletClient, logger } = this.config; - - const ethAccount = EthAddress.fromString((await walletClient.getAddresses())[0]); - const l1ContractAddresses = (await pxeService.getNodeInfo()).l1ContractAddresses; - - const gasTokenAddress = l1ContractAddresses.gasTokenAddress; - const gasPortalAddress = l1ContractAddresses.gasPortalAddress; - - if (gasTokenAddress.isZero() || gasPortalAddress.isZero()) { - throw new Error('Gas portal not deployed on L1'); - } - - const outbox = getContract({ - address: l1ContractAddresses.outboxAddress.toString(), - abi: OutboxAbi, - client: walletClient, - }); - - const gasL1 = getContract({ - address: gasTokenAddress.toString(), - abi: PortalERC20Abi, - client: walletClient, - }); - - const gasPortal = getContract({ - address: gasPortalAddress.toString(), - abi: GasPortalAbi, - client: walletClient, - }); - - return new GasPortalManager( - pxeService, - logger, - ethAccount, - gasPortalAddress, - gasPortal, - gasL1, - outbox, - publicClient, - walletClient, - ); - } - - static create(config: GasPortalManagerFactoryConfig): Promise { - const factory = new GasPortalManagerFactory(config); - return factory.createReal(); - } -} - -/** - * A Class for testing cross chain interactions, contains common interactions - * shared between cross chain tests. - */ -class GasPortalManager { - constructor( - /** Private eXecution Environment (PXE). */ - public pxeService: PXE, - /** Logger. */ - public logger: DebugLogger, - /** Eth account to interact with. */ - public ethAccount: EthAddress, - /** Portal address. */ - public tokenPortalAddress: EthAddress, - /** Token portal instance. */ - public tokenPortal: GetContractReturnType>, - /** Underlying token for portal tests. */ - public underlyingERC20: GetContractReturnType>, - /** Message Bridge Outbox. */ - public outbox: GetContractReturnType>, - /** Viem Public client instance. */ - public publicClient: PublicClient, - /** Viem Wallet Client instance. */ - public walletClient: WalletClient, - ) {} - - get l1GasTokenAddress() { - return EthAddress.fromString(this.underlyingERC20.address); - } - - generateClaimSecret(): [Fr, Fr] { - this.logger.debug("Generating a claim secret using pedersen's hash function"); - const secret = Fr.random(); - const secretHash = computeSecretHash(secret); - this.logger.info('Generated claim secret: ' + secretHash.toString()); - return [secret, secretHash]; - } - - async mintTokensOnL1(amount: bigint) { - this.logger.info( - `Minting tokens on L1 for ${this.ethAccount.toString()} in contract ${this.underlyingERC20.address}`, - ); - await this.publicClient.waitForTransactionReceipt({ - hash: await this.underlyingERC20.write.mint([this.ethAccount.toString(), amount]), - }); - } - - async getL1GasTokenBalance(address: EthAddress) { - return await this.underlyingERC20.read.balanceOf([address.toString()]); - } - - async sendTokensToPortalPublic(bridgeAmount: bigint, l2Address: AztecAddress, secretHash: Fr) { - this.logger.info( - `Approving erc20 tokens for the TokenPortal at ${this.tokenPortalAddress.toString()} ${this.tokenPortal.address}`, - ); - await this.publicClient.waitForTransactionReceipt({ - hash: await this.underlyingERC20.write.approve([this.tokenPortalAddress.toString(), bridgeAmount]), - }); - - // Deposit tokens to the TokenPortal - this.logger.info( - `Simulating token portal deposit configured for token ${await this.tokenPortal.read.l2TokenAddress()} with registry ${await this.tokenPortal.read.registry()} to retrieve message hash`, - ); - const args = [l2Address.toString(), bridgeAmount, secretHash.toString()] as const; - const { result: messageHash } = await this.tokenPortal.simulate.depositToAztecPublic(args); - this.logger.info('Sending messages to L1 portal to be consumed publicly'); - await this.publicClient.waitForTransactionReceipt({ - hash: await this.tokenPortal.write.depositToAztecPublic(args), - }); - - return Fr.fromString(messageHash); - } - - async prepareTokensOnL1(l1TokenBalance: bigint, bridgeAmount: bigint, owner: AztecAddress, mint = true) { - const [secret, secretHash] = this.generateClaimSecret(); - - // Mint tokens on L1 - if (mint) { - await this.mintTokensOnL1(l1TokenBalance); - } - - // Deposit tokens to the TokenPortal - const msgHash = await this.sendTokensToPortalPublic(bridgeAmount, owner, secretHash); - - return { secret, msgHash, secretHash }; - } -} diff --git a/yarn-project/cli/src/portal_manager.ts b/yarn-project/cli/src/portal_manager.ts new file mode 100644 index 00000000000..9b1808c406c --- /dev/null +++ b/yarn-project/cli/src/portal_manager.ts @@ -0,0 +1,160 @@ +// REFACTOR: This file has been shamelessly copied from yarn-project/end-to-end/src/shared/gas_portal_test_harness.ts +// We should make this a shared utility in the aztec.js package. +import { type AztecAddress, type DebugLogger, type EthAddress, Fr, type PXE, computeSecretHash } from '@aztec/aztec.js'; +import { GasPortalAbi, PortalERC20Abi, TokenPortalAbi } from '@aztec/l1-artifacts'; + +import { + type Account, + type Chain, + type GetContractReturnType, + type Hex, + type HttpTransport, + type PublicClient, + type WalletClient, + getContract, +} from 'viem'; + +/** + * A Class for testing cross chain interactions, contains common interactions + * shared between cross chain tests. + */ +abstract class PortalManager { + protected constructor( + /** Underlying token for portal tests. */ + public underlyingERC20Address: EthAddress, + /** Portal address. */ + public tokenPortalAddress: EthAddress, + public publicClient: PublicClient, + public walletClient: WalletClient, + /** Logger. */ + public logger: DebugLogger, + ) {} + + generateClaimSecret(): [Fr, Fr] { + this.logger.debug("Generating a claim secret using pedersen's hash function"); + const secret = Fr.random(); + const secretHash = computeSecretHash(secret); + this.logger.info('Generated claim secret: ' + secretHash.toString()); + return [secret, secretHash]; + } + + getERC20Contract(): GetContractReturnType> { + return getContract({ + address: this.underlyingERC20Address.toString(), + abi: PortalERC20Abi, + client: this.walletClient, + }); + } + + async mintTokensOnL1(amount: bigint) { + this.logger.info( + `Minting tokens on L1 for ${this.walletClient.account.address} in contract ${this.underlyingERC20Address}`, + ); + await this.publicClient.waitForTransactionReceipt({ + hash: await this.getERC20Contract().write.mint([this.walletClient.account.address, amount]), + }); + } + + async getL1TokenBalance(address: EthAddress) { + return await this.getERC20Contract().read.balanceOf([address.toString()]); + } + + protected async sendTokensToPortalPublic(bridgeAmount: bigint, l2Address: AztecAddress, secretHash: Fr) { + this.logger.info(`Approving erc20 tokens for the TokenPortal at ${this.tokenPortalAddress.toString()}`); + await this.publicClient.waitForTransactionReceipt({ + hash: await this.getERC20Contract().write.approve([this.tokenPortalAddress.toString(), bridgeAmount]), + }); + + const messageHash = await this.bridgeTokens(l2Address, bridgeAmount, secretHash); + return Fr.fromString(messageHash); + } + + protected abstract bridgeTokens(to: AztecAddress, amount: bigint, secretHash: Fr): Promise; + + async prepareTokensOnL1(l1TokenBalance: bigint, bridgeAmount: bigint, owner: AztecAddress, mint = true) { + const [secret, secretHash] = this.generateClaimSecret(); + + // Mint tokens on L1 + if (mint) { + await this.mintTokensOnL1(l1TokenBalance); + } + + // Deposit tokens to the TokenPortal + const msgHash = await this.sendTokensToPortalPublic(bridgeAmount, owner, secretHash); + + return { secret, msgHash, secretHash }; + } +} + +export class FeeJuicePortalManager extends PortalManager { + async bridgeTokens(to: AztecAddress, amount: bigint, secretHash: Fr): Promise { + const portal = getContract({ + address: this.tokenPortalAddress.toString(), + abi: GasPortalAbi, + client: this.walletClient, + }); + + this.logger.info( + `Simulating token portal deposit configured for token ${await portal.read.l2TokenAddress()} with registry ${await portal.read.registry()} to retrieve message hash`, + ); + + const args = [to.toString(), amount, secretHash.toString()] as const; + const { result: messageHash } = await portal.simulate.depositToAztecPublic(args); + this.logger.info('Sending messages to L1 portal to be consumed publicly'); + + await this.publicClient.waitForTransactionReceipt({ + hash: await portal.write.depositToAztecPublic(args), + }); + return messageHash; + } + + public static async create( + pxe: PXE, + publicClient: PublicClient, + walletClient: WalletClient, + logger: DebugLogger, + ): Promise { + const { + l1ContractAddresses: { gasTokenAddress, gasPortalAddress }, + } = await pxe.getNodeInfo(); + + if (gasTokenAddress.isZero() || gasPortalAddress.isZero()) { + throw new Error('Portal or token not deployed on L1'); + } + + return new FeeJuicePortalManager(gasTokenAddress, gasPortalAddress, publicClient, walletClient, logger); + } +} + +export class ERC20PortalManager extends PortalManager { + async bridgeTokens(to: AztecAddress, amount: bigint, secretHash: Fr): Promise { + const portal = getContract({ + address: this.tokenPortalAddress.toString(), + abi: TokenPortalAbi, + client: this.walletClient, + }); + + this.logger.info( + `Simulating token portal deposit configured for token ${await portal.read.l2Bridge()} with registry ${await portal.read.registry()} to retrieve message hash`, + ); + + const args = [to.toString(), amount, secretHash.toString()] as const; + const { result: messageHash } = await portal.simulate.depositToAztecPublic(args); + this.logger.info('Sending messages to L1 portal to be consumed publicly'); + + await this.publicClient.waitForTransactionReceipt({ + hash: await portal.write.depositToAztecPublic(args), + }); + return messageHash; + } + + public static create( + tokenAddress: EthAddress, + portalAddress: EthAddress, + publicClient: PublicClient, + walletClient: WalletClient, + logger: DebugLogger, + ): Promise { + return Promise.resolve(new ERC20PortalManager(tokenAddress, portalAddress, publicClient, walletClient, logger)); + } +} diff --git a/yarn-project/ethereum/src/deploy_l1_contracts.ts b/yarn-project/ethereum/src/deploy_l1_contracts.ts index 9e6ecdddf05..3013cf45795 100644 --- a/yarn-project/ethereum/src/deploy_l1_contracts.ts +++ b/yarn-project/ethereum/src/deploy_l1_contracts.ts @@ -17,7 +17,7 @@ import { getContract, http, } from 'viem'; -import { type HDAccount, type PrivateKeyAccount, mnemonicToAccount } from 'viem/accounts'; +import { type HDAccount, type PrivateKeyAccount, mnemonicToAccount, privateKeyToAccount } from 'viem/accounts'; import { foundry } from 'viem/chains'; import { type L1ContractAddresses } from './l1_contract_addresses.js'; @@ -96,17 +96,21 @@ export type L1Clients = { /** * Creates a wallet and a public viem client for interacting with L1. * @param rpcUrl - RPC URL to connect to L1. - * @param mnemonicOrHdAccount - Mnemonic or account for the wallet client. + * @param mnemonicOrPrivateKeyOrHdAccount - Mnemonic or account for the wallet client. * @param chain - Optional chain spec (defaults to local foundry). * @returns - A wallet and a public client. */ export function createL1Clients( rpcUrl: string, - mnemonicOrHdAccount: string | HDAccount | PrivateKeyAccount, + mnemonicOrPrivateKeyOrHdAccount: string | `0x${string}` | HDAccount | PrivateKeyAccount, chain: Chain = foundry, ): L1Clients { const hdAccount = - typeof mnemonicOrHdAccount === 'string' ? mnemonicToAccount(mnemonicOrHdAccount) : mnemonicOrHdAccount; + typeof mnemonicOrPrivateKeyOrHdAccount === 'string' + ? mnemonicOrPrivateKeyOrHdAccount.startsWith('0x') + ? privateKeyToAccount(mnemonicOrPrivateKeyOrHdAccount as `0x${string}`) + : mnemonicToAccount(mnemonicOrPrivateKeyOrHdAccount) + : mnemonicOrPrivateKeyOrHdAccount; const walletClient = createWalletClient({ account: hdAccount, From 4935ff8caab4c8dccc8eeddceffb9a995576c332 Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Wed, 24 Jul 2024 12:12:28 +0000 Subject: [PATCH 2/3] feat: integrate faucet into cli --- yarn-project/aztec-faucet/src/bin/index.ts | 10 ++++++++-- yarn-project/cli/src/cmds/devnet/faucet.ts | 15 +++++++++++++++ yarn-project/cli/src/cmds/devnet/index.ts | 13 ++++++++++++- yarn-project/cli/src/cmds/l1/create_l1_account.ts | 11 +++++++++++ yarn-project/cli/src/cmds/l1/index.ts | 5 +++++ 5 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 yarn-project/cli/src/cmds/devnet/faucet.ts create mode 100644 yarn-project/cli/src/cmds/l1/create_l1_account.ts diff --git a/yarn-project/aztec-faucet/src/bin/index.ts b/yarn-project/aztec-faucet/src/bin/index.ts index c4e3f43dc0b..01df08ebe3d 100644 --- a/yarn-project/aztec-faucet/src/bin/index.ts +++ b/yarn-project/aztec-faucet/src/bin/index.ts @@ -57,6 +57,12 @@ if (EXTRA_ASSETS) { }); } +class ThrottleError extends Error { + constructor(address: string) { + super(`Not funding address ${address}, please try again later`); + } +} + /** * Checks if the requested asset is something the faucet can handle. * @param asset - The asset to check @@ -88,7 +94,7 @@ function checkThrottle(asset: 'eth' | AssetName, address: Hex) { const current = new Date(); const diff = (current.getTime() - last.getTime()) / 1000; if (diff < interval) { - throw new Error(`Not funding address ${address}, please try again later`); + throw new ThrottleError(address); } } @@ -254,7 +260,7 @@ async function main() { await next(); } catch (err: any) { logger.error(err); - ctx.status = 400; + ctx.status = err instanceof ThrottleError ? 429 : 400; ctx.body = { error: err.message }; } }; diff --git a/yarn-project/cli/src/cmds/devnet/faucet.ts b/yarn-project/cli/src/cmds/devnet/faucet.ts new file mode 100644 index 00000000000..291a9c97fc2 --- /dev/null +++ b/yarn-project/cli/src/cmds/devnet/faucet.ts @@ -0,0 +1,15 @@ +import { type EthAddress } from '@aztec/circuits.js'; +import { type LogFn } from '@aztec/foundation/log'; + +export async function dripFaucet(faucetUrl: string, asset: string, account: EthAddress, log: LogFn): Promise { + const url = new URL(`${faucetUrl}/drip/${account.toString()}`); + url.searchParams.set('asset', asset); + const res = await fetch(url); + if (res.status === 200) { + log(`Dripped ${asset} for ${account.toString()}`); + } else if (res.status === 429) { + log(`Rate limited when dripping ${asset} for ${account.toString()}`); + } else { + log(`Failed to drip ${asset} for ${account.toString()}`); + } +} diff --git a/yarn-project/cli/src/cmds/devnet/index.ts b/yarn-project/cli/src/cmds/devnet/index.ts index bc6e1a0e88f..b3bc8a43da6 100644 --- a/yarn-project/cli/src/cmds/devnet/index.ts +++ b/yarn-project/cli/src/cmds/devnet/index.ts @@ -2,7 +2,7 @@ import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; import { type Command } from 'commander'; -import { ETHEREUM_HOST, l1ChainIdOption, pxeOption } from '../../utils/commands.js'; +import { ETHEREUM_HOST, l1ChainIdOption, parseEthereumAddress, pxeOption } from '../../utils/commands.js'; export function injectCommands(program: Command, log: LogFn, debugLogger: DebugLogger) { program @@ -36,5 +36,16 @@ export function injectCommands(program: Command, log: LogFn, debugLogger: DebugL ); }); + program + .command('drip-faucet') + .description('Drip the faucet') + .requiredOption('-u, --faucet-url ', 'Url of the faucet', 'http://localhost:8082') + .requiredOption('-t, --token ', 'The asset to drip', 'eth') + .requiredOption('-a, --address ', 'The Ethereum address to drip to', parseEthereumAddress) + .action(async options => { + const { dripFaucet } = await import('./faucet.js'); + await dripFaucet(options.faucetUrl, options.token, options.address, log); + }); + return program; } diff --git a/yarn-project/cli/src/cmds/l1/create_l1_account.ts b/yarn-project/cli/src/cmds/l1/create_l1_account.ts new file mode 100644 index 00000000000..4e4b01fb5ed --- /dev/null +++ b/yarn-project/cli/src/cmds/l1/create_l1_account.ts @@ -0,0 +1,11 @@ +import { type LogFn } from '@aztec/foundation/log'; + +import { generatePrivateKey, privateKeyToAccount } from 'viem/accounts'; + +export function createL1Account(log: LogFn) { + const privateKey = generatePrivateKey(); + const account = privateKeyToAccount(privateKey); + + log(`Private Key: ${privateKey}`); + log(`Address: ${account.address}`); +} diff --git a/yarn-project/cli/src/cmds/l1/index.ts b/yarn-project/cli/src/cmds/l1/index.ts index 6cce96cebe5..b980e7cc7e6 100644 --- a/yarn-project/cli/src/cmds/l1/index.ts +++ b/yarn-project/cli/src/cmds/l1/index.ts @@ -155,6 +155,11 @@ export function injectCommands(program: Command, log: LogFn, debugLogger: DebugL ); }); + program.command('create-l1-account').action(async () => { + const { createL1Account } = await import('./create_l1_account.js'); + createL1Account(log); + }); + program .command('get-l1-balance') .description('Gets the balance of gas tokens in L1 for the given Ethereum address.') From 0d646a1aab0491482eef52fb0d5e13d3c370f0d1 Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Wed, 24 Jul 2024 12:16:31 +0000 Subject: [PATCH 3/3] refactor: get-l1-balance works with any ERC20 token --- yarn-project/cli/src/cmds/l1/get_l1_balance.ts | 18 +++--------------- yarn-project/cli/src/cmds/l1/index.ts | 6 +++--- 2 files changed, 6 insertions(+), 18 deletions(-) diff --git a/yarn-project/cli/src/cmds/l1/get_l1_balance.ts b/yarn-project/cli/src/cmds/l1/get_l1_balance.ts index 3f67cf9d6e1..1ca93b6b312 100644 --- a/yarn-project/cli/src/cmds/l1/get_l1_balance.ts +++ b/yarn-project/cli/src/cmds/l1/get_l1_balance.ts @@ -1,28 +1,16 @@ import { type EthAddress } from '@aztec/circuits.js'; import { createEthereumChain } from '@aztec/ethereum'; -import { type DebugLogger, type LogFn } from '@aztec/foundation/log'; +import { type LogFn } from '@aztec/foundation/log'; import { PortalERC20Abi } from '@aztec/l1-artifacts'; import { createPublicClient, getContract, http } from 'viem'; -import { createCompatibleClient } from '../../client.js'; - -export async function getL1Balance( - who: EthAddress, - rpcUrl: string, - l1RpcUrl: string, - chainId: number, - log: LogFn, - debugLogger: DebugLogger, -) { - const client = await createCompatibleClient(rpcUrl, debugLogger); - const { l1ContractAddresses } = await client.getNodeInfo(); - +export async function getL1Balance(who: EthAddress, token: EthAddress, l1RpcUrl: string, chainId: number, log: LogFn) { const chain = createEthereumChain(l1RpcUrl, chainId); const publicClient = createPublicClient({ chain: chain.chainInfo, transport: http(chain.rpcUrl) }); const gasL1 = getContract({ - address: l1ContractAddresses.gasTokenAddress.toString(), + address: token.toString(), abi: PortalERC20Abi, client: publicClient, }); diff --git a/yarn-project/cli/src/cmds/l1/index.ts b/yarn-project/cli/src/cmds/l1/index.ts index b980e7cc7e6..d199864714b 100644 --- a/yarn-project/cli/src/cmds/l1/index.ts +++ b/yarn-project/cli/src/cmds/l1/index.ts @@ -162,18 +162,18 @@ export function injectCommands(program: Command, log: LogFn, debugLogger: DebugL program .command('get-l1-balance') - .description('Gets the balance of gas tokens in L1 for the given Ethereum address.') + .description('Gets the balance of an ERC token in L1 for the given Ethereum address.') .argument('', 'Ethereum address to check.', parseEthereumAddress) .requiredOption( '--l1-rpc-url ', 'Url of the ethereum host. Chain identifiers localhost and testnet can be used', ETHEREUM_HOST, ) - .addOption(pxeOption) + .requiredOption('-t, --token ', 'The address of the token to check the balance of', parseEthereumAddress) .addOption(l1ChainIdOption) .action(async (who, options) => { const { getL1Balance } = await import('./get_l1_balance.js'); - await getL1Balance(who, options.rpcUrl, options.l1RpcUrl, options.l1ChainId, log, debugLogger); + await getL1Balance(who, options.token, options.l1RpcUrl, options.l1ChainId, log); }); return program;