Skip to content

Commit

Permalink
rename to fund accounts and cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
dghelm committed Aug 24, 2024
1 parent 71a483e commit 837a849
Show file tree
Hide file tree
Showing 2 changed files with 99 additions and 87 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {confirm, select} from '@inquirer/prompts'
import {Command, Flags} from '@oclif/core'
import chalk from 'chalk'
import {ethers} from 'ethers'
import path from 'node:path'
import chalk from 'chalk'
import {select, confirm} from '@inquirer/prompts'
import {toString as qrCodeToString} from 'qrcode'

import {parseTomlConfig} from '../../utils/config-parser.js'
Expand All @@ -15,7 +15,7 @@ enum Layer {

const FUNDING_AMOUNT = 0.008

export default class HelperFundContracts extends Command {
export default class HelperFundAccounts extends Command {
static description = 'Fund L1 and L2 accounts for contracts'

static flags = {
Expand All @@ -28,6 +28,11 @@ export default class HelperFundContracts extends Command {
default: './config.toml',
description: 'Path to config.toml file',
}),
dev: Flags.boolean({
char: 'd',
default: false,
description: 'Use Anvil devnet funding logic',
}),
l1rpc: Flags.string({
char: 'o',
description: 'L1 RPC URL',
Expand All @@ -36,39 +41,35 @@ export default class HelperFundContracts extends Command {
char: 't',
description: 'L2 RPC URL',
}),
dev: Flags.boolean({
char: 'd',
description: 'Use Anvil devnet funding logic',
default: false,
manual: Flags.boolean({
char: 'm',
description: 'Manually fund the accounts',
}),
pod: Flags.boolean({
char: 'p',
default: false,
description: 'Run inside Kubernetes pod',
}),
manual: Flags.boolean({
char: 'm',
description: 'Manually fund the accounts',
}),
'private-key': Flags.string({
char: 'k',
description: 'Private key for funder wallet',
}),
}

private l1Provider!: ethers.JsonRpcProvider
private l2Provider!: ethers.JsonRpcProvider
private l1Rpc!: string
private l2Rpc!: string
private fundingWallet!: ethers.Wallet
private l1ETHGateway!: string
private blockExplorers: Record<Layer, {blockExplorerURI: string}> = {
[Layer.L1]: {blockExplorerURI: ''},
[Layer.L2]: {blockExplorerURI: ''},
}

private fundingWallet!: ethers.Wallet
private l1ETHGateway!: string
private l1Provider!: ethers.JsonRpcProvider
private l1Rpc!: string
private l2Provider!: ethers.JsonRpcProvider
private l2Rpc!: string

public async run(): Promise<void> {
const {flags} = await this.parse(HelperFundContracts)
const {flags} = await this.parse(HelperFundAccounts)

const configPath = path.resolve(flags.config)
const config = parseTomlConfig(configPath)
Expand Down Expand Up @@ -125,6 +126,65 @@ export default class HelperFundContracts extends Command {
this.log(chalk.green('Funding complete'))
}

private async bridgeFundsL1ToL2(recipient: string, amount: number): Promise<void> {
try {
this.log(chalk.cyan(`Bridging funds from L1 to L2 for recipient: ${recipient}`))

const gasLimit = BigInt(170_000)
const value = ethers.parseEther((amount + 0.001).toString())

const l1ETHGateway = new ethers.Contract(
this.l1ETHGateway,
['function depositETH(address _to, uint256 _amount, uint256 _gasLimit) payable'],
this.fundingWallet,
)

await this.logAddress(
this.l1ETHGateway,
`Depositing ${amount} ETH by sending ${ethers.formatEther(value)} to`,
Layer.L1,
)

const tx = await l1ETHGateway.depositETH(recipient, ethers.parseEther(amount.toString()), gasLimit, {value})
await this.logTx(tx.hash, 'Bridge transaction sent', Layer.L1)

const receipt = await tx.wait()
this.log(chalk.green(`Transaction mined in block: ${receipt.blockNumber}`))

this.log(
chalk.yellow(`Funds are being bridged to ${recipient}. Please wait for the transaction to be processed on L2.`),
)
} catch (error) {
this.error(`Error bridging funds from L1 to L2: ${error instanceof Error ? error.message : 'Unknown error'}`)
}
}

private async fundAddressAnvil(provider: ethers.JsonRpcProvider, address: string, amount: number, layer: Layer) {
try {
const result = await provider.send('anvil_setBalance', [address, ethers.parseEther(amount.toString()).toString()])
await this.logAddress(address, `Successfully funded with ${amount} ETH`, layer)
return result
} catch (error) {
this.error(
`Failed to fund ${address} (${layer} devnet): ${error instanceof Error ? error.message : 'Unknown error'}`,
)
}
}

private async fundAddressNetwork(provider: ethers.JsonRpcProvider, address: string, amount: number, layer: Layer) {
try {
const tx = await this.fundingWallet.sendTransaction({
to: address,
value: ethers.parseEther(amount.toString()),
})
await tx.wait()
await this.logTx(tx.hash, `Funded ${address} with ${amount} ETH`, layer)
} catch (error) {
this.error(`Failed to fund ${address} (${layer}): ${error instanceof Error ? error.message : 'Unknown error'}`)
}
}

// eslint-disable-next-line @typescript-eslint/no-explicit-any
private async fundL1Addresses(addresses: string[], flags: any): Promise<void> {
this.log(chalk.cyan('\nFunding L1 Addresses:'))
for (const address of addresses) {
Expand All @@ -134,65 +194,58 @@ export default class HelperFundContracts extends Command {
}

if (flags.dev) {
// eslint-disable-next-line no-await-in-loop
await this.fundAddressAnvil(this.l1Provider, address, FUNDING_AMOUNT, Layer.L1)
} else if (flags.manual) {
// eslint-disable-next-line no-await-in-loop
await this.promptManualFunding(address, FUNDING_AMOUNT, Layer.L1)
} else {
// eslint-disable-next-line no-await-in-loop
await this.fundAddressNetwork(this.l1Provider, address, FUNDING_AMOUNT, Layer.L1)
}
}
}

private async fundL2Addresses(addresses: string[], flags: any): Promise<void> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
private async fundL2Addresses(addresses: string[]): Promise<void> {
this.log(chalk.cyan('\nFunding L2 Addresses:'))
for (const address of addresses) {
if (!address) {
this.warn(`Address not found in config for one of the L2 accounts`)
continue
}

// eslint-disable-next-line no-await-in-loop
const fundingMethod = await this.promptUserForL2Funding()

if (fundingMethod === 'bridge') {
// eslint-disable-next-line no-await-in-loop
await this.bridgeFundsL1ToL2(address, FUNDING_AMOUNT)
} else if (fundingMethod === 'direct') {
// eslint-disable-next-line no-await-in-loop
await this.fundAddressNetwork(this.l2Provider, address, FUNDING_AMOUNT, Layer.L2)
} else {
// eslint-disable-next-line no-await-in-loop
await this.promptManualFunding(address, FUNDING_AMOUNT, Layer.L2)
}
}
}

private async fundAddressAnvil(provider: ethers.JsonRpcProvider, address: string, amount: number, layer: Layer) {
try {
const result = await provider.send('anvil_setBalance', [address, ethers.parseEther(amount.toString()).toString()])
await this.logAddress(address, `Successfully funded with ${amount} ETH`, layer)
return result
} catch (error) {
this.error(
`Failed to fund ${address} (${layer} devnet): ${error instanceof Error ? error.message : 'Unknown error'}`,
)
}
private async logAddress(address: string, description: string, layer: Layer): Promise<void> {
const link = await addressLink(address, this.blockExplorers[layer])
this.log(`${description}: ${chalk.cyan(link)}`)
}

private async fundAddressNetwork(provider: ethers.JsonRpcProvider, address: string, amount: number, layer: Layer) {
try {
const tx = await this.fundingWallet.sendTransaction({
to: address,
value: ethers.parseEther(amount.toString()),
})
await tx.wait()
await this.logTx(tx.hash, `Funded ${address} with ${amount} ETH`, layer)
} catch (error) {
this.error(`Failed to fund ${address} (${layer}): ${error instanceof Error ? error.message : 'Unknown error'}`)
}
private async logTx(txHash: string, description: string, layer: Layer): Promise<void> {
const link = await txLink(txHash, this.blockExplorers[layer])
this.log(`${description}: ${chalk.cyan(link)}`)
}

private async promptManualFunding(address: string, amount: number, layer: Layer) {
const chainId =
layer === Layer.L1 ? (await this.l1Provider.getNetwork()).chainId : (await this.l2Provider.getNetwork()).chainId

let qrString = `ethereum:${address}@${chainId}&value=${amount}`
const qrString = `ethereum:${address}@${chainId}&value=${amount}`

await this.logAddress(address, `Please fund the following address with ${chalk.yellow(amount)} ETH`, layer)
this.log('\n')
Expand All @@ -205,8 +258,10 @@ export default class HelperFundContracts extends Command {

let funded = false
while (!funded) {
// eslint-disable-next-line no-await-in-loop
await confirm({message: 'Press Enter when ready...'})
this.log(`Checking...`)
// eslint-disable-next-line no-await-in-loop
const balance = await (layer === Layer.L1 ? this.l1Provider : this.l2Provider).getBalance(address)
const formattedBalance = ethers.formatEther(balance)

Expand All @@ -221,56 +276,13 @@ export default class HelperFundContracts extends Command {

private async promptUserForL2Funding(): Promise<string> {
const answer = await select({
message: 'How would you like to fund the L2 address?',
choices: [
{name: 'Bridge funds from L1', value: 'bridge'},
{name: 'Directly fund L2 wallet', value: 'direct'},
{name: 'Manual funding', value: 'manual'},
],
message: 'How would you like to fund the L2 address?',
})
return answer
}

private async bridgeFundsL1ToL2(recipient: string, amount: number): Promise<void> {
try {
this.log(chalk.cyan(`Bridging funds from L1 to L2 for recipient: ${recipient}`))

const gasLimit = BigInt(170_000)
const value = ethers.parseEther((amount + 0.001).toString())

const l1ETHGateway = new ethers.Contract(
this.l1ETHGateway,
['function depositETH(address _to, uint256 _amount, uint256 _gasLimit) payable'],
this.fundingWallet,
)

await this.logAddress(
this.l1ETHGateway,
`Depositing ${amount} ETH by sending ${ethers.formatEther(value)} to`,
Layer.L1,
)

const tx = await l1ETHGateway.depositETH(recipient, ethers.parseEther(amount.toString()), gasLimit, {value})
await this.logTx(tx.hash, 'Bridge transaction sent', Layer.L1)

const receipt = await tx.wait()
this.log(chalk.green(`Transaction mined in block: ${receipt.blockNumber}`))

this.log(
chalk.yellow(`Funds are being bridged to ${recipient}. Please wait for the transaction to be processed on L2.`),
)
} catch (error) {
this.error(`Error bridging funds from L1 to L2: ${error instanceof Error ? error.message : 'Unknown error'}`)
}
}

private async logAddress(address: string, description: string, layer: Layer): Promise<void> {
const link = await addressLink(address, this.blockExplorers[layer])
this.log(`${description}: ${chalk.cyan(link)}`)
}

private async logTx(txHash: string, description: string, layer: Layer): Promise<void> {
const link = await txLink(txHash, this.blockExplorers[layer])
this.log(`${description}: ${chalk.cyan(link)}`)
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import {runCommand} from '@oclif/test'
import {expect} from 'chai'

describe('helper:fund_devnet', () => {
describe('helper:fund-accounts', () => {
it('runs helper:fund_devnet cmd', async () => {
const {stdout} = await runCommand('helper:fund_devnet')
expect(stdout).to.contain('hello world')
})

it('runs helper:fund_devnet --name oclif', async () => {
it('runs helper:fund-accounts --name oclif', async () => {
const {stdout} = await runCommand('helper:fund_devnet --name oclif')
expect(stdout).to.contain('hello oclif')
})
Expand Down

0 comments on commit 837a849

Please sign in to comment.