Skip to content

Commit

Permalink
initial commit for viem api
Browse files Browse the repository at this point in the history
  • Loading branch information
douglance committed Nov 14, 2024
1 parent b8d7b71 commit c947423
Show file tree
Hide file tree
Showing 9 changed files with 923 additions and 7 deletions.
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

0 comments on commit c947423

Please sign in to comment.