From 982f5dd79232230dac28fed9fbee683bb293d32a Mon Sep 17 00:00:00 2001 From: Doug Lance <4741454+douglance@users.noreply.github.com> Date: Wed, 18 Dec 2024 14:45:38 -0500 Subject: [PATCH 1/9] feat: adds viem package --- package.json | 2 +- .../customFeeTokenTestHelpers.ts | 71 +++++- packages/sdk/tests/testSetup.ts | 44 ++++ packages/viem/package.json | 32 +++ packages/viem/src/actions.ts | 224 ++++++++++++++++++ packages/viem/src/compatibility.ts | 95 ++++++++ packages/viem/src/createArbitrumClient.ts | 59 +++++ packages/viem/src/index.ts | 2 + packages/viem/tests/deposit.test.ts | 130 ++++++++++ packages/viem/tsconfig.json | 10 + yarn.lock | 108 ++++++++- 11 files changed, 771 insertions(+), 6 deletions(-) create mode 100644 packages/viem/package.json create mode 100644 packages/viem/src/actions.ts create mode 100644 packages/viem/src/compatibility.ts create mode 100644 packages/viem/src/createArbitrumClient.ts create mode 100644 packages/viem/src/index.ts create mode 100644 packages/viem/tests/deposit.test.ts create mode 100644 packages/viem/tsconfig.json diff --git a/package.json b/package.json index 21e09ec6a..b03ada272 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "ts-node": "^10.2.1", "tslint": "^6.1.3", "typechain": "7.0.0", - "typescript": "^4.9.5", + "typescript": "^5.6.3", "yargs": "^17.3.1" }, "resolutions": { diff --git a/packages/sdk/tests/integration/custom-fee-token/customFeeTokenTestHelpers.ts b/packages/sdk/tests/integration/custom-fee-token/customFeeTokenTestHelpers.ts index b84a8289b..0e4b097b4 100644 --- a/packages/sdk/tests/integration/custom-fee-token/customFeeTokenTestHelpers.ts +++ b/packages/sdk/tests/integration/custom-fee-token/customFeeTokenTestHelpers.ts @@ -1,5 +1,14 @@ import { StaticJsonRpcProvider } from '@ethersproject/providers' import { Signer, Wallet, ethers, utils } from 'ethers' +import { + Account, + parseEther, + parseUnits, + type Hex, + type WalletClient, + type Chain, + formatUnits, +} from 'viem' import { testSetup as _testSetup, @@ -58,7 +67,7 @@ export async function fundParentCustomFeeToken( const tx = await tokenContract.transfer( address, - utils.parseUnits('10', decimals) + utils.parseUnits('1000', decimals) ) await tx.wait() } @@ -109,3 +118,63 @@ export async function fundChildCustomFeeToken(childSigner: Signer) { }) await tx.wait() } + +export async function getAmountInEnvironmentDecimals( + amount: string +): Promise<[bigint, number]> { + if (isArbitrumNetworkWithCustomFeeToken()) { + const tokenDecimals = await getNativeTokenDecimals({ + parentProvider: ethProvider(), + childNetwork: localNetworks().l3Network!, + }) + return [parseUnits(amount, tokenDecimals), tokenDecimals] + } + return [parseEther(amount), 18] // ETH decimals +} + +export function normalizeBalanceDiffByDecimals( + balanceDiff: bigint, + tokenDecimals: number +): bigint { + // Convert to 18 decimals (ETH standard) for comparison + if (tokenDecimals === 18) return balanceDiff + + // Convert to decimal string with proper precision + const formattedDiff = formatUnits(balanceDiff, 18) + // Parse back with target decimals + return parseUnits(formattedDiff, tokenDecimals) +} + +export async function approveCustomFeeTokenWithViem({ + parentAccount, + parentWalletClient, + chain, +}: { + parentAccount: { address: string } + parentWalletClient: WalletClient + chain: Chain +}) { + if (!isArbitrumNetworkWithCustomFeeToken()) return + + const networks = localNetworks() + const inbox = networks.l3Network!.ethBridge.inbox + + const currentAllowance = await getParentCustomFeeTokenAllowance( + parentAccount.address, + inbox + ) + + // Only approve if allowance is insufficient + if (currentAllowance.lt(ethers.constants.MaxUint256)) { + const ethBridger = await EthBridger.fromProvider(arbProvider()) + const approveRequest = ethBridger.getApproveGasTokenRequest() + await parentWalletClient.sendTransaction({ + to: approveRequest.to as Hex, + data: approveRequest.data as Hex, + account: parentAccount as Account, + chain, + value: BigInt(0), + kzg: undefined, + }) + } +} diff --git a/packages/sdk/tests/testSetup.ts b/packages/sdk/tests/testSetup.ts index 597b0b511..ac5d067ea 100644 --- a/packages/sdk/tests/testSetup.ts +++ b/packages/sdk/tests/testSetup.ts @@ -40,6 +40,7 @@ import { isArbitrumNetworkWithCustomFeeToken, } from './integration/custom-fee-token/customFeeTokenTestHelpers' import { fundParentSigner } from './integration/testHelpers' +import { Chain } from 'viem' loadEnv() @@ -85,6 +86,8 @@ export const testSetup = async (): Promise<{ inboxTools: InboxTools parentDeployer: Signer childDeployer: Signer + localEthChain: Chain + localArbChain: Chain }> => { const ethProvider = new JsonRpcProvider(config.ethUrl) const arbProvider = new JsonRpcProvider(config.arbUrl) @@ -113,6 +116,23 @@ export const testSetup = async (): Promise<{ assertArbitrumNetworkHasTokenBridge(setChildChain) + // Generate Viem chains using the network data we already have + const localEthChain = generateViemChain( + { + chainId: setChildChain.parentChainId, + name: 'EthLocal', + }, + config.ethUrl + ) + + const localArbChain = generateViemChain( + { + chainId: setChildChain.chainId, + name: setChildChain.name, + }, + config.arbUrl + ) + const erc20Bridger = new Erc20Bridger(setChildChain) const adminErc20Bridger = new AdminErc20Bridger(setChildChain) const ethBridger = new EthBridger(setChildChain) @@ -136,6 +156,8 @@ export const testSetup = async (): Promise<{ inboxTools, parentDeployer, childDeployer, + localEthChain, + localArbChain, } } @@ -153,3 +175,25 @@ export function getLocalNetworksFromFile(): { return { l2Network: localL2, l3Network: localL3 } } + +function generateViemChain( + networkData: { + chainId: number + name: string + }, + rpcUrl: string +): Chain { + return { + id: networkData.chainId, + name: networkData.name, + nativeCurrency: { + decimals: 18, + name: 'Ether', + symbol: 'ETH', + }, + rpcUrls: { + default: { http: [rpcUrl] }, + public: { http: [rpcUrl] }, + }, + } as const +} diff --git a/packages/viem/package.json b/packages/viem/package.json new file mode 100644 index 000000000..48d6763e4 --- /dev/null +++ b/packages/viem/package.json @@ -0,0 +1,32 @@ +{ + "name": "@arbitrum/viem-sdk", + "version": "4.0.2", + "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", + "test": "mocha tests" + }, + "dependencies": { + "viem": "^2.21.55" + } +} diff --git a/packages/viem/src/actions.ts b/packages/viem/src/actions.ts new file mode 100644 index 000000000..62a2a9826 --- /dev/null +++ b/packages/viem/src/actions.ts @@ -0,0 +1,224 @@ +import { EthBridger } from '../../sdk/src/lib/assetBridger/ethBridger' + +import { + ParentToChildMessageStatus, + ParentTransactionReceipt, +} from '../../sdk/src/index' +import { BigNumber } from 'ethers' +import { + Account, + Address, + Hash, + PublicClient, + TransactionRequest, + WalletClient, +} from 'viem' +import { + transformPublicClientToProvider, + viemTransactionReceiptToEthersTransactionReceipt, +} from './compatibility' + +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 +} + +export type ArbitrumParentWalletActions = { + waitForCrossChainTransaction: ( + params: WaitForCrossChainTxParameters + ) => Promise + + sendCrossChainTransaction: ( + params: SendCrossChainTransactionParameters + ) => Promise + + depositEth: ( + params: DepositEthParameters + ) => Promise +} + +async function prepareDepositEthTransaction( + client: PublicClient, + { amount, account }: PrepareDepositEthParameters +): Promise { + 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 { + to: request.txRequest.to as `0x${string}`, + value: BigNumber.from(request.txRequest.value).toBigInt(), + data: request.txRequest.data as `0x${string}`, + } +} + +async function waitForCrossChainTransaction( + parentClient: PublicClient, + childClient: PublicClient, + { + hash, + confirmations = DEFAULT_CONFIRMATIONS, + timeout = DEFAULT_TIMEOUT, + }: WaitForCrossChainTxParameters +): Promise { + const childProvider = transformPublicClientToProvider(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') +} + +async function sendCrossChainTransaction( + parentClient: PublicClient, + childClient: PublicClient, + walletClient: WalletClient, + { + request, + confirmations = DEFAULT_CONFIRMATIONS, + timeout = DEFAULT_TIMEOUT, + }: SendCrossChainTransactionParameters +): Promise { + const hash = await walletClient.sendTransaction({ + ...request, + chain: walletClient.chain, + account: walletClient.account as Account, + }) + + return waitForCrossChainTransaction(parentClient, childClient, { + hash, + confirmations, + timeout, + }) +} + +async function depositEth( + parentClient: PublicClient, + childClient: PublicClient, + walletClient: WalletClient, + { + amount, + account, + confirmations = DEFAULT_CONFIRMATIONS, + timeout = DEFAULT_TIMEOUT, + }: DepositEthParameters +): Promise { + 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), + }) +} diff --git a/packages/viem/src/compatibility.ts b/packages/viem/src/compatibility.ts new file mode 100644 index 000000000..9acee96d4 --- /dev/null +++ b/packages/viem/src/compatibility.ts @@ -0,0 +1,95 @@ +import { + Log as EthersLog, + TransactionReceipt as EthersTransactionReceipt, +} from '@ethersproject/abstract-provider' +import { StaticJsonRpcProvider } from '@ethersproject/providers' +import { BigNumber } from 'ethers' +import { + Chain, + Client, + PublicClient, + Transport, + Log as ViemLog, + TransactionReceipt as ViemTransactionReceipt, +} from 'viem' + +// based on https://wagmi.sh/react/ethers-adapters#reference-implementation +export function publicClientToProvider( + publicClient: PublicClient +) { + 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') +} + +function viemLogToEthersLog(log: ViemLog): EthersLog { + return { + blockNumber: Number(log.blockNumber), + blockHash: log.blockHash!, + transactionIndex: log.transactionIndex!, + removed: log.removed, + address: log.address, + data: log.data, + topics: log.topics, + transactionHash: log.transactionHash!, + logIndex: log.logIndex!, + } +} + +export function viemTransactionReceiptToEthersTransactionReceipt( + receipt: ViemTransactionReceipt +): EthersTransactionReceipt { + return { + to: receipt.to!, + from: receipt.from!, + contractAddress: receipt.contractAddress!, + transactionIndex: receipt.transactionIndex, + gasUsed: BigNumber.from(receipt.gasUsed), + logsBloom: receipt.logsBloom, + blockHash: receipt.blockHash, + transactionHash: receipt.transactionHash, + logs: receipt.logs.map(log => viemLogToEthersLog(log)), + blockNumber: Number(receipt.blockNumber), + // todo: if we need this we can add it later + confirmations: -1, + cumulativeGasUsed: BigNumber.from(receipt.cumulativeGasUsed), + effectiveGasPrice: BigNumber.from(receipt.effectiveGasPrice), + // all transactions that we care about are well past byzantium + byzantium: true, + type: Number(receipt.type), + status: receipt.status === 'success' ? 1 : 0, + } +} diff --git a/packages/viem/src/createArbitrumClient.ts b/packages/viem/src/createArbitrumClient.ts new file mode 100644 index 000000000..eb30876ed --- /dev/null +++ b/packages/viem/src/createArbitrumClient.ts @@ -0,0 +1,59 @@ +import { + Chain, + PublicClient, + WalletClient, + createPublicClient, + http, +} from 'viem' +import { + ArbitrumDepositActions, + ArbitrumParentWalletActions, + arbitrumParentClientActions, + arbitrumParentWalletActions, +} from './actions' + +export type ArbitrumClients = { + parentPublicClient: PublicClient + childPublicClient: PublicClient & ArbitrumDepositActions + 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, +}: 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]), + }).extend(arbitrumParentClientActions()) + + const extendedParentWalletClient = parentWalletClient.extend( + arbitrumParentWalletActions(parentPublicClient, childPublicClient) + ) + + return { + parentPublicClient, + childPublicClient, + parentWalletClient: extendedParentWalletClient, + childWalletClient, + } +} diff --git a/packages/viem/src/index.ts b/packages/viem/src/index.ts new file mode 100644 index 000000000..297983871 --- /dev/null +++ b/packages/viem/src/index.ts @@ -0,0 +1,2 @@ +export * from './actions' +export * from './createArbitrumClient' diff --git a/packages/viem/tests/deposit.test.ts b/packages/viem/tests/deposit.test.ts new file mode 100644 index 000000000..1a826489a --- /dev/null +++ b/packages/viem/tests/deposit.test.ts @@ -0,0 +1,130 @@ +import { expect } from 'chai' +import { createWalletClient, http, parseEther, type Chain } from 'viem' +import { privateKeyToAccount } from 'viem/accounts' +import { config, testSetup } from '../../sdk/tests/testSetup' +import { createArbitrumClient } from '../src/createArbitrumClient' +import { fundParentSigner } from '../../sdk/tests/integration/testHelpers' +import { registerCustomArbitrumNetwork } from '../../sdk/src/lib/dataEntities/networks' +import { + approveParentCustomFeeToken, + fundParentCustomFeeToken, + isArbitrumNetworkWithCustomFeeToken, + getAmountInEnvironmentDecimals, + normalizeBalanceDiffByDecimals, + approveCustomFeeTokenWithViem, +} from '../../sdk/tests/integration/custom-fee-token/customFeeTokenTestHelpers' + +describe('deposit', function () { + this.timeout(300000) + + let localEthChain: Chain + let localArbChain: Chain + let setup: Awaited> + + before(async function () { + setup = await testSetup() + localEthChain = setup.localEthChain + localArbChain = setup.localArbChain + registerCustomArbitrumNetwork(setup.childChain) + }) + + beforeEach(async function () { + const parentAccount = privateKeyToAccount(`0x${config.ethKey}`) + await fundParentSigner(setup.parentSigner) + if (isArbitrumNetworkWithCustomFeeToken()) { + await fundParentCustomFeeToken(parentAccount.address) + await approveParentCustomFeeToken(setup.parentSigner) + } + }) + + it('deposits ETH from parent to child using deposit action', async function () { + const parentAccount = privateKeyToAccount(`0x${config.ethKey}`) + const [depositAmount, tokenDecimals] = await getAmountInEnvironmentDecimals( + '0.01' + ) + + const baseParentWalletClient = createWalletClient({ + account: parentAccount, + chain: localEthChain, + transport: http(config.ethUrl), + }) + + const baseChildWalletClient = createWalletClient({ + account: parentAccount, + chain: localArbChain, + transport: http(config.arbUrl), + }) + + const { childPublicClient, parentWalletClient } = createArbitrumClient({ + parentChain: localEthChain, + childChain: localArbChain, + parentWalletClient: baseParentWalletClient, + childWalletClient: baseChildWalletClient, + }) + + const initialBalance = await childPublicClient.getBalance({ + address: parentAccount.address, + }) + + if (isArbitrumNetworkWithCustomFeeToken()) { + await approveCustomFeeTokenWithViem({ + parentAccount, + parentWalletClient, + chain: localEthChain, + }) + } + + const result = await parentWalletClient.depositEth({ + amount: depositAmount, + account: parentAccount, + }) + + expect(result.status).to.equal('success') + + const finalBalance = await childPublicClient.getBalance({ + address: parentAccount.address, + }) + + const balanceDiff = finalBalance - initialBalance + const normalizedBalanceDiff = normalizeBalanceDiffByDecimals( + BigInt(balanceDiff), + tokenDecimals + ) + + expect(normalizedBalanceDiff.toString()).to.equal(depositAmount.toString()) + }) + + it('handles deposit failure gracefully', async function () { + const parentAccount = privateKeyToAccount(`0x${config.ethKey}`) + const depositAmount = parseEther('999999999') + + const baseParentWalletClient = createWalletClient({ + account: parentAccount, + chain: localEthChain, + transport: http(config.ethUrl), + }) + + const baseChildWalletClient = createWalletClient({ + account: parentAccount, + chain: localArbChain, + transport: http(config.arbUrl), + }) + + const { parentWalletClient } = createArbitrumClient({ + parentChain: localEthChain, + childChain: localArbChain, + parentWalletClient: baseParentWalletClient, + childWalletClient: baseChildWalletClient, + }) + + try { + await parentWalletClient.depositEth({ + amount: depositAmount, + account: parentAccount, + }) + expect.fail('Should have thrown an error') + } catch (error) { + expect(error).to.exist + } + }) +}) diff --git a/packages/viem/tsconfig.json b/packages/viem/tsconfig.json new file mode 100644 index 000000000..4035fbe9b --- /dev/null +++ b/packages/viem/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "target": "ES2020", + "rootDir": "./src", + "outDir": "./dist" + }, + "include": ["src/**/*.ts", "src/**/*.d.ts"], + "exclude": ["node_modules", "dist", "tests", "scripts"] +} diff --git a/yarn.lock b/yarn.lock index 07788bae1..e11549772 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@adraffy/ens-normalize@^1.10.1": + version "1.11.0" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.11.0.tgz#42cc67c5baa407ac25059fcd7d405cc5ecdb0c33" + integrity sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg== + "@aduh95/viz.js@^3.7.0": version "3.7.0" resolved "https://registry.yarnpkg.com/@aduh95/viz.js/-/viz.js-3.7.0.tgz#a20d86c5fc8f6abebdc39b96a4326e10375d77c0" @@ -1086,11 +1091,28 @@ tweetnacl "^1.0.3" tweetnacl-util "^0.15.1" +"@noble/curves@1.7.0", "@noble/curves@^1.4.0", "@noble/curves@^1.6.0", "@noble/curves@~1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.7.0.tgz#0512360622439256df892f21d25b388f52505e45" + integrity sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw== + dependencies: + "@noble/hashes" "1.6.0" + "@noble/hashes@1.2.0", "@noble/hashes@~1.2.0": version "1.2.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.2.0.tgz#a3150eeb09cc7ab207ebf6d7b9ad311a9bdbed12" integrity sha512-FZfhjEDbT5GRswV3C6uvLPHMiVD6lQBmpoX5+eSiPaMTXte/IKqI5dykDxzZB/WBeK/CDuQRBWarPdi3FNY2zQ== +"@noble/hashes@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.6.0.tgz#d4bfb516ad6e7b5111c216a5cc7075f4cf19e6c5" + integrity sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ== + +"@noble/hashes@1.6.1", "@noble/hashes@^1.4.0", "@noble/hashes@^1.5.0", "@noble/hashes@~1.6.0": + version "1.6.1" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.6.1.tgz#df6e5943edcea504bac61395926d6fd67869a0d5" + integrity sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w== + "@noble/secp256k1@1.7.1", "@noble/secp256k1@~1.7.0": version "1.7.1" resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c" @@ -1393,6 +1415,11 @@ resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.3.tgz#8584115565228290a6c6c4961973e0903bb3df2f" integrity sha512-/+SgoRjLq7Xlf0CWuLHq2LUZeL/w65kfzAPG5NH9pcmBhs+nunQTn4gvdwgMTIXnt9b2C/1SeL2XiysZEyIC9Q== +"@scure/base@~1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.2.1.tgz#dd0b2a533063ca612c17aa9ad26424a2ff5aa865" + integrity sha512-DGmGtC8Tt63J5GfHgfl5CuAXh96VF/LD8K9Hr/Gv0J2lAoRGlPOMpqMpMbCTOoOJMZCk2Xt+DskdDyn6dEFdzQ== + "@scure/bip32@1.1.5": version "1.1.5" resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.1.5.tgz#d2ccae16dcc2e75bc1d75f5ef3c66a338d1ba300" @@ -1402,6 +1429,15 @@ "@noble/secp256k1" "~1.7.0" "@scure/base" "~1.1.0" +"@scure/bip32@1.6.0", "@scure/bip32@^1.5.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.6.0.tgz#6dbc6b4af7c9101b351f41231a879d8da47e0891" + integrity sha512-82q1QfklrUUdXJzjuRU7iG7D7XiFx5PHYVS0+oeNKhyDLT7WPqs6pBcM2W5ZdwOwKCwoE1Vy1se+DHjcXwCYnA== + dependencies: + "@noble/curves" "~1.7.0" + "@noble/hashes" "~1.6.0" + "@scure/base" "~1.2.1" + "@scure/bip39@1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.1.1.tgz#b54557b2e86214319405db819c4b6a370cf340c5" @@ -1410,6 +1446,14 @@ "@noble/hashes" "~1.2.0" "@scure/base" "~1.1.0" +"@scure/bip39@1.5.0", "@scure/bip39@^1.4.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.5.0.tgz#c8f9533dbd787641b047984356531d84485f19be" + integrity sha512-Dop+ASYhnrwm9+HA/HwXg7j2ZqM6yk2fyLWb5znexjctFY3+E+eU8cIWI0Pql0Qx4hPZCijlGq4OL71g+Uz30A== + dependencies: + "@noble/hashes" "~1.6.0" + "@scure/base" "~1.2.1" + "@sentry/core@5.30.0": version "5.30.0" resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.30.0.tgz#6b203664f69e75106ee8b5a2fe1d717379b331f3" @@ -1766,6 +1810,11 @@ JSONStream@^1.3.5: jsonparse "^1.2.0" through ">=2.2.7 <3" +abitype@1.0.7, abitype@^1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.7.tgz#876a0005d211e1c9132825d45bcee7b46416b284" + integrity sha512-ZfYYSktDQUwc2eduYu8C4wOs+RDPmnRYMh7zNfzeMtGGgb0U+6tLGjixUic6mXf5xKKCcgT5Qp6cv39tOARVFw== + abstract-level@^1.0.0, abstract-level@^1.0.2, abstract-level@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/abstract-level/-/abstract-level-1.0.3.tgz#78a67d3d84da55ee15201486ab44c09560070741" @@ -3212,6 +3261,11 @@ event-stream@4.0.1: stream-combiner "^0.2.2" through "^2.3.8" +eventemitter3@5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" @@ -4083,6 +4137,11 @@ isexe@^2.0.0: resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== +isows@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.6.tgz#0da29d706fa51551c663c627ace42769850f86e7" + integrity sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw== + istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3" @@ -4775,6 +4834,19 @@ os-tmpdir@~1.0.2: resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" integrity sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g== +ox@0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/ox/-/ox-0.1.2.tgz#0f791be2ccabeaf4928e6d423498fe1c8094e560" + integrity sha512-ak/8K0Rtphg9vnRJlbOdaX9R7cmxD2MiSthjWGaQdMk3D7hrAlDoM+6Lxn7hN52Za3vrXfZ7enfke/5WjolDww== + dependencies: + "@adraffy/ens-normalize" "^1.10.1" + "@noble/curves" "^1.6.0" + "@noble/hashes" "^1.5.0" + "@scure/bip32" "^1.5.0" + "@scure/bip39" "^1.4.0" + abitype "^1.0.6" + eventemitter3 "5.0.1" + p-limit@^1.1.0: version "1.3.0" resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" @@ -5911,10 +5983,10 @@ typedarray-to-buffer@^3.1.5: dependencies: is-typedarray "^1.0.0" -typescript@^4.9.5: - version "4.9.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" - integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== +typescript@^5.6.3: + version "5.7.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.7.2.tgz#3169cf8c4c8a828cde53ba9ecb3d2b1d5dd67be6" + integrity sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg== typical@^4.0.0: version "4.0.0" @@ -6006,6 +6078,29 @@ v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== +viem@^2.21.55: + version "2.21.55" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.21.55.tgz#a57ad31fcf2a0f6c011b1909f02c94421ec4f781" + integrity sha512-PgXew7C11cAuEtOSgRyQx2kJxEOPUwIwZA9dMglRByqJuFVA7wSGZZOOo/93iylAA8E15bEdqy9xulU3oKZ70Q== + dependencies: + "@noble/curves" "1.7.0" + "@noble/hashes" "1.6.1" + "@scure/bip32" "1.6.0" + "@scure/bip39" "1.5.0" + abitype "1.0.7" + isows "1.0.6" + ox "0.1.2" + webauthn-p256 "0.0.10" + ws "8.18.0" + +webauthn-p256@0.0.10: + version "0.0.10" + resolved "https://registry.yarnpkg.com/webauthn-p256/-/webauthn-p256-0.0.10.tgz#877e75abe8348d3e14485932968edf3325fd2fdd" + integrity sha512-EeYD+gmIT80YkSIDb2iWq0lq2zbHo1CxHlQTeJ+KkCILWpVy3zASH3ByD4bopzfk0uCwXxLqKGLqp2W4O28VFA== + dependencies: + "@noble/curves" "^1.4.0" + "@noble/hashes" "^1.4.0" + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" @@ -6121,6 +6216,11 @@ ws@7.4.6, ws@7.5.10, ws@^7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== +ws@8.18.0: + version "8.18.0" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" + integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== + ws@8.5.0: version "8.5.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.5.0.tgz#bfb4be96600757fe5382de12c670dab984a1ed4f" From 7dbc8cecc0407b4820c66440b65180631bece5fe Mon Sep 17 00:00:00 2001 From: Doug Lance <4741454+douglance@users.noreply.github.com> Date: Thu, 19 Dec 2024 09:48:48 -0500 Subject: [PATCH 2/9] feat: adds initial viem package --- .../customFeeTokenTestHelpers.ts | 71 +----- packages/sdk/tests/testSetup.ts | 44 ---- packages/viem/src/actions.ts | 224 ------------------ packages/viem/src/createArbitrumClient.ts | 59 ----- packages/viem/src/index.ts | 3 +- packages/viem/tests/deposit.test.ts | 130 ---------- packages/viem/tsconfig.json | 5 +- 7 files changed, 5 insertions(+), 531 deletions(-) delete mode 100644 packages/viem/src/actions.ts delete mode 100644 packages/viem/src/createArbitrumClient.ts delete mode 100644 packages/viem/tests/deposit.test.ts diff --git a/packages/sdk/tests/integration/custom-fee-token/customFeeTokenTestHelpers.ts b/packages/sdk/tests/integration/custom-fee-token/customFeeTokenTestHelpers.ts index 0e4b097b4..b84a8289b 100644 --- a/packages/sdk/tests/integration/custom-fee-token/customFeeTokenTestHelpers.ts +++ b/packages/sdk/tests/integration/custom-fee-token/customFeeTokenTestHelpers.ts @@ -1,14 +1,5 @@ import { StaticJsonRpcProvider } from '@ethersproject/providers' import { Signer, Wallet, ethers, utils } from 'ethers' -import { - Account, - parseEther, - parseUnits, - type Hex, - type WalletClient, - type Chain, - formatUnits, -} from 'viem' import { testSetup as _testSetup, @@ -67,7 +58,7 @@ export async function fundParentCustomFeeToken( const tx = await tokenContract.transfer( address, - utils.parseUnits('1000', decimals) + utils.parseUnits('10', decimals) ) await tx.wait() } @@ -118,63 +109,3 @@ export async function fundChildCustomFeeToken(childSigner: Signer) { }) await tx.wait() } - -export async function getAmountInEnvironmentDecimals( - amount: string -): Promise<[bigint, number]> { - if (isArbitrumNetworkWithCustomFeeToken()) { - const tokenDecimals = await getNativeTokenDecimals({ - parentProvider: ethProvider(), - childNetwork: localNetworks().l3Network!, - }) - return [parseUnits(amount, tokenDecimals), tokenDecimals] - } - return [parseEther(amount), 18] // ETH decimals -} - -export function normalizeBalanceDiffByDecimals( - balanceDiff: bigint, - tokenDecimals: number -): bigint { - // Convert to 18 decimals (ETH standard) for comparison - if (tokenDecimals === 18) return balanceDiff - - // Convert to decimal string with proper precision - const formattedDiff = formatUnits(balanceDiff, 18) - // Parse back with target decimals - return parseUnits(formattedDiff, tokenDecimals) -} - -export async function approveCustomFeeTokenWithViem({ - parentAccount, - parentWalletClient, - chain, -}: { - parentAccount: { address: string } - parentWalletClient: WalletClient - chain: Chain -}) { - if (!isArbitrumNetworkWithCustomFeeToken()) return - - const networks = localNetworks() - const inbox = networks.l3Network!.ethBridge.inbox - - const currentAllowance = await getParentCustomFeeTokenAllowance( - parentAccount.address, - inbox - ) - - // Only approve if allowance is insufficient - if (currentAllowance.lt(ethers.constants.MaxUint256)) { - const ethBridger = await EthBridger.fromProvider(arbProvider()) - const approveRequest = ethBridger.getApproveGasTokenRequest() - await parentWalletClient.sendTransaction({ - to: approveRequest.to as Hex, - data: approveRequest.data as Hex, - account: parentAccount as Account, - chain, - value: BigInt(0), - kzg: undefined, - }) - } -} diff --git a/packages/sdk/tests/testSetup.ts b/packages/sdk/tests/testSetup.ts index ac5d067ea..597b0b511 100644 --- a/packages/sdk/tests/testSetup.ts +++ b/packages/sdk/tests/testSetup.ts @@ -40,7 +40,6 @@ import { isArbitrumNetworkWithCustomFeeToken, } from './integration/custom-fee-token/customFeeTokenTestHelpers' import { fundParentSigner } from './integration/testHelpers' -import { Chain } from 'viem' loadEnv() @@ -86,8 +85,6 @@ export const testSetup = async (): Promise<{ inboxTools: InboxTools parentDeployer: Signer childDeployer: Signer - localEthChain: Chain - localArbChain: Chain }> => { const ethProvider = new JsonRpcProvider(config.ethUrl) const arbProvider = new JsonRpcProvider(config.arbUrl) @@ -116,23 +113,6 @@ export const testSetup = async (): Promise<{ assertArbitrumNetworkHasTokenBridge(setChildChain) - // Generate Viem chains using the network data we already have - const localEthChain = generateViemChain( - { - chainId: setChildChain.parentChainId, - name: 'EthLocal', - }, - config.ethUrl - ) - - const localArbChain = generateViemChain( - { - chainId: setChildChain.chainId, - name: setChildChain.name, - }, - config.arbUrl - ) - const erc20Bridger = new Erc20Bridger(setChildChain) const adminErc20Bridger = new AdminErc20Bridger(setChildChain) const ethBridger = new EthBridger(setChildChain) @@ -156,8 +136,6 @@ export const testSetup = async (): Promise<{ inboxTools, parentDeployer, childDeployer, - localEthChain, - localArbChain, } } @@ -175,25 +153,3 @@ export function getLocalNetworksFromFile(): { return { l2Network: localL2, l3Network: localL3 } } - -function generateViemChain( - networkData: { - chainId: number - name: string - }, - rpcUrl: string -): Chain { - return { - id: networkData.chainId, - name: networkData.name, - nativeCurrency: { - decimals: 18, - name: 'Ether', - symbol: 'ETH', - }, - rpcUrls: { - default: { http: [rpcUrl] }, - public: { http: [rpcUrl] }, - }, - } as const -} diff --git a/packages/viem/src/actions.ts b/packages/viem/src/actions.ts deleted file mode 100644 index 62a2a9826..000000000 --- a/packages/viem/src/actions.ts +++ /dev/null @@ -1,224 +0,0 @@ -import { EthBridger } from '../../sdk/src/lib/assetBridger/ethBridger' - -import { - ParentToChildMessageStatus, - ParentTransactionReceipt, -} from '../../sdk/src/index' -import { BigNumber } from 'ethers' -import { - Account, - Address, - Hash, - PublicClient, - TransactionRequest, - WalletClient, -} from 'viem' -import { - transformPublicClientToProvider, - viemTransactionReceiptToEthersTransactionReceipt, -} from './compatibility' - -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 -} - -export type ArbitrumParentWalletActions = { - waitForCrossChainTransaction: ( - params: WaitForCrossChainTxParameters - ) => Promise - - sendCrossChainTransaction: ( - params: SendCrossChainTransactionParameters - ) => Promise - - depositEth: ( - params: DepositEthParameters - ) => Promise -} - -async function prepareDepositEthTransaction( - client: PublicClient, - { amount, account }: PrepareDepositEthParameters -): Promise { - 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 { - to: request.txRequest.to as `0x${string}`, - value: BigNumber.from(request.txRequest.value).toBigInt(), - data: request.txRequest.data as `0x${string}`, - } -} - -async function waitForCrossChainTransaction( - parentClient: PublicClient, - childClient: PublicClient, - { - hash, - confirmations = DEFAULT_CONFIRMATIONS, - timeout = DEFAULT_TIMEOUT, - }: WaitForCrossChainTxParameters -): Promise { - const childProvider = transformPublicClientToProvider(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') -} - -async function sendCrossChainTransaction( - parentClient: PublicClient, - childClient: PublicClient, - walletClient: WalletClient, - { - request, - confirmations = DEFAULT_CONFIRMATIONS, - timeout = DEFAULT_TIMEOUT, - }: SendCrossChainTransactionParameters -): Promise { - const hash = await walletClient.sendTransaction({ - ...request, - chain: walletClient.chain, - account: walletClient.account as Account, - }) - - return waitForCrossChainTransaction(parentClient, childClient, { - hash, - confirmations, - timeout, - }) -} - -async function depositEth( - parentClient: PublicClient, - childClient: PublicClient, - walletClient: WalletClient, - { - amount, - account, - confirmations = DEFAULT_CONFIRMATIONS, - timeout = DEFAULT_TIMEOUT, - }: DepositEthParameters -): Promise { - 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), - }) -} diff --git a/packages/viem/src/createArbitrumClient.ts b/packages/viem/src/createArbitrumClient.ts deleted file mode 100644 index eb30876ed..000000000 --- a/packages/viem/src/createArbitrumClient.ts +++ /dev/null @@ -1,59 +0,0 @@ -import { - Chain, - PublicClient, - WalletClient, - createPublicClient, - http, -} from 'viem' -import { - ArbitrumDepositActions, - ArbitrumParentWalletActions, - arbitrumParentClientActions, - arbitrumParentWalletActions, -} from './actions' - -export type ArbitrumClients = { - parentPublicClient: PublicClient - childPublicClient: PublicClient & ArbitrumDepositActions - 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, -}: 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]), - }).extend(arbitrumParentClientActions()) - - const extendedParentWalletClient = parentWalletClient.extend( - arbitrumParentWalletActions(parentPublicClient, childPublicClient) - ) - - return { - parentPublicClient, - childPublicClient, - parentWalletClient: extendedParentWalletClient, - childWalletClient, - } -} diff --git a/packages/viem/src/index.ts b/packages/viem/src/index.ts index 297983871..2d009673b 100644 --- a/packages/viem/src/index.ts +++ b/packages/viem/src/index.ts @@ -1,2 +1 @@ -export * from './actions' -export * from './createArbitrumClient' +export * from './compatibility' diff --git a/packages/viem/tests/deposit.test.ts b/packages/viem/tests/deposit.test.ts deleted file mode 100644 index 1a826489a..000000000 --- a/packages/viem/tests/deposit.test.ts +++ /dev/null @@ -1,130 +0,0 @@ -import { expect } from 'chai' -import { createWalletClient, http, parseEther, type Chain } from 'viem' -import { privateKeyToAccount } from 'viem/accounts' -import { config, testSetup } from '../../sdk/tests/testSetup' -import { createArbitrumClient } from '../src/createArbitrumClient' -import { fundParentSigner } from '../../sdk/tests/integration/testHelpers' -import { registerCustomArbitrumNetwork } from '../../sdk/src/lib/dataEntities/networks' -import { - approveParentCustomFeeToken, - fundParentCustomFeeToken, - isArbitrumNetworkWithCustomFeeToken, - getAmountInEnvironmentDecimals, - normalizeBalanceDiffByDecimals, - approveCustomFeeTokenWithViem, -} from '../../sdk/tests/integration/custom-fee-token/customFeeTokenTestHelpers' - -describe('deposit', function () { - this.timeout(300000) - - let localEthChain: Chain - let localArbChain: Chain - let setup: Awaited> - - before(async function () { - setup = await testSetup() - localEthChain = setup.localEthChain - localArbChain = setup.localArbChain - registerCustomArbitrumNetwork(setup.childChain) - }) - - beforeEach(async function () { - const parentAccount = privateKeyToAccount(`0x${config.ethKey}`) - await fundParentSigner(setup.parentSigner) - if (isArbitrumNetworkWithCustomFeeToken()) { - await fundParentCustomFeeToken(parentAccount.address) - await approveParentCustomFeeToken(setup.parentSigner) - } - }) - - it('deposits ETH from parent to child using deposit action', async function () { - const parentAccount = privateKeyToAccount(`0x${config.ethKey}`) - const [depositAmount, tokenDecimals] = await getAmountInEnvironmentDecimals( - '0.01' - ) - - const baseParentWalletClient = createWalletClient({ - account: parentAccount, - chain: localEthChain, - transport: http(config.ethUrl), - }) - - const baseChildWalletClient = createWalletClient({ - account: parentAccount, - chain: localArbChain, - transport: http(config.arbUrl), - }) - - const { childPublicClient, parentWalletClient } = createArbitrumClient({ - parentChain: localEthChain, - childChain: localArbChain, - parentWalletClient: baseParentWalletClient, - childWalletClient: baseChildWalletClient, - }) - - const initialBalance = await childPublicClient.getBalance({ - address: parentAccount.address, - }) - - if (isArbitrumNetworkWithCustomFeeToken()) { - await approveCustomFeeTokenWithViem({ - parentAccount, - parentWalletClient, - chain: localEthChain, - }) - } - - const result = await parentWalletClient.depositEth({ - amount: depositAmount, - account: parentAccount, - }) - - expect(result.status).to.equal('success') - - const finalBalance = await childPublicClient.getBalance({ - address: parentAccount.address, - }) - - const balanceDiff = finalBalance - initialBalance - const normalizedBalanceDiff = normalizeBalanceDiffByDecimals( - BigInt(balanceDiff), - tokenDecimals - ) - - expect(normalizedBalanceDiff.toString()).to.equal(depositAmount.toString()) - }) - - it('handles deposit failure gracefully', async function () { - const parentAccount = privateKeyToAccount(`0x${config.ethKey}`) - const depositAmount = parseEther('999999999') - - const baseParentWalletClient = createWalletClient({ - account: parentAccount, - chain: localEthChain, - transport: http(config.ethUrl), - }) - - const baseChildWalletClient = createWalletClient({ - account: parentAccount, - chain: localArbChain, - transport: http(config.arbUrl), - }) - - const { parentWalletClient } = createArbitrumClient({ - parentChain: localEthChain, - childChain: localArbChain, - parentWalletClient: baseParentWalletClient, - childWalletClient: baseChildWalletClient, - }) - - try { - await parentWalletClient.depositEth({ - amount: depositAmount, - account: parentAccount, - }) - expect.fail('Should have thrown an error') - } catch (error) { - expect(error).to.exist - } - }) -}) diff --git a/packages/viem/tsconfig.json b/packages/viem/tsconfig.json index 4035fbe9b..70de820d0 100644 --- a/packages/viem/tsconfig.json +++ b/packages/viem/tsconfig.json @@ -3,8 +3,9 @@ "compilerOptions": { "target": "ES2020", "rootDir": "./src", - "outDir": "./dist" + "outDir": "./dist", + "skipLibCheck": true }, "include": ["src/**/*.ts", "src/**/*.d.ts"], - "exclude": ["node_modules", "dist", "tests", "scripts"] + "exclude": ["node_modules", "dist"] } From e5230f1e3b34a2287b1c8bbcb4a2b89934810451 Mon Sep 17 00:00:00 2001 From: Doug Lance <4741454+douglance@users.noreply.github.com> Date: Fri, 20 Dec 2024 09:06:00 -0500 Subject: [PATCH 3/9] swap naming --- packages/{viem => viem-compatibility}/package.json | 7 +++---- packages/{viem => viem-compatibility}/src/compatibility.ts | 0 packages/{viem => viem-compatibility}/src/index.ts | 0 packages/{viem => viem-compatibility}/tsconfig.json | 0 4 files changed, 3 insertions(+), 4 deletions(-) rename packages/{viem => viem-compatibility}/package.json (74%) rename packages/{viem => viem-compatibility}/src/compatibility.ts (100%) rename packages/{viem => viem-compatibility}/src/index.ts (100%) rename packages/{viem => viem-compatibility}/tsconfig.json (100%) diff --git a/packages/viem/package.json b/packages/viem-compatibility/package.json similarity index 74% rename from packages/viem/package.json rename to packages/viem-compatibility/package.json index 48d6763e4..9e58a5344 100644 --- a/packages/viem/package.json +++ b/packages/viem-compatibility/package.json @@ -1,7 +1,7 @@ { - "name": "@arbitrum/viem-sdk", + "name": "@arbitrum/viem-compatibility", "version": "4.0.2", - "description": "Typescript library client-side interactions with Arbitrum using viem", + "description": "Typescript library for compatibility with viem", "author": "Offchain Labs, Inc.", "license": "Apache-2.0", "main": "dist/index.js", @@ -23,8 +23,7 @@ "dist/**/*" ], "scripts": { - "build": "rm -rf dist && tsc -p tsconfig.json", - "test": "mocha tests" + "build": "rm -rf dist && tsc -p tsconfig.json" }, "dependencies": { "viem": "^2.21.55" diff --git a/packages/viem/src/compatibility.ts b/packages/viem-compatibility/src/compatibility.ts similarity index 100% rename from packages/viem/src/compatibility.ts rename to packages/viem-compatibility/src/compatibility.ts diff --git a/packages/viem/src/index.ts b/packages/viem-compatibility/src/index.ts similarity index 100% rename from packages/viem/src/index.ts rename to packages/viem-compatibility/src/index.ts diff --git a/packages/viem/tsconfig.json b/packages/viem-compatibility/tsconfig.json similarity index 100% rename from packages/viem/tsconfig.json rename to packages/viem-compatibility/tsconfig.json From 5f48e518fef8472331a10ff14623f445f643d068 Mon Sep 17 00:00:00 2001 From: Doug Lance <4741454+douglance@users.noreply.github.com> Date: Fri, 20 Dec 2024 09:07:58 -0500 Subject: [PATCH 4/9] fix version --- packages/viem-compatibility/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/viem-compatibility/package.json b/packages/viem-compatibility/package.json index 9e58a5344..39a3f6da0 100644 --- a/packages/viem-compatibility/package.json +++ b/packages/viem-compatibility/package.json @@ -1,7 +1,7 @@ { "name": "@arbitrum/viem-compatibility", - "version": "4.0.2", - "description": "Typescript library for compatibility with viem", + "version": "0.0.1", + "description": "Typescript library for ethers compatibility with viem", "author": "Offchain Labs, Inc.", "license": "Apache-2.0", "main": "dist/index.js", From 798b640ecb333a06dcda4027908a245c5380c08f Mon Sep 17 00:00:00 2001 From: Doug Lance <4741454+douglance@users.noreply.github.com> Date: Fri, 20 Dec 2024 09:35:40 -0500 Subject: [PATCH 5/9] adds tests for viem compaitibility --- package.json | 2 +- packages/viem-compatibility/package.json | 10 +- .../tests/compatibility.test.ts | 146 ++++++++++++++++++ 3 files changed, 156 insertions(+), 2 deletions(-) create mode 100644 packages/viem-compatibility/tests/compatibility.test.ts diff --git a/package.json b/package.json index 151390d98..73f49f6e7 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "build": "yarn workspace @arbitrum/sdk build", "lint": "yarn workspace @arbitrum/sdk lint", "format": "yarn workspace @arbitrum/sdk format", - "test:unit": "yarn workspace @arbitrum/sdk test:unit", + "test:unit": "yarn workspaces run test:unit", "test:integration": "yarn workspace @arbitrum/sdk test:integration", "gen:abi": "yarn workspace @arbitrum/sdk gen:abi", "gen:network": "yarn workspace @arbitrum/sdk gen:network" diff --git a/packages/viem-compatibility/package.json b/packages/viem-compatibility/package.json index 39a3f6da0..ec3d8ec24 100644 --- a/packages/viem-compatibility/package.json +++ b/packages/viem-compatibility/package.json @@ -23,9 +23,17 @@ "dist/**/*" ], "scripts": { - "build": "rm -rf dist && tsc -p tsconfig.json" + "build": "rm -rf dist && tsc -p tsconfig.json", + "test:unit": "mocha -r ts-node/register 'tests/**/*.test.ts'" }, "dependencies": { "viem": "^2.21.55" + }, + "devDependencies": { + "@types/chai": "^4.2.11", + "@types/mocha": "^9.0.0", + "chai": "^4.2.0", + "mocha": "^9.2.1", + "ts-node": "^10.2.1" } } diff --git a/packages/viem-compatibility/tests/compatibility.test.ts b/packages/viem-compatibility/tests/compatibility.test.ts new file mode 100644 index 000000000..0d3f5b258 --- /dev/null +++ b/packages/viem-compatibility/tests/compatibility.test.ts @@ -0,0 +1,146 @@ +import { expect } from 'chai' +import { StaticJsonRpcProvider } from '@ethersproject/providers' +import { + createPublicClient, + defineChain, + http, + PublicClient, + TransactionReceipt, +} from 'viem' +import { mainnet } from 'viem/chains' +import { BigNumber } from 'ethers' +import { + publicClientToProvider, + transformPublicClientToProvider, + viemTransactionReceiptToEthersTransactionReceipt, +} from '../src/compatibility' + +const testChain = defineChain({ + ...mainnet, + rpcUrls: { + default: { + http: ['https://example.com'], + }, + public: { + http: ['https://example.com'], + }, + }, +}) + +describe('viem compatibility', () => { + describe('publicClientToProvider', () => { + it('converts a public client to a provider', () => { + const transport = http('https://example.com') + const publicClient = createPublicClient({ + chain: testChain, + transport, + }) as unknown as PublicClient + + const provider = publicClientToProvider(publicClient) + expect(provider).to.be.instanceOf(StaticJsonRpcProvider) + expect(provider.network.chainId).to.equal(testChain.id) + expect(provider.network.name).to.equal(testChain.name) + expect(provider.connection.url).to.equal('https://example.com') + }) + + it('throws error when chain is undefined', () => { + const publicClient = { + chain: undefined, + } as unknown as PublicClient + + expect(() => publicClientToProvider(publicClient)).to.throw( + '[publicClientToProvider] "chain" is undefined' + ) + }) + }) + + describe('transformPublicClientToProvider', () => { + it('transforms valid public client to provider', () => { + const transport = http('https://example.com') + const publicClient = createPublicClient({ + chain: testChain, + transport, + }) as unknown as PublicClient + + const provider = transformPublicClientToProvider(publicClient) + expect(provider).to.be.instanceOf(StaticJsonRpcProvider) + }) + + it('throws error for invalid provider', () => { + const invalidClient = {} as PublicClient + + expect(() => transformPublicClientToProvider(invalidClient)).to.throw( + 'Invalid provider' + ) + }) + }) + + describe('viemTransactionReceiptToEthersTransactionReceipt', () => { + it('converts viem transaction receipt to ethers format', () => { + const viemReceipt: TransactionReceipt = { + to: '0x1234', + from: '0x5678', + contractAddress: '0xabcd', + transactionIndex: 1, + gasUsed: BigInt(21000), + logsBloom: '0x', + blockHash: '0xblock', + transactionHash: '0xtx', + logs: [ + { + address: '0xcontract', + topics: ['0xtopic1'], + data: '0xdata', + blockNumber: BigInt(123), + transactionHash: '0xtx', + transactionIndex: 1, + blockHash: '0xblock', + logIndex: 0, + removed: false, + }, + ], + blockNumber: BigInt(123), + cumulativeGasUsed: BigInt(42000), + effectiveGasPrice: BigInt(2000000000), + status: 'success', + type: 'eip1559', + } + + const ethersReceipt = + viemTransactionReceiptToEthersTransactionReceipt(viemReceipt) + + expect(ethersReceipt.to).to.equal('0x1234') + expect(ethersReceipt.from).to.equal('0x5678') + expect(ethersReceipt.contractAddress).to.equal('0xabcd') + expect(ethersReceipt.transactionIndex).to.equal(1) + expect(ethersReceipt.gasUsed.eq(BigNumber.from(21000))).to.equal(true) + expect(ethersReceipt.blockNumber).to.equal(123) + expect(ethersReceipt.status).to.equal(1) + expect(ethersReceipt.logs[0].address).to.equal('0xcontract') + expect(ethersReceipt.byzantium).to.equal(true) + }) + + it('handles failed transaction status', () => { + const viemReceipt: TransactionReceipt = { + to: '0x1234', + from: '0x5678', + contractAddress: '0xabcd', + transactionIndex: 1, + gasUsed: BigInt(21000), + logsBloom: '0x', + blockHash: '0xblock', + transactionHash: '0xtx', + logs: [], + blockNumber: BigInt(123), + cumulativeGasUsed: BigInt(42000), + effectiveGasPrice: BigInt(2000000000), + status: 'reverted', + type: 'eip1559' as const, + } + + const ethersReceipt = + viemTransactionReceiptToEthersTransactionReceipt(viemReceipt) + expect(ethersReceipt.status).to.equal(0) + }) + }) +}) From 98270c0d6fdf8e33dcfe98c1a23877e30842b0f9 Mon Sep 17 00:00:00 2001 From: Doug Lance <4741454+douglance@users.noreply.github.com> Date: Fri, 20 Dec 2024 09:37:37 -0500 Subject: [PATCH 6/9] adds prettier and eslint to new package --- package.json | 4 ++-- packages/viem-compatibility/.eslintignore | 5 +++++ packages/viem-compatibility/.eslintrc | 8 ++++++++ packages/viem-compatibility/.prettierignore | 5 +++++ packages/viem-compatibility/.prettierrc.js | 5 +++++ packages/viem-compatibility/package.json | 4 +++- 6 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 packages/viem-compatibility/.eslintignore create mode 100644 packages/viem-compatibility/.eslintrc create mode 100644 packages/viem-compatibility/.prettierignore create mode 100644 packages/viem-compatibility/.prettierrc.js diff --git a/package.json b/package.json index 73f49f6e7..42088f23b 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,8 @@ "scripts": { "audit:ci": "audit-ci --config ./audit-ci.jsonc", "build": "yarn workspace @arbitrum/sdk build", - "lint": "yarn workspace @arbitrum/sdk lint", - "format": "yarn workspace @arbitrum/sdk format", + "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", "gen:abi": "yarn workspace @arbitrum/sdk gen:abi", diff --git a/packages/viem-compatibility/.eslintignore b/packages/viem-compatibility/.eslintignore new file mode 100644 index 000000000..99ead7177 --- /dev/null +++ b/packages/viem-compatibility/.eslintignore @@ -0,0 +1,5 @@ +dist/** +node_modules/** +coverage/** +src/lib/abi +docs/** diff --git a/packages/viem-compatibility/.eslintrc b/packages/viem-compatibility/.eslintrc new file mode 100644 index 000000000..bde91889b --- /dev/null +++ b/packages/viem-compatibility/.eslintrc @@ -0,0 +1,8 @@ +{ + "root": false, + "extends": ["../../.eslintrc.js"], + "parserOptions": { + "files": ["src/**/*.ts", "src/**/*.js"] + }, + "ignorePatterns": ["dist/**/*", "node_modules/**/*"] +} \ No newline at end of file diff --git a/packages/viem-compatibility/.prettierignore b/packages/viem-compatibility/.prettierignore new file mode 100644 index 000000000..ee20e44d9 --- /dev/null +++ b/packages/viem-compatibility/.prettierignore @@ -0,0 +1,5 @@ +build/** +cache/** +dist/** +src/lib/abi/** +.nyc_output diff --git a/packages/viem-compatibility/.prettierrc.js b/packages/viem-compatibility/.prettierrc.js new file mode 100644 index 000000000..008736fae --- /dev/null +++ b/packages/viem-compatibility/.prettierrc.js @@ -0,0 +1,5 @@ +const baseConfig = require('../../.prettierrc.js') + +module.exports = { + ...baseConfig, +} diff --git a/packages/viem-compatibility/package.json b/packages/viem-compatibility/package.json index ec3d8ec24..2a3a5a514 100644 --- a/packages/viem-compatibility/package.json +++ b/packages/viem-compatibility/package.json @@ -24,7 +24,9 @@ ], "scripts": { "build": "rm -rf dist && tsc -p tsconfig.json", - "test:unit": "mocha -r ts-node/register 'tests/**/*.test.ts'" + "test:unit": "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" }, "dependencies": { "viem": "^2.21.55" From 59ce5234b0f6126c54a24206d52475be22fd5206 Mon Sep 17 00:00:00 2001 From: Doug Lance <4741454+douglance@users.noreply.github.com> Date: Fri, 20 Dec 2024 09:52:55 -0500 Subject: [PATCH 7/9] remove dev deps --- packages/viem-compatibility/package.json | 7 ------- 1 file changed, 7 deletions(-) diff --git a/packages/viem-compatibility/package.json b/packages/viem-compatibility/package.json index 2a3a5a514..c4b54aea7 100644 --- a/packages/viem-compatibility/package.json +++ b/packages/viem-compatibility/package.json @@ -30,12 +30,5 @@ }, "dependencies": { "viem": "^2.21.55" - }, - "devDependencies": { - "@types/chai": "^4.2.11", - "@types/mocha": "^9.0.0", - "chai": "^4.2.0", - "mocha": "^9.2.1", - "ts-node": "^10.2.1" } } From 3bad69b8193592fca9fafebd1d11c41726695f7c Mon Sep 17 00:00:00 2001 From: Doug Lance <4741454+douglance@users.noreply.github.com> Date: Mon, 23 Dec 2024 13:21:02 -0500 Subject: [PATCH 8/9] pr feedback --- package.json | 2 + packages/viem-compatibility/package.json | 5 +- .../viem-compatibility/src/compatibility.ts | 40 ++------- .../tests/compatibility.test.ts | 66 +++++++------- yarn.lock | 87 ++++++++++--------- 5 files changed, 97 insertions(+), 103 deletions(-) diff --git a/package.json b/package.json index 42088f23b..ef6e5ba67 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "eslint-config-prettier": "^8.3.0", "eslint-plugin-mocha": "^9.0.0", "eslint-plugin-prettier": "^4.0.0", + "ethers": "^5.0.0", "hardhat": "^2.18.3", "mocha": "^9.2.1", "nyc": "^15.1.0", @@ -48,6 +49,7 @@ "tslint": "^6.1.3", "typechain": "7.0.0", "typescript": "^5.7.2", + "viem": "^2.0.0", "yargs": "^17.3.1" }, "resolutions": { diff --git a/packages/viem-compatibility/package.json b/packages/viem-compatibility/package.json index c4b54aea7..958bfb9a4 100644 --- a/packages/viem-compatibility/package.json +++ b/packages/viem-compatibility/package.json @@ -28,7 +28,8 @@ "lint": "eslint .", "format": "prettier './**/*.{js,json,md,ts,yml}' '!./src/lib/abi' --write && yarn run lint --fix" }, - "dependencies": { - "viem": "^2.21.55" + "peerDependencies": { + "ethers": "^5.0.0", + "viem": "^2.0.0" } } diff --git a/packages/viem-compatibility/src/compatibility.ts b/packages/viem-compatibility/src/compatibility.ts index 9acee96d4..6c184a418 100644 --- a/packages/viem-compatibility/src/compatibility.ts +++ b/packages/viem-compatibility/src/compatibility.ts @@ -2,21 +2,19 @@ import { Log as EthersLog, TransactionReceipt as EthersTransactionReceipt, } from '@ethersproject/abstract-provider' -import { StaticJsonRpcProvider } from '@ethersproject/providers' -import { BigNumber } from 'ethers' +import { BigNumber, providers } from 'ethers' import { - Chain, - Client, PublicClient, - Transport, Log as ViemLog, TransactionReceipt as ViemTransactionReceipt, } from 'viem' +interface HttpTransportConfig { + url: string +} + // based on https://wagmi.sh/react/ethers-adapters#reference-implementation -export function publicClientToProvider( - publicClient: PublicClient -) { +export function publicClientToProvider(publicClient: PublicClient) { const { chain } = publicClient if (typeof chain === 'undefined') { @@ -29,30 +27,10 @@ export function publicClientToProvider( 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' - ) -} + const transport = publicClient.transport as unknown as HttpTransportConfig + const url = transport.url ?? chain.rpcUrls.default.http[0] -export const transformPublicClientToProvider = ( - provider: PublicClient | Client -): StaticJsonRpcProvider => { - if (isPublicClient(provider)) { - return publicClientToProvider(provider) - } - throw new Error('Invalid provider') + return new providers.StaticJsonRpcProvider(url, network) } function viemLogToEthersLog(log: ViemLog): EthersLog { diff --git a/packages/viem-compatibility/tests/compatibility.test.ts b/packages/viem-compatibility/tests/compatibility.test.ts index 0d3f5b258..4a239ba0f 100644 --- a/packages/viem-compatibility/tests/compatibility.test.ts +++ b/packages/viem-compatibility/tests/compatibility.test.ts @@ -1,5 +1,5 @@ import { expect } from 'chai' -import { StaticJsonRpcProvider } from '@ethersproject/providers' +import { BigNumber, providers } from 'ethers' import { createPublicClient, defineChain, @@ -7,11 +7,9 @@ import { PublicClient, TransactionReceipt, } from 'viem' -import { mainnet } from 'viem/chains' -import { BigNumber } from 'ethers' +import { arbitrumSepolia, mainnet } from 'viem/chains' import { publicClientToProvider, - transformPublicClientToProvider, viemTransactionReceiptToEthersTransactionReceipt, } from '../src/compatibility' @@ -37,40 +35,50 @@ describe('viem compatibility', () => { }) as unknown as PublicClient const provider = publicClientToProvider(publicClient) - expect(provider).to.be.instanceOf(StaticJsonRpcProvider) + expect(provider).to.be.instanceOf(providers.StaticJsonRpcProvider) expect(provider.network.chainId).to.equal(testChain.id) expect(provider.network.name).to.equal(testChain.name) expect(provider.connection.url).to.equal('https://example.com') }) - it('throws error when chain is undefined', () => { - const publicClient = { - chain: undefined, - } as unknown as PublicClient + it('successfully converts PublicClient to Provider', () => { + const publicClient = createPublicClient({ + chain: arbitrumSepolia, + transport: http(), + }) as unknown as PublicClient - expect(() => publicClientToProvider(publicClient)).to.throw( - '[publicClientToProvider] "chain" is undefined' + const provider = publicClientToProvider(publicClient) + + expect(provider.network.chainId).to.equal(publicClient.chain!.id) + expect(provider.network.name).to.equal(publicClient.chain!.name) + expect(provider.connection.url).to.equal( + 'https://sepolia-rollup.arbitrum.io/rpc' ) }) - }) - describe('transformPublicClientToProvider', () => { - it('transforms valid public client to provider', () => { - const transport = http('https://example.com') + it('successfully converts PublicClient to Provider (custom Transport)', () => { const publicClient = createPublicClient({ - chain: testChain, - transport, + chain: arbitrumSepolia, + transport: http('https://arbitrum-sepolia.gateway.tenderly.co'), }) as unknown as PublicClient - const provider = transformPublicClientToProvider(publicClient) - expect(provider).to.be.instanceOf(StaticJsonRpcProvider) + const provider = publicClientToProvider(publicClient) + + expect(provider.network.chainId).to.equal(publicClient.chain!.id) + expect(provider.network.name).to.equal(publicClient.chain!.name) + expect(provider.connection.url).to.equal( + 'https://arbitrum-sepolia.gateway.tenderly.co' + ) }) - it('throws error for invalid provider', () => { - const invalidClient = {} as PublicClient + it('throws error when chain is undefined', () => { + const publicClient = { + chain: undefined, + transport: http(), + } as unknown as PublicClient - expect(() => transformPublicClientToProvider(invalidClient)).to.throw( - 'Invalid provider' + expect(() => publicClientToProvider(publicClient)).to.throw( + '[publicClientToProvider] "chain" is undefined' ) }) }) @@ -89,15 +97,15 @@ describe('viem compatibility', () => { logs: [ { address: '0xcontract', - topics: ['0xtopic1'], - data: '0xdata', - blockNumber: BigInt(123), - transactionHash: '0xtx', - transactionIndex: 1, blockHash: '0xblock', + blockNumber: BigInt(123), + data: '0xdata', logIndex: 0, removed: false, - }, + transactionHash: '0xtx', + transactionIndex: 1, + topics: [], + } as any, ], blockNumber: BigInt(123), cumulativeGasUsed: BigInt(42000), diff --git a/yarn.lock b/yarn.lock index 206319276..8258d4c9e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1810,11 +1810,16 @@ JSONStream@^1.3.5: jsonparse "^1.2.0" through ">=2.2.7 <3" -abitype@1.0.7, abitype@^1.0.6: +abitype@1.0.7: version "1.0.7" resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.7.tgz#876a0005d211e1c9132825d45bcee7b46416b284" integrity sha512-ZfYYSktDQUwc2eduYu8C4wOs+RDPmnRYMh7zNfzeMtGGgb0U+6tLGjixUic6mXf5xKKCcgT5Qp6cv39tOARVFw== +abitype@^1.0.6: + version "1.0.8" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.8.tgz#3554f28b2e9d6e9f35eb59878193eabd1b9f46ba" + integrity sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg== + abstract-level@^1.0.0, abstract-level@^1.0.2, abstract-level@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/abstract-level/-/abstract-level-1.0.3.tgz#78a67d3d84da55ee15201486ab44c09560070741" @@ -3168,6 +3173,42 @@ ethereumjs-util@^7.0.3: ethereum-cryptography "^0.1.3" rlp "^2.2.4" +ethers@^5.0.0, ethers@^5.6.9, ethers@^5.7.1: + version "5.7.2" + resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" + integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== + dependencies: + "@ethersproject/abi" "5.7.0" + "@ethersproject/abstract-provider" "5.7.0" + "@ethersproject/abstract-signer" "5.7.0" + "@ethersproject/address" "5.7.0" + "@ethersproject/base64" "5.7.0" + "@ethersproject/basex" "5.7.0" + "@ethersproject/bignumber" "5.7.0" + "@ethersproject/bytes" "5.7.0" + "@ethersproject/constants" "5.7.0" + "@ethersproject/contracts" "5.7.0" + "@ethersproject/hash" "5.7.0" + "@ethersproject/hdnode" "5.7.0" + "@ethersproject/json-wallets" "5.7.0" + "@ethersproject/keccak256" "5.7.0" + "@ethersproject/logger" "5.7.0" + "@ethersproject/networks" "5.7.1" + "@ethersproject/pbkdf2" "5.7.0" + "@ethersproject/properties" "5.7.0" + "@ethersproject/providers" "5.7.2" + "@ethersproject/random" "5.7.0" + "@ethersproject/rlp" "5.7.0" + "@ethersproject/sha2" "5.7.0" + "@ethersproject/signing-key" "5.7.0" + "@ethersproject/solidity" "5.7.0" + "@ethersproject/strings" "5.7.0" + "@ethersproject/transactions" "5.7.0" + "@ethersproject/units" "5.7.0" + "@ethersproject/wallet" "5.7.0" + "@ethersproject/web" "5.7.1" + "@ethersproject/wordlists" "5.7.0" + ethers@^5.1.0: version "5.6.4" resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.6.4.tgz#23629e9a7d4bc5802dfb53d4da420d738744b53c" @@ -3204,42 +3245,6 @@ ethers@^5.1.0: "@ethersproject/web" "5.6.0" "@ethersproject/wordlists" "5.6.0" -ethers@^5.6.9, ethers@^5.7.1: - version "5.7.2" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-5.7.2.tgz#3a7deeabbb8c030d4126b24f84e525466145872e" - integrity sha512-wswUsmWo1aOK8rR7DIKiWSw9DbLWe6x98Jrn8wcTflTVvaXhAMaB5zGAXy0GYQEQp9iO1iSHWVyARQm11zUtyg== - dependencies: - "@ethersproject/abi" "5.7.0" - "@ethersproject/abstract-provider" "5.7.0" - "@ethersproject/abstract-signer" "5.7.0" - "@ethersproject/address" "5.7.0" - "@ethersproject/base64" "5.7.0" - "@ethersproject/basex" "5.7.0" - "@ethersproject/bignumber" "5.7.0" - "@ethersproject/bytes" "5.7.0" - "@ethersproject/constants" "5.7.0" - "@ethersproject/contracts" "5.7.0" - "@ethersproject/hash" "5.7.0" - "@ethersproject/hdnode" "5.7.0" - "@ethersproject/json-wallets" "5.7.0" - "@ethersproject/keccak256" "5.7.0" - "@ethersproject/logger" "5.7.0" - "@ethersproject/networks" "5.7.1" - "@ethersproject/pbkdf2" "5.7.0" - "@ethersproject/properties" "5.7.0" - "@ethersproject/providers" "5.7.2" - "@ethersproject/random" "5.7.0" - "@ethersproject/rlp" "5.7.0" - "@ethersproject/sha2" "5.7.0" - "@ethersproject/signing-key" "5.7.0" - "@ethersproject/solidity" "5.7.0" - "@ethersproject/strings" "5.7.0" - "@ethersproject/transactions" "5.7.0" - "@ethersproject/units" "5.7.0" - "@ethersproject/wallet" "5.7.0" - "@ethersproject/web" "5.7.1" - "@ethersproject/wordlists" "5.7.0" - ethjs-util@0.1.6, ethjs-util@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.6.tgz#f308b62f185f9fe6237132fb2a9818866a5cd536" @@ -6078,10 +6083,10 @@ v8-compile-cache@^2.0.3: resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== -viem@^2.21.55: - version "2.21.55" - resolved "https://registry.yarnpkg.com/viem/-/viem-2.21.55.tgz#a57ad31fcf2a0f6c011b1909f02c94421ec4f781" - integrity sha512-PgXew7C11cAuEtOSgRyQx2kJxEOPUwIwZA9dMglRByqJuFVA7wSGZZOOo/93iylAA8E15bEdqy9xulU3oKZ70Q== +viem@^2.0.0: + version "2.21.57" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.21.57.tgz#bedbb444bb42e07ccc2264a9a0441903a113aab8" + integrity sha512-Mw4f4Dw0+Y/wSHdynVmP4uh+Cw15HEoj8BOKvKH5nGA6oFZYRxSy9Ruu7ZG8jexeAVCZ57aIuXb0gNg6Vb1x0g== dependencies: "@noble/curves" "1.7.0" "@noble/hashes" "1.6.1" From a86f179f22055d157dc1b7f7c7b1c4c0db21b40f Mon Sep 17 00:00:00 2001 From: spsjvc Date: Thu, 26 Dec 2024 10:58:50 +0100 Subject: [PATCH 9/9] remove type casts --- .../tests/compatibility.test.ts | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/packages/viem-compatibility/tests/compatibility.test.ts b/packages/viem-compatibility/tests/compatibility.test.ts index 4a239ba0f..0386e9326 100644 --- a/packages/viem-compatibility/tests/compatibility.test.ts +++ b/packages/viem-compatibility/tests/compatibility.test.ts @@ -1,13 +1,8 @@ import { expect } from 'chai' import { BigNumber, providers } from 'ethers' -import { - createPublicClient, - defineChain, - http, - PublicClient, - TransactionReceipt, -} from 'viem' +import { createPublicClient, defineChain, http, TransactionReceipt } from 'viem' import { arbitrumSepolia, mainnet } from 'viem/chains' + import { publicClientToProvider, viemTransactionReceiptToEthersTransactionReceipt, @@ -32,7 +27,7 @@ describe('viem compatibility', () => { const publicClient = createPublicClient({ chain: testChain, transport, - }) as unknown as PublicClient + }) const provider = publicClientToProvider(publicClient) expect(provider).to.be.instanceOf(providers.StaticJsonRpcProvider) @@ -45,7 +40,7 @@ describe('viem compatibility', () => { const publicClient = createPublicClient({ chain: arbitrumSepolia, transport: http(), - }) as unknown as PublicClient + }) const provider = publicClientToProvider(publicClient) @@ -60,7 +55,7 @@ describe('viem compatibility', () => { const publicClient = createPublicClient({ chain: arbitrumSepolia, transport: http('https://arbitrum-sepolia.gateway.tenderly.co'), - }) as unknown as PublicClient + }) const provider = publicClientToProvider(publicClient) @@ -72,10 +67,11 @@ describe('viem compatibility', () => { }) it('throws error when chain is undefined', () => { - const publicClient = { + const transport = http('https://example.com') + const publicClient = createPublicClient({ chain: undefined, - transport: http(), - } as unknown as PublicClient + transport, + }) expect(() => publicClientToProvider(publicClient)).to.throw( '[publicClientToProvider] "chain" is undefined' @@ -105,7 +101,7 @@ describe('viem compatibility', () => { transactionHash: '0xtx', transactionIndex: 1, topics: [], - } as any, + }, ], blockNumber: BigInt(123), cumulativeGasUsed: BigInt(42000),