Skip to content

Commit

Permalink
update to internal ethers api
Browse files Browse the repository at this point in the history
  • Loading branch information
douglance committed Nov 18, 2024
1 parent c947423 commit f0c6ca2
Show file tree
Hide file tree
Showing 3 changed files with 215 additions and 117 deletions.
141 changes: 64 additions & 77 deletions src/experimental/arbitrumDeposit/actions.ts
Original file line number Diff line number Diff line change
@@ -1,90 +1,77 @@
import {
type PublicClient,
type WalletClient,
type Client,
encodeFunctionData,
Account,
Address,
parseTransaction,
serializeTransaction
import { BigNumber } from 'ethers'
import {
Account,
Address,
Client,
type PublicClient,
TransactionRequest,
} from 'viem'
import { localEthChain } from '../chains'
import { inboxAbi } from './abis/inbox'
import { EthBridger } from '../../lib/assetBridger/ethBridger'
import { transformPublicClientToProvider } from './transformViemToEthers'

export type ArbitrumDepositActions = {
depositEth: (args: {
amount: bigint;
account: Account | Address;
walletClient: WalletClient;
}) => Promise<`0x${string}`>
export type PrepareDepositEthParameters = {
amount: bigint
account: Account | Address
}

type ArbitrumChainConfig = {
ethBridge: {
inbox: `0x${string}`
}
export type PrepareDepositEthToParameters = PrepareDepositEthParameters & {
destinationAddress: Address
parentPublicClient: PublicClient
}

export function arbitrumDepositActions(childChain: ArbitrumChainConfig) {
return <TClient extends PublicClient>(parentPublicClient: TClient): ArbitrumDepositActions => {
const getDepositRequest = async ({
amount,
account
}: {
amount: bigint
account: Account | Address
}) => {
const from = typeof account === 'string' ? account : account.address

return {
to: childChain.ethBridge.inbox,
value: amount,
data: encodeFunctionData({
abi: inboxAbi,
functionName: 'depositEth',
args: []
}),
from
}
}
export async function prepareDepositEthTransaction(
client: Client,
{ amount, account }: PrepareDepositEthParameters
): Promise<TransactionRequest> {
const provider = transformPublicClientToProvider(client)
const ethBridger = await EthBridger.fromProvider(provider)
const request = await ethBridger.getDepositRequest({
amount: BigNumber.from(amount),
from: typeof account === 'string' ? account : account.address,
})

return {
async depositEth({ amount, account, walletClient }) {
const request = await getDepositRequest({
amount,
account
})
return {
to: request.txRequest.to as `0x${string}`,
value: BigNumber.from(request.txRequest.value).toBigInt(),
data: request.txRequest.data as `0x${string}`,
}
}

const gasPrice = await parentPublicClient.getGasPrice()

const nonce = await parentPublicClient.getTransactionCount({
address: typeof account === 'string' ? account as `0x${string}` : account.address,
blockTag: 'latest'
})

const signedTx = await walletClient.signTransaction({
...request,
account,
chain: localEthChain,
gas: BigInt('130000'),
maxFeePerGas: gasPrice,
maxPriorityFeePerGas: gasPrice,
nonce
})
export async function prepareDepositEthToTransaction(
client: Client,
{
amount,
account,
destinationAddress,
parentPublicClient,
}: PrepareDepositEthToParameters
): Promise<TransactionRequest> {
const childProvider = transformPublicClientToProvider(client)
const parentProvider = transformPublicClientToProvider(parentPublicClient)
const ethBridger = await EthBridger.fromProvider(childProvider)

// Parse and serialize with L2 chain ID
const parsedTx = parseTransaction((signedTx as any).raw)
const serializedTx = serializeTransaction({
...parsedTx,
})
const request = await ethBridger.getDepositToRequest({
amount: BigNumber.from(amount),
destinationAddress,
from: typeof account === 'string' ? account : account.address,
parentProvider,
childProvider,
})

// Send to L2
const hash = await parentPublicClient.sendRawTransaction({
serializedTransaction: serializedTx
})
return {
to: request.txRequest.to as `0x${string}`,
value: BigNumber.from(request.txRequest.value).toBigInt(),
data: request.txRequest.data as `0x${string}`,
}
}

return hash
}
export function arbitrumDepositActions() {
return function (client: Client) {
return {
prepareDepositEthTransaction: (args: PrepareDepositEthParameters) =>
prepareDepositEthTransaction(client, args),
prepareDepositEthToTransaction: (args: PrepareDepositEthToParameters) =>
prepareDepositEthToTransaction(client, args),
}
}
}
}
44 changes: 44 additions & 0 deletions src/experimental/arbitrumDeposit/transformViemToEthers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { StaticJsonRpcProvider } from '@ethersproject/providers'
import { Chain, Client, PublicClient, Transport } from 'viem'

// based on https://wagmi.sh/react/ethers-adapters#reference-implementation
export function publicClientToProvider<TChain extends Chain | undefined>(
publicClient: PublicClient<Transport, TChain>
) {
const { chain } = publicClient

if (typeof chain === 'undefined') {
throw new Error(`[publicClientToProvider] "chain" is undefined`)
}

const network = {
chainId: chain.id,
name: chain.name,
ensAddress: chain.contracts?.ensRegistry?.address,
}

return new StaticJsonRpcProvider(chain.rpcUrls.default.http[0], network)
}

function isPublicClient(object: any): object is PublicClient {
return (
object !== undefined &&
object !== null &&
typeof object === 'object' &&
'transport' in object &&
object.transport !== null &&
typeof object.transport === 'object' &&
'url' in object.transport &&
typeof object.transport.url === 'string' &&
object.type === 'publicClient'
)
}

export const transformPublicClientToProvider = (
provider: PublicClient | Client
): StaticJsonRpcProvider => {
if (isPublicClient(provider)) {
return publicClientToProvider(provider)
}
throw new Error('Invalid provider')
}
147 changes: 107 additions & 40 deletions tests/integration/arbitrumDeposit.test.ts
Original file line number Diff line number Diff line change
@@ -1,90 +1,157 @@
import { expect } from 'chai'
import { createWalletClient, createPublicClient, http, parseEther } from 'viem'
import {
createWalletClient,
createPublicClient,
http,
parseEther,
type PublicClient,
} from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { config, testSetup } from '../../scripts/testSetup'
import { arbitrumDepositActions } from '../../src/experimental/arbitrumDeposit/actions'
import { testSetup, config } from '../../scripts/testSetup'
import { localEthChain, localArbChain } from '../../src/experimental/chains'

describe('arbitrumDepositActions', function() {
this.timeout(60000)

it('deposits ether', async function() {
const { childChain } = await testSetup()
describe('arbitrumDepositActions', function () {
before(async function () {
await testSetup()
})

it('deposits ETH from L1 to L2', async function () {
const account = privateKeyToAccount(`0x${config.ethKey}` as `0x${string}`)
const depositAmount = parseEther('0.01')

// Create parent clients
// Create L1 clients
const parentWalletClient = createWalletClient({
account,
chain: localEthChain,
transport: http(config.ethUrl)
transport: http(config.ethUrl),
})

const parentPublicClient = createPublicClient({
chain: localEthChain,
transport: http(config.ethUrl)
}).extend(arbitrumDepositActions({
ethBridge: {
inbox: childChain.ethBridge.inbox as `0x${string}`
}
}))
transport: http(config.ethUrl),
})

// Create child client for balance checks
// Create L2 client and extend with deposit actions
const childPublicClient = createPublicClient({
chain: localArbChain,
transport: http(config.arbUrl)
})
transport: http(config.arbUrl),
}).extend(arbitrumDepositActions())

// Get initial L2 balance
const initialBalance = await childPublicClient.getBalance({
address: account.address
address: account.address,
})
console.log('Initial child balance:', initialBalance)

// Prepare and send deposit transaction
const request = await childPublicClient.prepareDepositEthTransaction({
amount: depositAmount,
account,
})

const hash = await parentWalletClient.sendTransaction(request)

// Wait for L1 transaction
const receipt = await parentPublicClient.waitForTransactionReceipt({
hash,
})

expect(receipt.status).to.equal('success')

// Wait for L2 balance to increase
let finalBalance = initialBalance
let attempts = 0
const maxAttempts = 10

while (attempts < maxAttempts) {
await new Promise(resolve => setTimeout(resolve, 3000))

const currentBalance = await childPublicClient.getBalance({
address: account.address,
})

if (currentBalance > initialBalance) {
finalBalance = currentBalance
break
}

attempts++
}

const balanceDiff = finalBalance - initialBalance
expect(balanceDiff).to.equal(depositAmount)
})

it('deposits ETH from L1 to a different L2 address', async function () {
const account = privateKeyToAccount(`0x${config.ethKey}` as `0x${string}`)
const destinationAddress =
'0x1234567890123456789012345678901234567890' as `0x${string}`
const depositAmount = parseEther('0.01')
console.log('Deposit amount:', depositAmount)

const hash = await parentPublicClient.depositEth({
// Create L1 clients
const parentWalletClient = createWalletClient({
account,
chain: localEthChain,
transport: http(config.ethUrl),
})

const parentPublicClient = createPublicClient({
chain: localEthChain,
transport: http(config.ethUrl),
})

// Create L2 client and extend with deposit actions
const childPublicClient = createPublicClient({
chain: localArbChain,
transport: http(config.arbUrl),
}).extend(arbitrumDepositActions())

// Get initial destination balance
const initialBalance = await childPublicClient.getBalance({
address: destinationAddress,
})

// Prepare and send deposit transaction
const request = await childPublicClient.prepareDepositEthToTransaction({
amount: depositAmount,
account: account.address,
walletClient: parentWalletClient
destinationAddress,
parentPublicClient,
})

const hash = await parentWalletClient.sendTransaction({
...request,
chain: localEthChain,
})

// Wait for parent transaction
const receipt = await parentPublicClient.waitForTransactionReceipt({
// Wait for L1 transaction
const receipt = await parentPublicClient.waitForTransactionReceipt({
hash,
confirmations: 1
})

expect(receipt.status).to.equal('success')

// Poll for child balance change
// Wait for L2 balance to increase
let finalBalance = initialBalance
let attempts = 0
const maxAttempts = 10

while (attempts < maxAttempts) {
await new Promise(resolve => setTimeout(resolve, 3000))

const currentBalance = await childPublicClient.getBalance({
address: account.address
address: destinationAddress,
})

console.log(`Attempt ${attempts + 1} - Current balance:`, currentBalance)

if (currentBalance > initialBalance) {
finalBalance = currentBalance
break
}

attempts++
}

console.log('Final child balance:', finalBalance)
console.log('Balance difference:', finalBalance - initialBalance)

expect(Number(finalBalance)).to.be.greaterThan(
Number(initialBalance),
'child balance did not increase after deposit'
)
const balanceDiff = finalBalance - initialBalance
expect(balanceDiff).to.equal(depositAmount)
})
})
})

0 comments on commit f0c6ca2

Please sign in to comment.