-
Notifications
You must be signed in to change notification settings - Fork 204
feat: initialize viem package #565
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
base: main
Are you sure you want to change the base?
Changes from 11 commits
c88bc2e
e74b931
a10e420
757ac90
a7c2d6d
37afa5f
5b8ea8b
1262a9e
44b85bd
ad08638
3b25110
b61e1b5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
dist/** | ||
node_modules/** | ||
coverage/** | ||
src/lib/abi | ||
docs/** |
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/**/*"] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
build/** | ||
cache/** | ||
dist/** | ||
src/lib/abi/** | ||
.nyc_output |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
const baseConfig = require('../../.prettierrc.js') | ||
|
||
module.exports = { | ||
...baseConfig, | ||
} |
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" | ||
} | ||
} |
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), | ||
}) | ||
} |
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 | ||
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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think we need the childWalletClient here There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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? There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
}: 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, | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export * from './actions' | ||
export * from './createArbitrumClient' |
Uh oh!
There was an error while loading. Please reload this page.