Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: adds viem compatibility package #558

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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'
154 changes: 154 additions & 0 deletions packages/viem-compatibility/tests/compatibility.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { expect } from 'chai'
import { BigNumber, providers } from 'ethers'
import {
createPublicClient,
defineChain,
http,
PublicClient,
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,
}) as unknown as PublicClient

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(),
}) as unknown as PublicClient

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'),
}) as unknown as PublicClient

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 publicClient = {
chain: undefined,
transport: http(),
} as unknown as PublicClient

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: [],
} as any,
],
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
Loading