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

feat: initialize viem package #565

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
Open
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: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
"private": true,
"scripts": {
"audit:ci": "audit-ci --config ./audit-ci.jsonc",
"build": "yarn workspace @arbitrum/sdk build",
"build": "yarn workspaces run build",
"lint": "yarn workspaces run lint",
"format": "yarn workspaces run format",
"test:unit": "yarn workspaces run test:unit",
"test:integration": "yarn workspace @arbitrum/sdk test:integration",
"test:integration": "yarn workspaces run test:integration",
"gen:abi": "yarn workspace @arbitrum/sdk gen:abi",
"gen:network": "yarn workspace @arbitrum/sdk gen:network"
"gen:network": "yarn workspace @arbitrum/sdk gen:network",
"clean": "yarn workspaces run clean"
},
"workspaces": {
"packages": [
Expand Down
4 changes: 3 additions & 1 deletion packages/ethers-viem-compat/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@
"scripts": {
"build": "rm -rf dist && tsc -p tsconfig.json",
"test:unit": "mocha -r ts-node/register 'tests/**/*.test.ts'",
"test:integration": "echo 'No integration tests for ethers-viem-compat'",
"lint": "eslint .",
"format": "prettier './**/*.{js,json,md,ts,yml}' '!./src/lib/abi' --write && yarn run lint --fix"
"format": "prettier './**/*.{js,json,md,ts,yml}' '!./src/lib/abi' --write && yarn run lint --fix",
"clean": "rm -rf node_modules && rm -rf dist"
},
"peerDependencies": {
"ethers": "^5.0.0",
Expand Down
5 changes: 5 additions & 0 deletions packages/sdk-viem/.eslintignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
dist/**
node_modules/**
coverage/**
src/lib/abi
docs/**
8 changes: 8 additions & 0 deletions packages/sdk-viem/.eslintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"root": false,
"extends": ["../../.eslintrc.js"],
"parserOptions": {
"files": ["src/**/*.ts", "src/**/*.js"]
},
"ignorePatterns": ["dist/**/*", "node_modules/**/*"]
}
5 changes: 5 additions & 0 deletions packages/sdk-viem/.prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
build/**
cache/**
dist/**
src/lib/abi/**
.nyc_output
5 changes: 5 additions & 0 deletions packages/sdk-viem/.prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const baseConfig = require('../../.prettierrc.js')

module.exports = {
...baseConfig,
}
42 changes: 42 additions & 0 deletions packages/sdk-viem/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
{
"name": "@arbitrum/sdk-viem",
"version": "0.0.1",
"description": "Typescript library client-side interactions with Arbitrum using viem",
"author": "Offchain Labs, Inc.",
"license": "Apache-2.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"repository": {
"type": "git",
"url": "git+https://github.com/offchainlabs/arbitrum-sdk.git"
},
"engines": {
"node": ">=v11",
"npm": "please-use-yarn",
"yarn": ">= 1.0.0"
},
"bugs": {
"url": "https://github.com/offchainlabs/arbitrum-sdk/issues"
},
"homepage": "https://offchainlabs.com",
"files": [
"dist/**/*"
],
"scripts": {
"build": "rm -rf dist && tsc -p tsconfig.json",
"gen:network": "yarn workspace @arbitrum/sdk gen:network",
"test:unit": "echo 'No unit tests for sdk-viem'",
"test:integration": "mocha -r ts-node/register 'tests/**/*.test.ts'",
"lint": "eslint .",
"format": "prettier './**/*.{js,json,md,ts,yml}' '!./src/lib/abi' --write && yarn run lint --fix",
"clean": "rm -rf node_modules && rm -rf dist"
},
"dependencies": {
"@arbitrum/sdk": "workspace:*",
"@offchainlabs/ethers-viem-compat": "workspace:*"
},
"peerDependencies": {
"ethers": "^5.0.0",
"viem": "^2.0.0"
}
}
223 changes: 223 additions & 0 deletions packages/sdk-viem/src/actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import {
EthBridger,
ParentToChildMessageStatus,
ParentTransactionReceipt,
} from '@arbitrum/sdk'
import {
publicClientToProvider,
viemTransactionReceiptToEthersTransactionReceipt,
} from '@offchainlabs/ethers-viem-compat'
import { BigNumber } from 'ethers'
import {
Account,
Address,
Hash,
PublicClient,
TransactionRequest,
WalletClient,
} from 'viem'

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

