Skip to content

Commit

Permalink
chaingen
Browse files Browse the repository at this point in the history
  • Loading branch information
hamdiallam committed Jan 14, 2025
1 parent ebc887b commit 5640c6d
Show file tree
Hide file tree
Showing 8 changed files with 3,540 additions and 446 deletions.
20 changes: 12 additions & 8 deletions packages/viem/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,17 @@
"src/*"
],
"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",
Expand All @@ -33,5 +34,8 @@
},
"peerDependencies": {
"viem": "^2.17.9"
},
"dependencies": {
"@iarna/toml": "^2.2.5"
}
}
}
File renamed without changes.
127 changes: 127 additions & 0 deletions packages/viem/scripts/chaingen.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { Eta } from 'eta'
import fs from 'fs'
import toml from '@iarna/toml'

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 SUPERCHAIN_REGISTRY_PATH = '../../lib/superchain-registry'
const NETWORKS = ['mainnet', 'sepolia']

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

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 = `${SUPERCHAIN_REGISTRY_PATH}/superchain/configs/${network}`
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>
let 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
}

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)
}
})()
52 changes: 52 additions & 0 deletions packages/viem/scripts/templates/chains.eta
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// 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 %>
* @type {Chain}
*/
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 %>,
<% }) %>

]
Loading

0 comments on commit 5640c6d

Please sign in to comment.