Skip to content

Commit

Permalink
viem/chains: chain exports for the superchain registry (#615)
Browse files Browse the repository at this point in the history
* viem superchain registry bindings

* chaingen

* lint fix

* more lint

* fix template according to lint rules. use path.join

* lint

* changeset

* index.ts for chains. remove from global export

* export chains

* add some comments

* undo optimism submodule
  • Loading branch information
hamdiallam authored Jan 16, 2025
1 parent 15ca959 commit d07cb41
Show file tree
Hide file tree
Showing 14 changed files with 3,506 additions and 448 deletions.
5 changes: 5 additions & 0 deletions .changeset/fast-cars-compete.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@eth-optimism/viem": patch
---

added viem chain bindings based on the superchain registry
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "lib/superchain-registry"]
path = lib/superchain-registry
url = https://github.com/ethereum-optimism/superchain-registry
2 changes: 1 addition & 1 deletion apps/bridge-app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { getDefaultConfig, RainbowKitProvider } from '@rainbow-me/rainbowkit'

import type { Chain, Transport } from 'viem'
import { configureOpChains } from '@eth-optimism/op-app'
import { supersimL1, supersimL2A, supersimL2B } from '@eth-optimism/viem/chains'

import { Bridge } from '@/routes'
import { Layout } from '@/components/Layout'
Expand All @@ -20,7 +21,6 @@ import { NETWORK_TYPE } from '@/constants/networkType'
import { Home } from '@/routes/Home'
import { Playground } from '@/routes/Playground'
import { Toaster } from '@eth-optimism/ui-components'
import { supersimL1, supersimL2A, supersimL2B } from '@eth-optimism/viem'

const classNames = {
app: 'app w-full min-h-screen flex flex-col',
Expand Down
1 change: 1 addition & 0 deletions lib/superchain-registry
Submodule superchain-registry added at c08331
22 changes: 14 additions & 8 deletions packages/viem/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,27 @@
"dist/*",
"src/*"
],
"exports": {
".": "./dist/index.js",
"./chains": "./dist/chains/index.js"
},
"scripts": {
"docs": "typedoc",
"test": "vitest",
"clean": "rm -rf build types tsconfig.tsbuildinfo",
"typecheck": "tsc --noEmit --emitDeclarationOnly false",
"typecheck:ci": "tsc --noEmit --emitDeclarationOnly false",
"build": "tsc && resolve-tspaths",
"generate": "pnpm dlx tsx ./scripts/generate.ts",
"clean": "rm -rf build types tsconfig.tsbuildinfo",
"docs": "typedoc",
"gen:abis": "pnpm dlx tsx ./scripts/abigen",
"gen:chains": "pnpm dlx tsx ./scripts/chaingen",
"lint": "eslint \"**/*.{ts,tsx}\" && pnpm prettier --check \"**/*.{ts,tsx}\"",
"lint:ci": "eslint \"**/*.{ts,tsx}\" --quiet && pnpm prettier --check \"**/*.{ts,tsx}\"",
"lint:fix": "eslint \"**/*.{ts,tsx}\" --fix --quiet && pnpm prettier \"**/*.{ts,tsx}\" --write --loglevel=warn"
"lint:fix": "eslint \"**/*.{ts,tsx}\" --fix --quiet && pnpm prettier \"**/*.{ts,tsx}\" --write --loglevel=warn",
"test": "vitest",
"typecheck": "tsc --noEmit --emitDeclarationOnly false",
"typecheck:ci": "tsc --noEmit --emitDeclarationOnly false"
},
"devDependencies": {
"@types/node": "^22.5.4",
"@viem/anvil": "^0.0.7",
"@iarna/toml": "^2.2.5",
"eta": "^3.1.1",
"resolve-tspaths": "^0.8.18",
"typedoc": "^0.26.7",
Expand All @@ -34,4 +40,4 @@
"peerDependencies": {
"viem": "^2.17.9"
}
}
}
File renamed without changes.
165 changes: 165 additions & 0 deletions packages/viem/scripts/chaingen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
import toml from '@iarna/toml'
import { Eta } from 'eta'
import fs from 'fs'
import path from 'path'
import type { Address, PublicClient } from 'viem'
import { createPublicClient, erc20Abi, http } from 'viem'
import { mainnet, sepolia } from 'viem/chains'

// Hardcoded. Can take a more elaborate approach if needed.
const NETWORKS = ['mainnet', 'sepolia']
const SUPERCHAIN_REGISTRY_PATH = path.join(
'..',
'..',
'lib',
'superchain-registry',
)

type ChainDefinition = {
chainName: string
exportName: string
chainId: number
sourceChainId: number
rpc: string
explorer: string
nativeCurrency: NativeCurrency
l1Addresses: Record<string, Address>
}

type NativeCurrency = { name: string; symbol: string; decimals: number }

/** Utility functions **/

function camelCase(str: string): string {
const parts = str.replace(/-/g, ' ').replace(/_/g, ' ').split(' ')
return (
parts[0].toLowerCase() +
parts
.slice(1)
.map((part) => part[0].toUpperCase() + part.substring(1))
.join('')
)
}