const DEFAULT_CONFIRMATIONS = 1
const DEFAULT_TIMEOUT = 1000 * 60 * 5 // 5 minutes

export type WaitForCrossChainTxParameters = {
hash: Hash
timeout?: number
confirmations?: number
}

export type SendCrossChainTransactionParameters = {
request: TransactionRequest
timeout?: number
confirmations?: number
}

export type CrossChainTransactionStatus = {
status: 'success' | 'failed'
complete: boolean
message?: unknown
childTxReceipt?: unknown
hash: Hash
}

export type DepositEthParameters = {
amount: bigint
account: Account | Address
confirmations?: number
timeout?: number
}

export type ArbitrumDepositActions = {
prepareDepositEthTransaction: (
params: PrepareDepositEthParameters
) => Promise<TransactionRequest>
}

export type ArbitrumParentWalletActions = {
waitForCrossChainTransaction: (
params: WaitForCrossChainTxParameters
) => Promise<CrossChainTransactionStatus>

sendCrossChainTransaction: (
params: SendCrossChainTransactionParameters
) => Promise<CrossChainTransactionStatus>

depositEth: (
params: DepositEthParameters
) => Promise<CrossChainTransactionStatus>
}

export async function prepareDepositEthTransaction(
client: PublicClient,
{ amount, account }: PrepareDepositEthParameters
): Promise<TransactionRequest> {
const provider = publicClientToProvider(client)
const ethBridger = await EthBridger.fromProvider(provider)
const request = await ethBridger.getDepositRequest({
amount: BigNumber.from(amount),
from: typeof account === 'string' ? account : account.address,
})

return {
to: request.txRequest.to as Address,
value: BigNumber.from(request.txRequest.value).toBigInt(),
data: request.txRequest.data as Address,
}
}

export async function waitForCrossChainTransaction(
parentClient: PublicClient,
childClient: PublicClient,
{
hash,
confirmations = DEFAULT_CONFIRMATIONS,
timeout = DEFAULT_TIMEOUT,
}: WaitForCrossChainTxParameters
): Promise<CrossChainTransactionStatus> {
const childProvider = publicClientToProvider(childClient)

const viemReceipt = await parentClient.waitForTransactionReceipt({
hash,
confirmations,
})

const ethersReceipt =
viemTransactionReceiptToEthersTransactionReceipt(viemReceipt)
const parentReceipt = new ParentTransactionReceipt(ethersReceipt)

// Try to get eth deposits first
try {
const ethDeposits = await parentReceipt.getEthDeposits(childProvider)
if (ethDeposits.length > 0) {
const result = await ethDeposits[0].wait(confirmations, timeout)
return {
status: result ? 'success' : 'failed',
complete: Boolean(result),
message: ethDeposits[0],
childTxReceipt: result,
hash,
}
}
} catch (e) {
// Not an eth deposit, continue to check for other message types
}

// Check for other cross chain messages
try {
const messages = await parentReceipt.getParentToChildMessages(childProvider)
if (messages.length > 0) {
const result = await messages[0].waitForStatus(confirmations, timeout)
return {
status:
result.status === ParentToChildMessageStatus.REDEEMED
? 'success'
: 'failed',
complete: result.status === ParentToChildMessageStatus.REDEEMED,
message: messages[0],
childTxReceipt: result,
hash,
}
}
} catch (e) {
// Not a cross chain message
}

throw new Error('No cross chain message found in transaction')
}

