Skip to content

Commit

Permalink
feat: add viem compatibility package (#558)
Browse files Browse the repository at this point in the history
Co-authored-by: spsjvc <git@spsjvc.com>
  • Loading branch information
douglance and spsjvc authored Dec 26, 2024
1 parent 52e3668 commit 87a64a8
Show file tree
Hide file tree
Showing 11 changed files with 439 additions and 39 deletions.
8 changes: 5 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
"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",
"test:unit": "yarn workspace @arbitrum/sdk test:unit",
"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",
"gen:network": "yarn workspace @arbitrum/sdk gen:network"
Expand Down Expand Up @@ -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",
Expand All @@ -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": {
Expand Down
5 changes: 5 additions & 0 deletions packages/viem-compatibility/.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/viem-compatibility/.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/viem-compatibility/.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/viem-compatibility/.prettierrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const baseConfig = require('../../.prettierrc.js')

module.exports = {
...baseConfig,
}
35 changes: 35 additions & 0 deletions packages/viem-compatibility/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"name": "@arbitrum/viem-compatibility",
"version": "0.0.1",
"description": "Typescript library for ethers compatibility with 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: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"
},
"peerDependencies": {
"ethers": "^5.0.0",
"viem": "^2.0.0"
}
}
73 changes: 73 additions & 0 deletions packages/viem-compatibility/src/compatibility.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {
Log as EthersLog,
TransactionReceipt as EthersTransactionReceipt,
} from '@ethersproject/abstract-provider'
import { BigNumber, providers } from 'ethers'
import {
PublicClient,
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) {
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,
}

const transport = publicClient.transport as unknown as HttpTransportConfig
const url = transport.url ?? chain.rpcUrls.default.http[0]

return new providers.StaticJsonRpcProvider(url, network)
}

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,
}
}
1 change: 1 addition & 0 deletions packages/viem-compatibility/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './compatibility'
150 changes: 150 additions & 0 deletions packages/viem-compatibility/tests/compatibility.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
import { expect } from 'chai'
import { BigNumber, providers } from 'ethers'
import { createPublicClient, defineChain, http, TransactionReceipt } from 'viem'
import { arbitrumSepolia, mainnet } from 'viem/chains'

import {
publicClientToProvider,
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,
})

const provider = publicClientToProvider(publicClient)
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('successfully converts PublicClient to Provider', () => {
const publicClient = createPublicClient({
chain: arbitrumSepolia,
transport: http(),
})

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

it('successfully converts PublicClient to Provider (custom Transport)', () => {
const publicClient = createPublicClient({
chain: arbitrumSepolia,
transport: http('https://arbitrum-sepolia.gateway.tenderly.co'),
})

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 when chain is undefined', () => {
const transport = http('https://example.com')
const publicClient = createPublicClient({
chain: undefined,
transport,
})

expect(() => publicClientToProvider(publicClient)).to.throw(
'[publicClientToProvider] "chain" is undefined'
)
})
})

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',
blockHash: '0xblock',
blockNumber: BigInt(123),
data: '0xdata',
logIndex: 0,
removed: false,
transactionHash: '0xtx',
transactionIndex: 1,
topics: [],
},
],
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)
})
})
})
11 changes: 11 additions & 0 deletions packages/viem-compatibility/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"target": "ES2020",
"rootDir": "./src",
"outDir": "./dist",
"skipLibCheck": true
},
"include": ["src/**/*.ts", "src/**/*.d.ts"],
"exclude": ["node_modules", "dist"]
}
Loading

0 comments on commit 87a64a8

Please sign in to comment.