async function nativeCurrency(
client: PublicClient,
address: Address | undefined,
): Promise<NativeCurrency> {
if (!address) {
return { name: 'Ether', symbol: 'ETH', decimals: 18 }
}

const [name, symbol, decimals] = await Promise.all([
client.readContract({ address, abi: erc20Abi, functionName: 'name' }),
client.readContract({ address, abi: erc20Abi, functionName: 'symbol' }),
client.readContract({ address, abi: erc20Abi, functionName: 'decimals' }),
])

return { name, symbol, decimals }
}

/** Chain Generation **/

async function main() {
console.log('Running chain generation...')
const eta = new Eta({
views: './scripts/templates',
debug: true,
autoTrim: [false, false],
})

const mainnetHttp = process.env.MAINNET_RPC_URL
? [process.env.MAINNET_RPC_URL]
: mainnet.rpcUrls.default.http
const mainnetClient = createPublicClient({
chain: { ...mainnet, rpcUrls: { default: { http: mainnetHttp } } },
transport: http(),
})

const sepoliaHttp = process.env.SEPOLIA_RPC_URL
? [process.env.SEPOLIA_RPC_URL]
: sepolia.rpcUrls.default.http
const sepoliaClient = createPublicClient({
chain: { ...sepolia, rpcUrls: { default: { http: sepoliaHttp } } },
transport: http(),
})

for (const network of NETWORKS) {
console.log(`Generating ${network}`)
const client = network === 'mainnet' ? mainnetClient : sepoliaClient

const configPath = path.join(
SUPERCHAIN_REGISTRY_PATH,
'superchain',
'configs',
network,
)

// `superchain.toml` contains information on activation times as well as the
// applicable l1 contracts for the chain. We exclude this file as a chain entry
const entries = fs
.readdirSync(configPath)
.filter((entry) => !entry.includes('superchain'))

const chainDefs = await Promise.all(
entries.map(async (entry) => {
const chainConfig = toml.parse(
fs.readFileSync(`${configPath}/${entry}`, 'utf8'),
)

const addresses = chainConfig.addresses as Record<string, Address>
const l1Addresses = {
// Referenced as `portal` in viem
portal: addresses.OptimismPortalProxy,

// Standard Deployments
l1StandardBridge: addresses.L1StandardBridgeProxy,
l1Erc721Bridge: addresses.L1ERC721BridgeProxy,
l1CrossDomainMessenger: addresses.L1CrossDomainMessengerProxy,
systemConfig: addresses.SystemConfigProxy,
}

if (addresses.DisputeGameFactoryProxy) {
l1Addresses['disputeGameFactory'] = addresses.DisputeGameFactoryProxy
}
if (addresses.L2OutputOracleProxy) {
l1Addresses['l2OutputOracle'] = addresses.L2OutputOracleProxy
}

// This is an edge case handler for the `arena-z-testnet` chain name.
const normalizedName = entry
.replace('.toml', '')
.replace('-testnet', '')

return {
chainName: chainConfig.name as string,
exportName: camelCase(`${normalizedName}-${network}`),
chainId: chainConfig.chain_id as number,
sourceChainId: network === 'mainnet' ? 1 : 11155111,
rpc: chainConfig.public_rpc as string,
explorer: chainConfig.explorer as string,
nativeCurrency: await nativeCurrency(
client,
chainConfig.gas_paying_token as Address,
),
l1Addresses,
} satisfies ChainDefinition
}),
)

const fileContents = eta.render('chains', { chainDefs, network })

console.log(`Writing chains to file...`)
fs.writeFileSync(`src/chains/${network}.ts`, fileContents)
}
}

;(async () => {
try {
await main()
process.exit(0)
} catch (e) {
console.error(e)
process.exit(-1)
}
})()
48 changes: 48 additions & 0 deletions packages/viem/scripts/templates/chains.eta
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// DO NOT MODIFY THIS FILE IS AUTOGENERATED
import type { Chain } from 'viem'
import { defineChain } from 'viem'
import { chainConfig } from 'viem/op-stack'

<%- it.chainDefs.forEach(function(chainDef){ %>
/**
* Chain Definition for <%= chainDef.chainName %>
*/
export const <%= chainDef.exportName %>: Chain = defineChain({
...chainConfig,
name: '<%= chainDef.chainName %>',
id: <%= chainDef.chainId %>,
sourceId: <%= chainDef.sourceChainId %>,
nativeCurrency: {
name: '<%= chainDef.nativeCurrency.name %>',
symbol: '<%= chainDef.nativeCurrency.symbol %>',
decimals: <%= chainDef.nativeCurrency.decimals %>,
},
rpcUrls: {
default: {
http: ['<%= chainDef.rpc %>'],
},
},
blockExplorers: {
default: {
name: '<%= chainDef.chainName %> Explorer',
url: '<%= chainDef.explorer %>',
},
},
contracts: {
...chainConfig.contracts,
<%_ Object.entries(chainDef.l1Addresses).forEach(function([name, address]){ %>
<%= name %>: {
<%= chainDef.sourceChainId %>: {
address: '<%= address %>',
},
},
<%_ }) %>
},
})
<% }) -%>

export const <%= it.network %>Chains = [
<%_ it.chainDefs.forEach(function(chainDef){ %>
<%= chainDef.exportName %>,
<%_ }) %>
]
3 changes: 3 additions & 0 deletions packages/viem/src/chains/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './mainnet.js'
export * from './sepolia.js'
export * from './supersim.js'
Loading

0 comments on commit d07cb41

Please sign in to comment.