From 343225a7c1e180d28d6c700004783b219fa87b8b Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Wed, 21 Dec 2022 10:46:16 -0500 Subject: [PATCH] Refactor chain metadata (#1449) - Merge the chain metadata from the explorer, the existing sdk metadata, and the sdk's chain connection configs, all into one - DRY up configs in infra + sdk - Alphabetize chain names and configs - Fix circular dep in types - Add convenience utils for retrieving chain metadata --- .../config/environments/testnet3/agent.ts | 14 +- .../config/environments/testnet3/chains.ts | 1 - typescript/infra/src/core/deploy.ts | 2 +- typescript/sdk/package.json | 1 + .../sdk/src/consts/chainConnectionConfigs.ts | 234 +------ typescript/sdk/src/consts/chainMetadata.ts | 649 +++++++++++++++--- typescript/sdk/src/consts/chains.ts | 49 +- typescript/sdk/src/consts/metamask.ts | 46 -- typescript/sdk/src/events.ts | 36 +- typescript/sdk/src/gas/calculator.ts | 2 +- typescript/sdk/src/index.ts | 18 +- .../sdk/src/providers/ChainConnection.ts | 9 +- typescript/sdk/src/types.ts | 8 +- yarn.lock | 8 + 14 files changed, 670 insertions(+), 407 deletions(-) delete mode 100644 typescript/sdk/src/consts/metamask.ts diff --git a/typescript/infra/config/environments/testnet3/agent.ts b/typescript/infra/config/environments/testnet3/agent.ts index a9321a74a..b4d055723 100644 --- a/typescript/infra/config/environments/testnet3/agent.ts +++ b/typescript/infra/config/environments/testnet3/agent.ts @@ -1,3 +1,5 @@ +import { chainMetadata } from '@hyperlane-xyz/sdk'; + import { ALL_KEY_ROLES } from '../../../src/agents/roles'; import { AgentConfig } from '../../../src/config'; import { @@ -44,22 +46,22 @@ export const hyperlane: AgentConfig = { }, chainOverrides: { alfajores: { - reorgPeriod: 0, + reorgPeriod: chainMetadata.alfajores.blocks.reorgPeriod, }, fuji: { - reorgPeriod: 3, + reorgPeriod: chainMetadata.fuji.blocks.reorgPeriod, }, mumbai: { - reorgPeriod: 32, + reorgPeriod: chainMetadata.mumbai.blocks.reorgPeriod, }, bsctestnet: { - reorgPeriod: 9, + reorgPeriod: chainMetadata.bsctestnet.blocks.reorgPeriod, }, goerli: { - reorgPeriod: 3, + reorgPeriod: chainMetadata.goerli.blocks.reorgPeriod, }, moonbasealpha: { - reorgPeriod: 0, + reorgPeriod: chainMetadata.moonbasealpha.blocks.reorgPeriod, }, }, }, diff --git a/typescript/infra/config/environments/testnet3/chains.ts b/typescript/infra/config/environments/testnet3/chains.ts index a643eac00..59cafa8ce 100644 --- a/typescript/infra/config/environments/testnet3/chains.ts +++ b/typescript/infra/config/environments/testnet3/chains.ts @@ -5,7 +5,6 @@ export const testnetConfigs = { fuji: chainConnectionConfigs.fuji, mumbai: { ...chainConnectionConfigs.mumbai, - confirmations: 3, overrides: { maxFeePerGas: 70 * 10 ** 9, // 70 gwei maxPriorityFeePerGas: 40 * 10 ** 9, // 40 gwei diff --git a/typescript/infra/src/core/deploy.ts b/typescript/infra/src/core/deploy.ts index 094b81aa7..af7e8d39f 100644 --- a/typescript/infra/src/core/deploy.ts +++ b/typescript/infra/src/core/deploy.ts @@ -102,7 +102,7 @@ export class HyperlaneCoreInfraDeployer< multisigIsm: contracts.multisigIsm.address, }, rpcStyle: 'ethereum', - finalityBlocks: metadata.finalityBlocks.toString(), + finalityBlocks: metadata.blocks.reorgPeriod.toString(), connection: { type: ConnectionType.Http, url: '', diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index 18d94959b..614634e5b 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -17,6 +17,7 @@ "@nomiclabs/hardhat-waffle": "^2.0.2", "@types/coingecko-api": "^1.0.10", "@types/node": "^16.9.1", + "@wagmi/chains": "^0.1.3", "chai": "^4.3.6", "dotenv": "^10.0.0", "ethereum-waffle": "^3.4.4", diff --git a/typescript/sdk/src/consts/chainConnectionConfigs.ts b/typescript/sdk/src/consts/chainConnectionConfigs.ts index 6a02b0a0a..83af564f3 100644 --- a/typescript/sdk/src/consts/chainConnectionConfigs.ts +++ b/typescript/sdk/src/consts/chainConnectionConfigs.ts @@ -3,206 +3,40 @@ import { ethers } from 'ethers'; import { StaticCeloJsonRpcProvider } from '@hyperlane-xyz/celo-ethers-provider'; import { ChainMap, ChainName, IChainConnection } from '../types'; - -export const ethereum: IChainConnection = { - provider: new ethers.providers.JsonRpcProvider( - 'https://cloudflare-eth.com', - 1, - ), - confirmations: 7, - blockExplorerUrl: 'https://etherscan.io', -}; - -export const celo: IChainConnection = { - provider: new StaticCeloJsonRpcProvider('https://forno.celo.org', 42220), - confirmations: 1, - blockExplorerUrl: 'https://celoscan.io', -}; - -export const polygon: IChainConnection = { - provider: new ethers.providers.JsonRpcProvider( - 'https://rpc-mainnet.matic.quiknode.pro', - 137, - ), - confirmations: 200, - blockExplorerUrl: 'https://polygonscan.com', -}; - -export const avalanche: IChainConnection = { - provider: new ethers.providers.JsonRpcProvider( - 'https://api.avax.network/ext/bc/C/rpc', - 43114, - ), - confirmations: 3, - blockExplorerUrl: 'https://snowtrace.io', -}; - -export const arbitrum: IChainConnection = { - provider: new ethers.providers.JsonRpcProvider( - 'https://arb1.arbitrum.io/rpc', - 42161, - ), - confirmations: 1, - blockExplorerUrl: 'https://arbiscan.io', -}; - -export const optimism: IChainConnection = { - provider: new ethers.providers.JsonRpcProvider( - 'https://mainnet.optimism.io', - 10, - ), - confirmations: 1, - blockExplorerUrl: 'https://optimistic.etherscan.io', - apiPrefix: 'api-', -}; - -export const bsc: IChainConnection = { - provider: new ethers.providers.JsonRpcProvider( - 'https://rpc.ankr.com/bsc', - 56, - ), - confirmations: 1, - blockExplorerUrl: 'https://bscscan.com', -}; - -export const alfajores: IChainConnection = { - provider: new StaticCeloJsonRpcProvider( - 'https://alfajores-forno.celo-testnet.org', - 44787, - ), - confirmations: 1, - blockExplorerUrl: 'https://alfajores.celoscan.io', - apiPrefix: 'api-', -}; - -export const fuji: IChainConnection = { - provider: new ethers.providers.JsonRpcProvider( - 'https://api.avax-test.network/ext/bc/C/rpc', - 43113, - ), - confirmations: 3, - blockExplorerUrl: 'https://testnet.snowtrace.io', - apiPrefix: 'api-', -}; - -export const goerli: IChainConnection = { - provider: new ethers.providers.JsonRpcProvider( - 'https://rpc.ankr.com/eth_goerli', - 5, - ), - confirmations: 1, - blockExplorerUrl: 'https://goerli.etherscan.io/', - apiPrefix: 'api-', -}; - -export const optimismgoerli: IChainConnection = { - provider: new ethers.providers.JsonRpcProvider( - 'https://goerli.optimism.io', - 420, - ), - confirmations: 1, - blockExplorerUrl: 'https://goerli-optimism.etherscan.io/', - apiPrefix: 'api-', -}; - -export const arbitrumgoerli: IChainConnection = { - provider: new ethers.providers.JsonRpcProvider( - 'https://goerli-rollup.arbitrum.io/rpc ', - 421613, - ), - confirmations: 1, - blockExplorerUrl: 'https://goerli.arbiscan.io', - apiPrefix: 'api-', -}; - -export const mumbai: IChainConnection = { - provider: new ethers.providers.JsonRpcProvider( - 'https://rpc-mumbai.maticvigil.com', - 80001, - ), - confirmations: 30, - blockExplorerUrl: 'https://mumbai.polygonscan.com', - apiPrefix: 'api-', -}; - -export const bsctestnet: IChainConnection = { - provider: new ethers.providers.JsonRpcProvider( - 'https://data-seed-prebsc-1-s3.binance.org:8545', - 97, - ), - confirmations: 1, - blockExplorerUrl: 'https://testnet.bscscan.com', - apiPrefix: 'api-', -}; - -export const moonbasealpha: IChainConnection = { - provider: new ethers.providers.JsonRpcProvider( - 'https://rpc.api.moonbase.moonbeam.network', - 1287, - ), - confirmations: 1, - blockExplorerUrl: 'https://moonbase.moonscan.io', - apiPrefix: 'api-', -}; - -export const moonbeam: IChainConnection = { - provider: new ethers.providers.JsonRpcProvider( - 'https://rpc.api.moonbeam.network', - 1284, - ), - confirmations: 1, - blockExplorerUrl: 'https://moonscan.io', - apiPrefix: 'api-moonbeam.', -}; - -export const test1: IChainConnection = { - provider: new ethers.providers.JsonRpcProvider( - 'http://localhost:8545', - 31337, - ), - confirmations: 1, -}; - -export const test2: IChainConnection = { - provider: new ethers.providers.JsonRpcProvider( - 'http://localhost:8545', - 31337, - ), - confirmations: 1, -}; - -export const test3: IChainConnection = { - provider: new ethers.providers.JsonRpcProvider( - 'http://localhost:8545', - 31337, - ), - confirmations: 1, -}; - -export const chainConnectionConfigs: ChainMap = { - arbitrum, - bsc, - ethereum, - celo, - polygon, - avalanche, - alfajores, - fuji, - goerli, - mumbai, - bsctestnet, - optimism, - moonbasealpha, - moonbeam, - optimismgoerli, - arbitrumgoerli, - test1, - test2, - test3, -}; +import { objMap } from '../utils/objects'; + +import { chainMetadata } from './chainMetadata'; +import { Chains, TestChains } from './chains'; + +function testChainConnection() { + return { + provider: new ethers.providers.JsonRpcProvider( + 'http://localhost:8545', + 31337, + ), + confirmations: 1, + }; +} + +export const chainConnectionConfigs: ChainMap = + objMap(chainMetadata, (chainName, metadata) => { + if (TestChains.includes(chainName)) return testChainConnection(); + + const providerClass = + chainName === Chains.alfajores || chainName === Chains.celo + ? StaticCeloJsonRpcProvider + : ethers.providers.JsonRpcProvider; + + return { + provider: new providerClass(metadata.publicRpcUrls[0].http, metadata.id), + confirmations: metadata.blocks.confirmations, + blockExplorerUrl: metadata.blockExplorers[0].url, + blockExplorerApiUrl: metadata.blockExplorers[0].apiUrl, + }; + }); export const testChainConnectionConfigs = { - test1, - test2, - test3, + test1: testChainConnection(), + test2: testChainConnection(), + test3: testChainConnection(), }; diff --git a/typescript/sdk/src/consts/chainMetadata.ts b/typescript/sdk/src/consts/chainMetadata.ts index 9a2b731b0..5180978dc 100644 --- a/typescript/sdk/src/consts/chainMetadata.ts +++ b/typescript/sdk/src/consts/chainMetadata.ts @@ -1,187 +1,628 @@ -import { ChainName } from '../types'; +import type { Chain as WagmiChain } from '@wagmi/chains'; + +import { objMap } from '../utils/objects'; + +import { ChainName, Chains, Mainnets, Testnets } from './chains'; + +export enum ExplorerFamily { + Etherscan = 'etherscan', + Blockscout = 'blockscout', + Other = 'other', +} /** - * A Chain and its characteristics + * Collection of useful properties and settings + * for Hyperlane-supported chains */ -export type ChainMetadata = { +export interface ChainMetadata { id: number; - finalityBlocks: number; - nativeTokenDecimals?: number; - paginate?: RpcPagination; - // The CoinGecko API expects, in some cases, IDs that do not match - // ChainNames. + name: ChainName; + /** Human-readable name */ + displayName: string; + /** Shorter human-readable name */ + displayNameShort?: string; + /** Default currency/token used by chain */ + nativeToken: { + name: string; + symbol: string; + decimals: number; + }; + /** Collection of RPC endpoints */ + publicRpcUrls: Array<{ + http: string; + webSocket?: string; + pagination?: RpcPagination; + }>; + /** Collection of block explorers */ + blockExplorers: Array<{ + name: string; + url: string; + family: ExplorerFamily; + apiUrl?: string; + }>; + blocks: { + // Number of blocks to wait before considering a transaction confirmed + confirmations: number; + // TODO consider merging with confirmations, require agent code changes + // Number of blocks before a transaction has a near-zero chance of reverting + reorgPeriod: number; + // Rough estimate of time per block in seconds + estimateBlockTime: number; + }; + // The CoinGecko API sometimes expects IDs that do not match ChainNames gasCurrencyCoinGeckoId?: string; // URL of the gnosis safe transaction service. gnosisSafeTransactionServiceUrl?: string; -}; +} -/** - * RPC Pagination information - */ export interface RpcPagination { blocks: number; from: number; } -// IDs can be generated in many ways-- for example, in JS: -// > Array.from('celo').map((c, i) => c.charCodeAt(0).toString(16).padStart(2, '0')).join('') -// '63656c6f' - /** - * Mainnets + * Common native currencies */ -export const celo: ChainMetadata = { - id: 42220, - finalityBlocks: 0, - gnosisSafeTransactionServiceUrl: - 'https://transaction-service.gnosis-safe-staging.celo-networks-dev.org', +const avaxToken = { + decimals: 18, + name: 'Avalanche', + symbol: 'AVAX', }; +const bnbToken = { + decimals: 18, + name: 'BNB', + symbol: 'BNB', +}; +const celoToken = { + decimals: 18, + name: 'CELO', + symbol: 'CELO', +}; +const etherToken = { name: 'Ether', symbol: 'ETH', decimals: 18 }; +const maticToken = { name: 'MATIC', symbol: 'MATIC', decimals: 18 }; -export const ethereum: ChainMetadata = { - id: 1, - finalityBlocks: 20, - gnosisSafeTransactionServiceUrl: 'https://safe-transaction.gnosis.io', +/** + * Chain metadata + */ + +export const alfajores: ChainMetadata = { + id: 44787, + name: Chains.alfajores, + displayName: 'Alfajores', + nativeToken: celoToken, + publicRpcUrls: [{ http: 'https://alfajores-forno.celo-testnet.org' }], + blockExplorers: [ + { + name: 'CeloScan', + url: 'https://alfajores.celoscan.io', + family: ExplorerFamily.Etherscan, + }, + { + name: 'Blockscout', + url: 'https://explorer.celo.org/alfajores', + family: ExplorerFamily.Blockscout, + }, + ], + blocks: { + confirmations: 1, + reorgPeriod: 0, + estimateBlockTime: 5, + }, }; export const arbitrum: ChainMetadata = { id: 42161, - finalityBlocks: 0, + name: Chains.arbitrum, + displayName: 'Arbitrum', + nativeToken: etherToken, + publicRpcUrls: [{ http: 'https://arb1.arbitrum.io/rpc' }], + blockExplorers: [ + { + name: 'Arbiscan', + url: 'https://arbiscan.io', + apiUrl: 'https://api.arbiscan.io', + family: ExplorerFamily.Etherscan, + }, + ], + blocks: { + confirmations: 1, + reorgPeriod: 0, + estimateBlockTime: 3, + }, gasCurrencyCoinGeckoId: 'ethereum', // ETH is used for gas gnosisSafeTransactionServiceUrl: 'https://safe-transaction.arbitrum.gnosis.io/', }; -export const optimism: ChainMetadata = { - id: 10, - finalityBlocks: 0, - gasCurrencyCoinGeckoId: 'ethereum', // ETH is used for gas +export const arbitrumgoerli: ChainMetadata = { + id: 421613, + name: Chains.arbitrumgoerli, + displayName: 'Arbitrum Goerli', + displayNameShort: 'Arb. Goerli', + nativeToken: etherToken, + publicRpcUrls: [{ http: 'https://goerli-rollup.arbitrum.io/rpc' }], + blockExplorers: [ + { + name: 'Arbiscan', + url: 'https://goerli.arbiscan.io/', + apiUrl: 'https://api-goerli.arbiscan.io', + family: ExplorerFamily.Etherscan, + }, + ], + blocks: { + confirmations: 1, + reorgPeriod: 1, + estimateBlockTime: 3, + }, +}; + +export const avalanche: ChainMetadata = { + id: 43114, + name: Chains.avalanche, + displayName: 'Avalanche', + nativeToken: avaxToken, + publicRpcUrls: [ + { + http: 'https://api.avax.network/ext/bc/C/rpc', + pagination: { + blocks: 100000, + from: 6765067, + }, + }, + ], + blockExplorers: [ + { + name: 'SnowTrace', + url: 'https://snowtrace.io', + apiUrl: 'https://api.snowtrace.io', + family: ExplorerFamily.Other, + }, + ], + blocks: { + confirmations: 3, + reorgPeriod: 3, + estimateBlockTime: 2, + }, + gasCurrencyCoinGeckoId: 'avalanche-2', gnosisSafeTransactionServiceUrl: - 'https://safe-transaction.optimism.gnosis.io/', + 'https://safe-transaction.avalanche.gnosis.io/', }; export const bsc: ChainMetadata = { id: 56, - finalityBlocks: 15, + name: Chains.bsc, + displayName: 'Binance Smart Chain', + displayNameShort: 'Binance', + nativeToken: bnbToken, + publicRpcUrls: [ + { http: 'https://bsc-dataseed.binance.org' }, + { http: 'https://rpc.ankr.com/bsc' }, + ], + blockExplorers: [ + { + name: 'BscScan', + url: 'https://bscscan.com', + apiUrl: 'https://api.bscscan.com', + family: ExplorerFamily.Etherscan, + }, + ], + blocks: { + confirmations: 1, + reorgPeriod: 15, + estimateBlockTime: 3, + }, gasCurrencyCoinGeckoId: 'binancecoin', gnosisSafeTransactionServiceUrl: 'https://safe-transaction.bsc.gnosis.io/', }; -export const avalanche: ChainMetadata = { - id: 43114, - finalityBlocks: 3, - paginate: { - // Needs to be low to avoid RPC timeouts - blocks: 100000, - from: 6765067, +export const bsctestnet: ChainMetadata = { + id: 97, + name: Chains.bsctestnet, + displayName: 'BSC Testnet', + nativeToken: bnbToken, + publicRpcUrls: [{ http: 'https://data-seed-prebsc-1-s3.binance.org:8545' }], + blockExplorers: [ + { + name: 'BscScan', + url: 'https://testnet.bscscan.com', + apiUrl: 'https://api-testnet.bscscan.com', + family: ExplorerFamily.Etherscan, + }, + ], + blocks: { + confirmations: 1, + reorgPeriod: 9, + estimateBlockTime: 3, }, - gasCurrencyCoinGeckoId: 'avalanche-2', - gnosisSafeTransactionServiceUrl: - 'https://safe-transaction.avalanche.gnosis.io/', }; -export const polygon: ChainMetadata = { - id: 137, - finalityBlocks: 256, - paginate: { - // Needs to be low to avoid RPC timeouts - blocks: 10000, - from: 19657100, +export const celo: ChainMetadata = { + id: 42220, + name: Chains.celo, + displayName: 'Celo', + nativeToken: celoToken, + publicRpcUrls: [{ http: 'https://forno.celo.org' }], + blockExplorers: [ + { + name: 'CeloScan', + url: 'https://celoscan.io', + apiUrl: 'https://api.celoscan.io', + family: ExplorerFamily.Etherscan, + }, + { + name: 'Blockscout', + url: 'https://explorer.celo.org', + family: ExplorerFamily.Blockscout, + }, + ], + blocks: { + confirmations: 1, + reorgPeriod: 0, + estimateBlockTime: 5, }, - gasCurrencyCoinGeckoId: 'matic-network', gnosisSafeTransactionServiceUrl: - 'https://safe-transaction.polygon.gnosis.io/', + 'https://transaction-service.gnosis-safe-staging.celo-networks-dev.org', }; -/** - * Testnets - */ -export const alfajores: ChainMetadata = { - id: 44787, - finalityBlocks: 0, +export const ethereum: ChainMetadata = { + id: 1, + name: Chains.ethereum, + displayName: 'Ethereum', + nativeToken: etherToken, + publicRpcUrls: [{ http: 'https://cloudflare-eth.com' }], + blockExplorers: [ + { + name: 'Etherscan', + url: 'https://etherscan.io', + apiUrl: 'https://api.etherscan.io', + family: ExplorerFamily.Etherscan, + }, + { + name: 'Blockscout', + url: 'https://blockscout.com/eth/mainnet', + family: ExplorerFamily.Blockscout, + }, + ], + blocks: { + confirmations: 7, + reorgPeriod: 20, + estimateBlockTime: 13, + }, + gnosisSafeTransactionServiceUrl: 'https://safe-transaction.gnosis.io', }; export const fuji: ChainMetadata = { id: 43113, - finalityBlocks: 3, + name: Chains.fuji, + displayName: 'Fuji', + nativeToken: avaxToken, + publicRpcUrls: [{ http: 'https://api.avax-test.network/ext/bc/C/rpc' }], + blockExplorers: [ + { + name: 'SnowTrace', + url: 'https://testnet.snowtrace.io', + apiUrl: 'https://api-testnet.snowtrace.io', + family: ExplorerFamily.Other, + }, + ], + blocks: { + confirmations: 3, + reorgPeriod: 3, + estimateBlockTime: 2, + }, }; export const goerli: ChainMetadata = { id: 5, - finalityBlocks: 2, -}; - -export const optimismgoerli: ChainMetadata = { - id: 420, - finalityBlocks: 1, + name: Chains.goerli, + displayName: 'Goerli', + nativeToken: etherToken, + publicRpcUrls: [{ http: 'https://rpc.ankr.com/eth_goerli' }], + blockExplorers: [ + { + name: 'Etherscan', + url: 'https://goerli.etherscan.io', + apiUrl: 'https://api-goerli.etherscan.io', + family: ExplorerFamily.Etherscan, + }, + ], + blocks: { + confirmations: 1, + reorgPeriod: 2, + estimateBlockTime: 13, + }, }; -export const arbitrumgoerli: ChainMetadata = { - id: 421613, - finalityBlocks: 1, +export const moonbasealpha: ChainMetadata = { + id: 1287, + name: Chains.moonbasealpha, + displayName: 'Moonbase Alpha', + displayNameShort: 'Moonbase', + nativeToken: { + decimals: 18, + name: 'DEV', + symbol: 'DEV', + }, + publicRpcUrls: [{ http: 'https://rpc.api.moonbase.moonbeam.network' }], + blockExplorers: [ + { + name: 'MoonScan', + url: 'https://moonbase.moonscan.io', + apiUrl: 'https://api-moonbase.moonscan.io', + family: ExplorerFamily.Etherscan, + }, + ], + blocks: { + confirmations: 1, + reorgPeriod: 1, + estimateBlockTime: 12, + }, }; -export const zksync2testnet: ChainMetadata = { - id: 280, - finalityBlocks: 1, +export const moonbeam: ChainMetadata = { + id: 1284, + name: Chains.moonbeam, + displayName: 'Moonbeam', + nativeToken: { + decimals: 18, + name: 'GLMR', + symbol: 'GLMR', + }, + publicRpcUrls: [{ http: 'https://rpc.api.moonbeam.network' }], + blockExplorers: [ + { + name: 'MoonScan', + url: 'https://moonscan.io', + apiUrl: 'https://api-moonbeam.moonscan.io', + family: ExplorerFamily.Etherscan, + }, + ], + blocks: { + confirmations: 1, + reorgPeriod: 1, + estimateBlockTime: 12, + }, }; export const mumbai: ChainMetadata = { id: 80001, - finalityBlocks: 32, - paginate: { - // eth_getLogs and eth_newFilter are limited to a 10,000 blocks range - blocks: 10000, - from: 22900000, + name: Chains.mumbai, + displayName: 'Mumbai', + nativeToken: maticToken, + publicRpcUrls: [ + { + http: 'https://rpc-mumbai.maticvigil.com', + pagination: { + // eth_getLogs and eth_newFilter are limited to a 10,000 blocks range + blocks: 10000, + from: 22900000, + }, + }, + { + http: 'https://matic-mumbai.chainstacklabs.com', + }, + ], + blockExplorers: [ + { + name: 'PolygonScan', + url: 'https://mumbai.polygonscan.com', + apiUrl: 'https://api-testnet.polygonscan.com', + family: ExplorerFamily.Etherscan, + }, + ], + blocks: { + confirmations: 3, + reorgPeriod: 32, + estimateBlockTime: 5, }, }; -const testChains = { - test1: { - id: 13371, - finalityBlocks: 0, +export const optimism: ChainMetadata = { + id: 10, + name: Chains.optimism, + displayName: 'Optimism', + nativeToken: etherToken, + publicRpcUrls: [{ http: 'https://mainnet.optimism.io' }], + blockExplorers: [ + { + name: 'Etherscan', + url: 'https://optimistic.etherscan.io', + apiUrl: 'https://api-optimistic.etherscan.io', + family: ExplorerFamily.Etherscan, + }, + ], + blocks: { + confirmations: 1, + reorgPeriod: 0, + estimateBlockTime: 3, }, - test2: { - id: 13372, - finalityBlocks: 1, + gasCurrencyCoinGeckoId: 'ethereum', // ETH is used for gas + gnosisSafeTransactionServiceUrl: + 'https://safe-transaction.optimism.gnosis.io/', +}; + +export const optimismgoerli: ChainMetadata = { + id: 420, + name: Chains.optimismgoerli, + displayName: 'Optimism Goerli', + displayNameShort: 'Opt. Goerli', + nativeToken: etherToken, + publicRpcUrls: [{ http: 'https://goerli.optimism.io' }], + blockExplorers: [ + { + name: 'Etherscan', + url: 'https://goerli-optimism.etherscan.io', + apiUrl: 'https://api-goerli-optimism.etherscan.io', + family: ExplorerFamily.Etherscan, + }, + ], + blocks: { + confirmations: 1, + reorgPeriod: 1, + estimateBlockTime: 3, }, - test3: { - id: 13373, - finalityBlocks: 2, +}; + +export const polygon: ChainMetadata = { + id: 137, + name: Chains.polygon, + displayName: 'Polygon', + nativeToken: etherToken, + publicRpcUrls: [ + { + http: 'https://rpc-mainnet.matic.quiknode.pro', + pagination: { + // Needs to be low to avoid RPC timeouts + blocks: 10000, + from: 19657100, + }, + }, + { http: 'https://polygon-rpc.com' }, + ], + blockExplorers: [ + { + name: 'PolygonScan', + url: 'https://polygonscan.com', + apiUrl: 'https://api.polygonscan.com', + family: ExplorerFamily.Etherscan, + }, + ], + blocks: { + confirmations: 200, + reorgPeriod: 256, + estimateBlockTime: 2, }, + gasCurrencyCoinGeckoId: 'matic-network', + gnosisSafeTransactionServiceUrl: + 'https://safe-transaction.polygon.gnosis.io/', }; -export const bsctestnet: ChainMetadata = { - id: 97, - finalityBlocks: 9, +export const test1: ChainMetadata = { + id: 13371, + name: Chains.test1, + displayName: 'Test 1', + nativeToken: etherToken, + publicRpcUrls: [{ http: 'http://localhost:8545' }], + blockExplorers: [], + blocks: { + confirmations: 1, + reorgPeriod: 0, + estimateBlockTime: 3, + }, }; -export const moonbasealpha: ChainMetadata = { - id: 1287, - finalityBlocks: 1, +export const test2: ChainMetadata = { + id: 13372, + name: Chains.test2, + displayName: 'Test 2', + nativeToken: etherToken, + publicRpcUrls: [{ http: 'http://localhost:8545' }], + blockExplorers: [], + blocks: { + confirmations: 1, + reorgPeriod: 1, + estimateBlockTime: 3, + }, }; -export const moonbeam: ChainMetadata = { - id: 1284, - finalityBlocks: 1, +export const test3: ChainMetadata = { + id: 13373, + name: Chains.test3, + displayName: 'Test 3', + nativeToken: etherToken, + publicRpcUrls: [{ http: 'http://localhost:8545' }], + blockExplorers: [], + blocks: { + confirmations: 1, + reorgPeriod: 2, + estimateBlockTime: 3, + }, }; +/** + * Collection maps + */ export const chainMetadata = { + alfajores, arbitrum, + arbitrumgoerli, + avalanche, bsc, + bsctestnet, celo, ethereum, - avalanche, - optimism, - polygon, - alfajores, fuji, goerli, - mumbai, - bsctestnet, moonbasealpha, moonbeam, + mumbai, + optimism, optimismgoerli, - arbitrumgoerli, - zksync2testnet, - ...testChains, + polygon, + test1, + test2, + test3, } as Record; + +// For convenient use in wagmi-based apps +export const wagmiChainMetadata: Record = objMap( + chainMetadata, + (_, metadata) => ({ + id: metadata.id, + name: metadata.displayName, + network: metadata.name as string, + nativeCurrency: metadata.nativeToken, + rpcUrls: { default: { http: [metadata.publicRpcUrls[0].http] } }, + blockExplorers: metadata.blockExplorers.length + ? { + default: { + name: metadata.blockExplorers[0].name, + url: metadata.blockExplorers[0].url, + }, + } + : undefined, + testnet: Testnets.includes(metadata.name), + }), +); + +export const chainIdToMetadata = Object.values(chainMetadata).reduce< + Record +>((result, chain) => { + result[chain.id] = chain; + return result; +}, {}); + +export const mainnetChainsMetadata: Array = Mainnets.map( + (chainName) => chainMetadata[chainName], +); +export const testnetChainsMetadata: Array = Testnets.map( + (chainName) => chainMetadata[chainName], +); + +/** + * @deprecated use ChainMetadata + */ +export type PartialChainMetadata = { + id: number; + finalityBlocks: number; + nativeTokenDecimals?: number; + paginate?: RpcPagination; + // The CoinGecko API expects, in some cases, IDs that do not match + // ChainNames. + gasCurrencyCoinGeckoId?: string; + // URL of the gnosis safe transaction service. + gnosisSafeTransactionServiceUrl?: string; +}; + +/** + * @deprecated use chainMetadata + */ +export const partialChainMetadata: Record = + objMap(chainMetadata, (_, metadata) => ({ + id: metadata.id, + finalityBlocks: metadata.blocks.confirmations, + nativeTokenDecimals: metadata.nativeToken.decimals, + paginate: metadata.publicRpcUrls[0]?.pagination, + gasCurrencyCoinGeckoId: metadata.gasCurrencyCoinGeckoId, + gnosisSafeTransactionServiceUrl: metadata.gnosisSafeTransactionServiceUrl, + })); diff --git a/typescript/sdk/src/consts/chains.ts b/typescript/sdk/src/consts/chains.ts index 1031f57cf..92f58163d 100644 --- a/typescript/sdk/src/consts/chains.ts +++ b/typescript/sdk/src/consts/chains.ts @@ -1,38 +1,40 @@ -import { ChainName } from '../types'; - /** * Enumeration of Hyperlane supported chains + * Must be string type to be used with Object.keys */ -export enum Chains { // must be string type to be used with Object.keys - arbitrum = 'arbitrum', +export enum Chains { alfajores = 'alfajores', + arbitrum = 'arbitrum', + arbitrumgoerli = 'arbitrumgoerli', + avalanche = 'avalanche', bsc = 'bsc', - mumbai = 'mumbai', - goerli = 'goerli', - fuji = 'fuji', + bsctestnet = 'bsctestnet', celo = 'celo', ethereum = 'ethereum', - avalanche = 'avalanche', - optimism = 'optimism', - polygon = 'polygon', - bsctestnet = 'bsctestnet', + fuji = 'fuji', + goerli = 'goerli', moonbasealpha = 'moonbasealpha', moonbeam = 'moonbeam', + mumbai = 'mumbai', + optimism = 'optimism', optimismgoerli = 'optimismgoerli', - arbitrumgoerli = 'arbitrumgoerli', + polygon = 'polygon', test1 = 'test1', test2 = 'test2', test3 = 'test3', } +export type ChainName = keyof typeof Chains; + export enum DeprecatedChains { - rinkeby = 'rinkeby', - optimismrinkeby = 'optimismrinkeby', + arbitrumkovan = 'arbitrumkovan', arbitrumrinkeby = 'arbitrumrinkeby', kovan = 'kovan', + rinkeby = 'rinkeby', optimismkovan = 'optimismkovan', - arbitrumkovan = 'arbitrumkovan', + optimismrinkeby = 'optimismrinkeby', } + export const AllDeprecatedChains = Object.keys(DeprecatedChains) as string[]; export const Mainnets = [ @@ -46,4 +48,21 @@ export const Mainnets = [ Chains.moonbeam, ] as Array; +export const Testnets = [ + Chains.alfajores, + Chains.arbitrumgoerli, + Chains.bsctestnet, + Chains.fuji, + Chains.goerli, + Chains.moonbasealpha, + Chains.mumbai, + Chains.optimismgoerli, +] as Array; + +export const TestChains = [ + Chains.test1, + Chains.test2, + Chains.test3, +] as Array; + export const AllChains = Object.keys(Chains) as Array; diff --git a/typescript/sdk/src/consts/metamask.ts b/typescript/sdk/src/consts/metamask.ts deleted file mode 100644 index d51c97562..000000000 --- a/typescript/sdk/src/consts/metamask.ts +++ /dev/null @@ -1,46 +0,0 @@ -export type MetamaskNetwork = { - chainId: string; - chainName: string; - nativeCurrency: { name: string; symbol: string; decimals: number }; - rpcUrls: string[]; - blockExplorerUrls: string[]; - iconUrls: string[]; -}; - -export const CELO_PARAMS: MetamaskNetwork = { - chainId: '0xa4ec', - chainName: 'Celo', - nativeCurrency: { name: 'Celo', symbol: 'CELO', decimals: 18 }, - rpcUrls: ['https://forno.celo.org'], - blockExplorerUrls: ['https://explorer.celo.org/'], - iconUrls: ['future'], -}; - -export const ALFAJORES_PARAMS: MetamaskNetwork = { - chainId: '0xaef3', - chainName: 'Alfajores Testnet', - nativeCurrency: { name: 'Alfajores Celo', symbol: 'A-CELO', decimals: 18 }, - rpcUrls: ['https://alfajores-forno.celo-testnet.org'], - blockExplorerUrls: ['https://alfajores-blockscout.celo-testnet.org/'], - iconUrls: ['future'], -}; - -export const BAKLAVA_PARAMS: MetamaskNetwork = { - chainId: '0xf370', - chainName: 'Baklava Testnet', - nativeCurrency: { name: 'Baklava Celo', symbol: 'B-CELO', decimals: 18 }, - rpcUrls: ['https://baklava-forno.celo-testnet.org'], - blockExplorerUrls: ['https://baklava-blockscout.celo-testnet.org/'], - iconUrls: ['future'], -}; - -export async function connect(params: MetamaskNetwork): Promise { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - const w = window as any; - if (w.ethereum) { - await w.ethereum.request({ - method: 'wallet_addEthereumChain', - params: [params], - }); - } -} diff --git a/typescript/sdk/src/events.ts b/typescript/sdk/src/events.ts index 5c4907962..307ccca51 100644 --- a/typescript/sdk/src/events.ts +++ b/typescript/sdk/src/events.ts @@ -123,8 +123,8 @@ export async function getEvents( startBlock?: number, endBlock?: number, ): Promise> { - const domain = chainMetadata[chain]; - if (domain.paginate) { + const mustPaginate = !!chainMetadata[chain].publicRpcUrls[0].pagination; + if (mustPaginate) { return getPaginatedEvents( multiprovider, chain, @@ -144,8 +144,8 @@ export async function findEvent( filter: TypedEventFilter, startBlock?: number, ): Promise> { - const domain = chainMetadata[chain]; - if (domain.paginate) { + const mustPaginate = !!chainMetadata[chain].publicRpcUrls[0].pagination; + if (mustPaginate) { return findFromPaginatedEvents( multiprovider, chain, @@ -165,15 +165,15 @@ async function getPaginatedEvents( startBlock?: number, endBlock?: number, ): Promise> { - const domain = chainMetadata[chain]; - if (!domain.paginate) { + const pagination = chainMetadata[chain].publicRpcUrls[0].pagination; + if (!pagination) { throw new Error('Domain need not be paginated'); } // get the first block by params // or domain deployment block const firstBlock = startBlock - ? Math.max(startBlock, domain.paginate.from) - : domain.paginate.from; + ? Math.max(startBlock, pagination.from) + : pagination.from; // get the last block by params // or current block number let lastBlock; @@ -185,12 +185,8 @@ async function getPaginatedEvents( } // query domain pagination limit at a time, concurrently const eventArrayPromises = []; - for ( - let from = firstBlock; - from <= lastBlock; - from += domain.paginate.blocks - ) { - const nextFrom = from + domain.paginate.blocks; + for (let from = firstBlock; from <= lastBlock; from += pagination.blocks) { + const nextFrom = from + pagination.blocks; const to = Math.min(nextFrom, lastBlock); const eventArrayPromise = contract.queryFilter(filter, from, to); eventArrayPromises.push(eventArrayPromise); @@ -212,15 +208,15 @@ async function findFromPaginatedEvents( startBlock?: number, endBlock?: number, ): Promise> { - const domain = chainMetadata[chain]; - if (!domain.paginate) { + const pagination = chainMetadata[chain].publicRpcUrls[0].pagination; + if (!pagination) { throw new Error('Domain need not be paginated'); } // get the first block by params // or domain deployment block const firstBlock = startBlock - ? Math.max(startBlock, domain.paginate.from) - : domain.paginate.from; + ? Math.max(startBlock, pagination.from) + : pagination.from; // get the last block by params // or current block number let lastBlock; @@ -232,8 +228,8 @@ async function findFromPaginatedEvents( } // query domain pagination limit at a time, concurrently // eslint-disable-next-line for-direction - for (let end = lastBlock; end > firstBlock; end -= domain.paginate.blocks) { - const nextEnd = end - domain.paginate.blocks; + for (let end = lastBlock; end > firstBlock; end -= pagination.blocks) { + const nextEnd = end - pagination.blocks; const from = Math.max(nextEnd, firstBlock); const queriedEvents = await contract.queryFilter(filter, from, end); if (queriedEvents.length > 0) { diff --git a/typescript/sdk/src/gas/calculator.ts b/typescript/sdk/src/gas/calculator.ts index 171ad0094..fa1f3163f 100644 --- a/typescript/sdk/src/gas/calculator.ts +++ b/typescript/sdk/src/gas/calculator.ts @@ -252,7 +252,7 @@ export class InterchainGasCalculator { * @returns The number of decimals of `chain`'s native token. */ protected tokenDecimals(chain: Chain): number { - return chainMetadata[chain].nativeTokenDecimals ?? DEFAULT_TOKEN_DECIMALS; + return chainMetadata[chain].nativeToken.decimals ?? DEFAULT_TOKEN_DECIMALS; } /** diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index 8509d6d76..f485fd10d 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -1,10 +1,23 @@ export { - AllChains, Chains, + ChainName, + DeprecatedChains, + AllChains, Mainnets, AllDeprecatedChains, } from './consts/chains'; -export { chainMetadata } from './consts/chainMetadata'; +export { + ChainMetadata, + RpcPagination, + ExplorerFamily, + chainMetadata, + chainIdToMetadata, + mainnetChainsMetadata, + testnetChainsMetadata, + wagmiChainMetadata, + PartialChainMetadata, + partialChainMetadata, +} from './consts/chainMetadata'; export { chainConnectionConfigs, testChainConnectionConfigs, @@ -16,7 +29,6 @@ export { export { ChainMap, - ChainName, CompleteChainMap, Connection, NameOrDomain, diff --git a/typescript/sdk/src/providers/ChainConnection.ts b/typescript/sdk/src/providers/ChainConnection.ts index 107c26e16..bc9926972 100644 --- a/typescript/sdk/src/providers/ChainConnection.ts +++ b/typescript/sdk/src/providers/ChainConnection.ts @@ -9,7 +9,7 @@ export class ChainConnection { overrides: ethers.Overrides; confirmations: number; blockExplorerUrl: string; - apiPrefix: string; + blockExplorerApiUrl: string; logger: Debugger; constructor(dc: IChainConnection) { @@ -18,7 +18,7 @@ export class ChainConnection { this.overrides = dc.overrides ?? {}; this.confirmations = dc.confirmations ?? 0; this.blockExplorerUrl = dc.blockExplorerUrl ?? 'UNKNOWN_EXPLORER'; - this.apiPrefix = dc.apiPrefix ?? 'api.'; + this.blockExplorerApiUrl = dc.blockExplorerApiUrl ?? this.blockExplorerUrl; this.logger = debug('hyperlane:ChainConnection'); } @@ -38,10 +38,7 @@ export class ChainConnection { } getApiUrl(): string { - const prefix = 'https://'; - return `${prefix}${this.apiPrefix}${this.blockExplorerUrl.slice( - prefix.length, - )}/api`; + return this.blockExplorerApiUrl; } async handleTx( diff --git a/typescript/sdk/src/types.ts b/typescript/sdk/src/types.ts index 298334c16..8a46f98dc 100644 --- a/typescript/sdk/src/types.ts +++ b/typescript/sdk/src/types.ts @@ -1,9 +1,9 @@ import type { ethers } from 'ethers'; -import type { Chains } from './consts/chains'; +import type { ChainName } from './consts/chains'; -// A union type of the keys in the Chains enum -export type ChainName = keyof typeof Chains; +// Re-export ChainName for convenience +export { ChainName }; // A full object map of all chains to a value type export type CompleteChainMap = Record; export type LooseChainMap = Record; @@ -35,5 +35,5 @@ export interface IChainConnection { overrides?: ethers.Overrides; confirmations?: number; blockExplorerUrl?: string; - apiPrefix?: string; + blockExplorerApiUrl?: string; } diff --git a/yarn.lock b/yarn.lock index 26295ea68..1536f99e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3846,6 +3846,7 @@ __metadata: "@types/coingecko-api": ^1.0.10 "@types/debug": ^4.1.7 "@types/node": ^16.9.1 + "@wagmi/chains": ^0.1.3 chai: ^4.3.6 coingecko-api: ^1.0.10 cross-fetch: ^3.1.5 @@ -4945,6 +4946,13 @@ __metadata: languageName: node linkType: hard +"@wagmi/chains@npm:^0.1.3": + version: 0.1.3 + resolution: "@wagmi/chains@npm:0.1.3" + checksum: 341ad5bca7b1ebc425395974dc03f3e32d0aca14f44c75950ced8cd0baed30561b9c53c9f1f7cbc60e4d949f38b257d086b772f15c98b334e4a28b4ef02690ee + languageName: node + linkType: hard + "@yarnpkg/lockfile@npm:^1.1.0": version: 1.1.0 resolution: "@yarnpkg/lockfile@npm:1.1.0"