Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip: pure viem api #551

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
"prepare": "yarn run gen:abi",
"gen:abi": "node ./scripts/genAbi.js",
"gen:network": "ts-node ./scripts/genNetwork.ts",
"gen:wagmi": "wagmi generate",
"prepublishOnly": "yarn build && yarn format",
"preversion": "yarn lint",
"prebuild": "yarn gen:abi",
Expand All @@ -50,7 +51,8 @@
"@ethersproject/bignumber": "^5.1.1",
"@ethersproject/bytes": "^5.0.8",
"async-mutex": "^0.4.0",
"ethers": "^5.1.0"
"ethers": "^5.1.0",
"viem": "^2.21.45"
},
"devDependencies": {
"@arbitrum/nitro-contracts": "^1.1.1",
Expand All @@ -65,6 +67,7 @@
"@typescript-eslint/eslint-plugin": "^5.14.0",
"@typescript-eslint/eslint-plugin-tslint": "^5.27.1",
"@typescript-eslint/parser": "^5.14.0",
"@wagmi/cli": "^2.1.18",
"audit-ci": "^6.3.0",
"axios": "^1.7.4",
"chai": "^4.2.0",
Expand All @@ -84,7 +87,7 @@
"ts-node": "^10.2.1",
"tslint": "^6.1.3",
"typechain": "7.0.0",
"typescript": "^4.9.5",
"typescript": "^5.5.4",
"yargs": "^17.3.1"
},
"files": [
Expand Down
42 changes: 42 additions & 0 deletions src/experimental/arbitrumDeposit/abis/inbox.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
export const inboxAbi = [
{
inputs: [],
name: 'depositEth',
outputs: [{ type: 'uint256' }],
stateMutability: 'payable',
type: 'function'
},
{
inputs: [
{ name: 'to', type: 'address' },
{ name: 'l2CallValue', type: 'uint256' },
{ name: 'maxSubmissionCost', type: 'uint256' },
{ name: 'excessFeeRefundAddress', type: 'address' },
{ name: 'callValueRefundAddress', type: 'address' },
{ name: 'gasLimit', type: 'uint256' },
{ name: 'maxFeePerGas', type: 'uint256' },
{ name: 'data', type: 'bytes' }
],
name: 'createRetryableTicket',
outputs: [{ type: 'uint256' }],
stateMutability: 'payable',
type: 'function'
},
{
anonymous: false,
inputs: [
{ indexed: false, name: 'messageNum', type: 'uint256' },
{ indexed: false, name: 'data', type: 'bytes' }
],
name: 'InboxMessageDelivered',
type: 'event'
},
{
anonymous: false,
inputs: [
{ indexed: false, name: 'messageNum', type: 'uint256' }
],
name: 'InboxMessageDeliveredFromOrigin',
type: 'event'
}
] as const
23 changes: 23 additions & 0 deletions src/experimental/arbitrumDeposit/abis/l1GatewayRouter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
export const l1GatewayRouterAbi = [
{
inputs: [
{ name: '_token', type: 'address' },
{ name: '_to', type: 'address' },
{ name: '_amount', type: 'uint256' },
{ name: '_maxGas', type: 'uint256' },
{ name: '_gasPriceBid', type: 'uint256' },
{ name: '_data', type: 'bytes' }
],
name: 'outboundTransfer',
outputs: [{ type: 'bytes' }],
stateMutability: 'payable',
type: 'function'
},
{
inputs: [{ name: '_token', type: 'address' }],
name: 'getGateway',
outputs: [{ type: 'address' }],
stateMutability: 'view',
type: 'function'
}
] as const
90 changes: 90 additions & 0 deletions src/experimental/arbitrumDeposit/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import {
type PublicClient,
type WalletClient,
type Client,
encodeFunctionData,
Account,
Address,
parseTransaction,
serializeTransaction
} from 'viem'
import { localEthChain } from '../chains'
import { inboxAbi } from './abis/inbox'

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

type ArbitrumChainConfig = {
ethBridge: {
inbox: `0x${string}`
}
}

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
}
}

return {
async depositEth({ amount, account, walletClient }) {
const request = await getDepositRequest({
amount,
account
})

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
})

// Parse and serialize with L2 chain ID
const parsedTx = parseTransaction((signedTx as any).raw)
const serializedTx = serializeTransaction({
...parsedTx,
})

// Send to L2
const hash = await parentPublicClient.sendRawTransaction({
serializedTransaction: serializedTx
})

return hash
}
}
}
}
56 changes: 56 additions & 0 deletions src/experimental/arbitrumDeposit/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import type { Address, Hash, Transport } from 'viem'
import type {
PublicClient,
WalletClient,
Account,
Chain
} from 'viem'

export type ArbitrumDepositConfig = {
inboxAddress: Address
}

export type GasOverrides = {
gasLimit?: {
min?: bigint
max?: bigint
}
maxFeePerGas?: {
min?: bigint
max?: bigint
}
maxSubmissionCost?: {
min?: bigint
max?: bigint
}
}

export type RetryableGasParams = {
gasLimit: bigint
maxFeePerGas: bigint
maxSubmissionCost: bigint
}

export type EthDepositParameters = {
amount: bigint
account: Account | Address
to?: Address // Optional destination address
retryableGasOverrides?: GasOverrides
}

export type Erc20DepositParameters = {
token: Address
amount: bigint
account: Account | Address
to?: Address // Optional destination address, defaults to sender
}

export type ApproveErc20Parameters = {
token: Address
amount: bigint
account: Account | Address
}

export type ArbitrumDepositActions = {
depositEth: (args: EthDepositParameters) => Promise<Hash>
}
39 changes: 39 additions & 0 deletions src/experimental/chains.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { type Chain } from 'viem'

export const localEthChain = {
id: 1337,
name: 'EthLocal',
nativeCurrency: {
decimals: 18,
name: 'Ether',
symbol: 'ETH',
},
rpcUrls: {
default: { http: ['http://localhost:8545'] },
public: { http: ['http://localhost:8545'] },
}
} as const satisfies Chain

export const localArbChain = {
id: 412346,
name: 'ArbLocal',
nativeCurrency: {
decimals: 18,
name: 'Ether',
symbol: 'ETH',
},
rpcUrls: {
default: { http: ['http://localhost:8547'] },
public: { http: ['http://localhost:8547'] },
}
} as const satisfies Chain

function getChainConfig(chainId: number) {
const chains = {
[localEthChain.id]: localEthChain,
[localArbChain.id]: localArbChain
}
return chains[chainId as keyof typeof chains]
}

export { getChainConfig }
90 changes: 90 additions & 0 deletions tests/integration/arbitrumDeposit.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { expect } from 'chai'
import { createWalletClient, createPublicClient, http, parseEther } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
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()

const account = privateKeyToAccount(`0x${config.ethKey}` as `0x${string}`)

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

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

// Create child client for balance checks
const childPublicClient = createPublicClient({
chain: localArbChain,
transport: http(config.arbUrl)
})

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

const depositAmount = parseEther('0.01')
console.log('Deposit amount:', depositAmount)

const hash = await parentPublicClient.depositEth({
amount: depositAmount,
account: account.address,
walletClient: parentWalletClient
})

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

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

// Poll for child balance change
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
})

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'
)
})
})
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"compilerOptions": {
"target": "ES2017",
"target": "ES2020",
"module": "commonjs",
"declaration": true,
"rootDir": "./src",
Expand Down
Loading
Loading