export async function sendCrossChainTransaction(
parentClient: PublicClient,
childClient: PublicClient,
walletClient: WalletClient,
{
request,
confirmations = DEFAULT_CONFIRMATIONS,
timeout = DEFAULT_TIMEOUT,
}: SendCrossChainTransactionParameters
): Promise<CrossChainTransactionStatus> {
const hash = await walletClient.sendTransaction({
...request,
chain: walletClient.chain,
account: walletClient.account as Account,
})

return waitForCrossChainTransaction(parentClient, childClient, {
hash,
confirmations,
timeout,
})
}

export async function depositEth(
parentClient: PublicClient,
childClient: PublicClient,
walletClient: WalletClient,
{
amount,
account,
confirmations = DEFAULT_CONFIRMATIONS,
timeout = DEFAULT_TIMEOUT,
}: DepositEthParameters
): Promise<CrossChainTransactionStatus> {
const request = await prepareDepositEthTransaction(childClient, {
amount,
account,
})

return sendCrossChainTransaction(parentClient, childClient, walletClient, {
request,
confirmations,
timeout,
})
}

export function arbitrumParentClientActions() {
return (client: PublicClient): ArbitrumDepositActions => ({
prepareDepositEthTransaction: params =>
prepareDepositEthTransaction(client, params),
})
}

export function arbitrumParentWalletActions(
parentClient: PublicClient,
childClient: PublicClient
) {
return (walletClient: WalletClient): ArbitrumParentWalletActions => ({
waitForCrossChainTransaction: (params: WaitForCrossChainTxParameters) =>
waitForCrossChainTransaction(parentClient, childClient, params),
sendCrossChainTransaction: (params: SendCrossChainTransactionParameters) =>
sendCrossChainTransaction(
parentClient,
childClient,
walletClient,
params
),
depositEth: (params: DepositEthParameters) =>
depositEth(parentClient, childClient, walletClient, params),
})
}
57 changes: 57 additions & 0 deletions packages/sdk-viem/src/createArbitrumClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {
Chain,
PublicClient,
WalletClient,
createPublicClient,
http,
} from 'viem'
import {
ArbitrumParentWalletActions,
arbitrumParentWalletActions,
} from './actions'

export type ArbitrumClients = {
parentPublicClient: PublicClient
childPublicClient: PublicClient
chrstph-dvx marked this conversation as resolved.
Show resolved Hide resolved
parentWalletClient: WalletClient & ArbitrumParentWalletActions
childWalletClient?: WalletClient
}

export type CreateArbitrumClientParams = {
parentChain: Chain
childChain: Chain
parentRpcUrl?: string
childRpcUrl?: string
parentWalletClient: WalletClient
childWalletClient?: WalletClient
}

export function createArbitrumClient({
parentChain,
childChain,
parentRpcUrl,
childRpcUrl,
parentWalletClient,
childWalletClient,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need the childWalletClient here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, I would not request any client here, and just create everything within the function

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you clarify? typically, the consumer will want control of their clients, no?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If they want to keep control on their client, they can extend with actions on their own. I would expect createArbitrumClient to do every thing for me and create the clients not extend them. But it's not an issue.

}: CreateArbitrumClientParams): ArbitrumClients {
const parentPublicClient = createPublicClient({
chain: parentChain,
transport: http(parentRpcUrl || parentChain.rpcUrls.default.http[0]),
})

const childPublicClient = createPublicClient({
chain: childChain,
transport: http(childRpcUrl || childChain.rpcUrls.default.http[0]),
})

const extendedParentWalletClient = parentWalletClient.extend(
arbitrumParentWalletActions(parentPublicClient, childPublicClient)
)

return {
parentPublicClient,
childPublicClient,
parentWalletClient: extendedParentWalletClient,
childWalletClient,
}
}
2 changes: 2 additions & 0 deletions packages/sdk-viem/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './actions'
export * from './createArbitrumClient'
Loading
Loading