github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-ts/wagmi.config.ts (about) 1 import { ContractConfig, defineConfig, Plugin } from '@wagmi/cli' 2 import { actions, react } from '@wagmi/cli/plugins' 3 import * as glob from 'glob' 4 import { readFileSync, writeFileSync } from 'fs' 5 import type { Abi, Address } from 'abitype' 6 import { isDeepStrictEqual } from 'util' 7 import { camelCase, constantCase } from 'change-case' 8 9 /** 10 * Predeployed contract addresses 11 * In future it would be nice to have a json file in contracts bedrock be generated as source of truth 12 * Keep this in sync with op-bindings/predeploys/addresses.go in meantime 13 */ 14 const predeployContracts = { 15 LegacyMessagePasser: { 16 address: '0x4200000000000000000000000000000000000000', 17 introduced: 'Legacy', 18 deprecated: true, 19 proxied: true, 20 }, 21 DeployerWhitelist: { 22 address: '0x4200000000000000000000000000000000000002', 23 introduced: 'Legacy', 24 deprecated: true, 25 proxied: true, 26 }, 27 LegacyERC20ETH: { 28 address: '0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000', 29 introduced: 'Legacy', 30 deprecated: true, 31 proxied: false, 32 }, 33 WETH9: { 34 address: '0x4200000000000000000000000000000000000006', 35 introduced: 'Legacy', 36 deprecated: false, 37 proxied: false, 38 }, 39 L2CrossDomainMessenger: { 40 address: '0x4200000000000000000000000000000000000007', 41 introduced: 'Legacy', 42 deprecated: false, 43 proxied: true, 44 }, 45 L2StandardBridge: { 46 address: '0x4200000000000000000000000000000000000010', 47 introduced: 'Legacy', 48 deprecated: false, 49 proxied: true, 50 }, 51 SequencerFeeVault: { 52 address: '0x4200000000000000000000000000000000000011', 53 introduced: 'Legacy', 54 deprecated: false, 55 proxied: true, 56 }, 57 OptimismMintableERC20Factory: { 58 address: '0x4200000000000000000000000000000000000012', 59 introduced: 'Legacy', 60 deprecated: false, 61 proxied: true, 62 }, 63 L1BlockNumber: { 64 address: '0x4200000000000000000000000000000000000013', 65 introduced: 'Legacy', 66 deprecated: true, 67 proxied: true, 68 }, 69 GasPriceOracle: { 70 address: '0x420000000000000000000000000000000000000F', 71 introduced: 'Legacy', 72 deprecated: false, 73 proxied: true, 74 }, 75 GovernanceToken: { 76 address: '0x4200000000000000000000000000000000000042', 77 introduced: 'Legacy', 78 deprecated: false, 79 proxied: false, 80 }, 81 L1Block: { 82 address: '0x4200000000000000000000000000000000000015', 83 introduced: 'Bedrock', 84 deprecated: false, 85 proxied: true, 86 }, 87 L2ToL1MessagePasser: { 88 address: '0x4200000000000000000000000000000000000016', 89 introduced: 'Bedrock', 90 deprecated: false, 91 proxied: true, 92 }, 93 L2ERC721Bridge: { 94 address: '0x4200000000000000000000000000000000000014', 95 introduced: 'Legacy', 96 deprecated: false, 97 proxied: true, 98 }, 99 OptimismMintableERC721Factory: { 100 address: '0x4200000000000000000000000000000000000017', 101 introduced: 'Bedrock', 102 deprecated: false, 103 proxied: true, 104 }, 105 ProxyAdmin: { 106 address: '0x4200000000000000000000000000000000000018', 107 introduced: 'Bedrock', 108 deprecated: false, 109 proxied: true, 110 }, 111 BaseFeeVault: { 112 address: '0x4200000000000000000000000000000000000019', 113 introduced: 'Bedrock', 114 deprecated: false, 115 proxied: true, 116 }, 117 L1FeeVault: { 118 address: '0x420000000000000000000000000000000000001a', 119 introduced: 'Bedrock', 120 deprecated: false, 121 proxied: true, 122 }, 123 } as const 124 125 type DeploymentJson = { 126 abi: Abi 127 address: `0x${string}` 128 } 129 const chains = { 130 1: 'mainnet', 131 10: 'optimism-mainnet', 132 5: 'goerli', 133 420: 'optimism-goerli', 134 } as const 135 136 if (!glob.sync('node_modules/*').length) { 137 throw new Error( 138 'No node_modules found. Please run `pnpm install` before running this script' 139 ) 140 } 141 142 const deployments = { 143 [1]: glob.sync( 144 `node_modules/@eth-optimism/contracts-bedrock/deployments/${chains[1]}/*.json` 145 ), 146 [10]: glob.sync( 147 `node_modules/@eth-optimism/contracts-bedrock/deployments/${chains[10]}/*.json` 148 ), 149 [5]: glob.sync( 150 `node_modules/@eth-optimism/contracts-bedrock/deployments/${chains[5]}/*.json` 151 ), 152 [420]: glob.sync( 153 `node_modules/@eth-optimism/contracts-bedrock/deployments/${chains[420]}/*.json` 154 ), 155 } 156 Object.entries(deployments).forEach(([chain, deploymentFiles]) => { 157 if (deploymentFiles.length === 0) { 158 throw new Error(`No bedrock deployments found for ${chains[chain]}`) 159 } 160 }) 161 162 const getWagmiContracts = ( 163 deploymentFiles: string[], 164 filterDuplicates = false 165 ) => 166 deploymentFiles.map((artifactPath) => { 167 const deployment = JSON.parse( 168 readFileSync(artifactPath, 'utf8') 169 ) as DeploymentJson 170 171 // There is a known bug in the wagmi/cli repo where some contracts have FOO_CASE and fooCase in same contract causing issues 172 // This is a common pattern at OP 173 // @see https://github.com/wagmi-dev/wagmi/issues/2724 174 const abi = filterDuplicates 175 ? deployment.abi.filter((item) => { 176 if (item.type !== 'function') { 177 return true 178 } 179 if (item.name !== constantCase(item.name)) { 180 return true 181 } 182 // if constante case make sure it is not a duplicate 183 // e.g. make sure fooBar doesn't exist with FOO_BAR 184 return !deployment.abi.some( 185 (otherItem) => 186 otherItem.type === 'function' && 187 otherItem.name !== item.name && 188 otherItem.name === camelCase(item.name) 189 ) 190 }) 191 : deployment.abi 192 const contractConfig = { 193 abi, 194 name: artifactPath.split('/').reverse()[0]?.replace('.json', ''), 195 address: deployment.address, 196 } satisfies ContractConfig 197 if (!contractConfig.name) { 198 throw new Error( 199 'Unable to identify the name of the contract at ' + artifactPath 200 ) 201 } 202 return contractConfig 203 }) 204 205 /** 206 * Returns the contracts for the wagmi cli config 207 */ 208 const getContractConfigs = (filterDuplicates = false) => { 209 const contracts = { 210 1: getWagmiContracts(deployments[1], filterDuplicates), 211 10: getWagmiContracts(deployments[10], filterDuplicates), 212 5: getWagmiContracts(deployments[5], filterDuplicates), 213 420: getWagmiContracts(deployments[420], filterDuplicates), 214 } 215 216 const allContracts = Object.values(contracts).flat() 217 218 const config: ContractConfig[] = [] 219 220 // this for loop is not terribly efficient but seems fast enough for the scale here 221 for (const contract of allContracts) { 222 // we will only process the implementation ABI but will use teh proxy addresses for deployments 223 const isProxy = contract.name.endsWith('Proxy') 224 // once we see the first deployment of a contract we will process all networks all at once 225 const alreadyProcessedContract = config.find( 226 (c) => c.name === contract.name 227 ) 228 if (isProxy || alreadyProcessedContract) { 229 continue 230 } 231 232 const implementations = { 233 // @warning Later code assumes mainnet is first!!! 234 [1]: contracts[1].find((c) => c.name === contract.name), 235 // @warning Later code assumes mainnet is first!!! 236 [10]: contracts[10].find((c) => c.name === contract.name), 237 [5]: contracts[5].find((c) => c.name === contract.name), 238 [420]: contracts[420].find((c) => c.name === contract.name), 239 } 240 const maybeProxyName = contract.name + 'Proxy' 241 const proxies = { 242 // @warning Later code assumes mainnet is first!!! 243 [1]: contracts[1].find((c) => c.name === maybeProxyName), 244 // @warning Later code assumes mainnet is first!!! 245 [10]: contracts[10].find((c) => c.name === maybeProxyName), 246 [5]: contracts[5].find((c) => c.name === maybeProxyName), 247 [420]: contracts[420].find((c) => c.name === maybeProxyName), 248 } 249 250 const predeploy = predeployContracts[ 251 contract.name as keyof typeof predeployContracts 252 ] as { address: Address } | undefined 253 254 // If the contract has different abis on different networks we don't want to group them as a single abi 255 const isContractUnique = !Object.values(implementations).some( 256 (implementation) => 257 implementation && !isDeepStrictEqual(implementation.abi, contract.abi) 258 ) 259 if (!isContractUnique) { 260 Object.entries(implementations) 261 .filter(([_, implementation]) => implementation) 262 .forEach(([chain, implementation], i) => { 263 if (implementation) { 264 // make the first one canonical. This will be mainnet or op mainnet if they exist 265 const name = 266 i === 0 ? contract.name : `${contract.name}_${chains[chain]}` 267 const nextConfig = { 268 abi: implementation.abi, 269 name, 270 address: { 271 [Number.parseInt(chain)]: 272 predeploy?.address ?? 273 proxies[chain]?.address ?? 274 implementation?.address, 275 }, // predeploy?.address ?? proxies[chain]?.address ?? implementation?.address 276 } satisfies ContractConfig 277 config.push(nextConfig) 278 } 279 }) 280 continue 281 } 282 283 const wagmiConfig = { 284 abi: contract.abi, 285 name: contract.name, 286 address: {}, 287 } satisfies ContractConfig 288 289 Object.entries(implementations).forEach(([chain, proxy]) => { 290 if (proxy) { 291 wagmiConfig.address[chain] = 292 predeploy?.address ?? proxy.address ?? contract.address 293 } 294 }) 295 // if proxies exist overwrite the address with the proxy address 296 Object.entries(proxies).forEach(([chain, proxy]) => { 297 if (proxy) { 298 wagmiConfig.address[chain] = predeploy?.address ?? proxy.address 299 } 300 }) 301 302 config.push(wagmiConfig) 303 } 304 305 return config 306 } 307 308 /** 309 * This plugin will create a addresses mapping from contract name to address 310 */ 311 const addressesByContractByNetworkPlugin: Plugin = { 312 name: 'addressesByContractByNetwork', 313 run: async ({ contracts }) => { 314 const addresses = Object.fromEntries( 315 contracts.map((contract) => [contract.name, contract.address ?? {}]) 316 ) 317 // write to json file so it's easy to audit in prs relative to the generated file diff 318 writeFileSync('./addresses.json', JSON.stringify(addresses, null, 2)) 319 return { 320 content: [ 321 `export const addresses = ${JSON.stringify(addresses)} as const`, 322 `export const predeploys = ${JSON.stringify(predeployContracts)}`, 323 ].join('\n'), 324 } 325 }, 326 } 327 328 /** 329 * This plugin will create an abi mapping from contract name to abi 330 */ 331 const abiPlugin: Plugin = { 332 name: 'abisByContractByNetwork', 333 run: async ({ contracts }) => { 334 const abis = Object.fromEntries( 335 contracts.map((contract) => [contract.name, contract.abi]) 336 ) 337 // write to json file so it's easy to audit in prs relative to the generated file diff 338 writeFileSync('./abis.json', JSON.stringify(abis, null, 2)) 339 return { 340 content: `export const abis = ${JSON.stringify(abis)} as const`, 341 } 342 }, 343 } 344 345 /** 346 * This plugin adds an eslint ignore to the generated code 347 */ 348 const eslintIgnorePlugin: Plugin = { 349 name: 'eslintIgnore', 350 run: async () => { 351 return { 352 prepend: `/* eslint-disable */`, 353 content: ``, 354 } 355 }, 356 } 357 358 const contracts = getContractConfigs() 359 // there is a known wagmi bug with contracts who have both FOO_BAR and fooBar method 360 const contractsWithFilteredDuplicates = getContractConfigs(true) 361 // @see https://wagmi.sh/cli 362 export default defineConfig([ 363 { 364 out: 'src/constants.ts', 365 contracts, 366 plugins: [ 367 eslintIgnorePlugin, 368 addressesByContractByNetworkPlugin, 369 abiPlugin, 370 ], 371 }, 372 { 373 out: 'src/actions.ts', 374 contracts: contractsWithFilteredDuplicates, 375 plugins: [ 376 eslintIgnorePlugin, 377 actions({ 378 getContract: true, 379 // don't include actions because they can be more simply done via getContract 380 prepareWriteContract: false, 381 readContract: false, 382 watchContractEvent: false, 383 writeContract: false, 384 }), 385 ], 386 }, 387 { 388 out: 'src/react.ts', 389 contracts: contractsWithFilteredDuplicates, 390 plugins: [ 391 eslintIgnorePlugin, 392 react({ 393 useContractRead: true, 394 useContractWrite: true, 395 useContractEvent: true, 396 // don't include more niche actions to keep api more simple 397 useContractFunctionRead: false, 398 useContractFunctionWrite: false, 399 useContractItemEvent: false, 400 usePrepareContractFunctionWrite: false, 401 usePrepareContractWrite: false, 402 }), 403 ], 404 }, 405 ])