diff --git a/.changeset/wise-mangos-crash.md b/.changeset/wise-mangos-crash.md new file mode 100644 index 0000000000..e46546c5f7 --- /dev/null +++ b/.changeset/wise-mangos-crash.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/sdk': patch +--- + +Remove healthy RPC URLs and remove NeutronTestnet diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7b7d9d5c99..ca16cd2edd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -128,6 +128,34 @@ jobs: - name: Unit Tests run: yarn test + metadata-check: + runs-on: ubuntu-latest + needs: [yarn-build] + steps: + - uses: actions/checkout@v3 + with: + submodules: recursive + fetch-depth: 0 + + - name: yarn-cache + uses: actions/cache@v3 + with: + path: | + **/node_modules + .yarn + key: ${{ runner.os }}-yarn-cache-${{ hashFiles('./yarn.lock') }} + + - name: build-cache + uses: actions/cache@v3 + with: + path: | + ./* + !./rust + key: ${{ github.sha }} + + - name: Metadata Health Check + run: yarn workspace @hyperlane-xyz/sdk run test:metadata + e2e: runs-on: larger-runner needs: [yarn-build] diff --git a/typescript/infra/config/environments/testnet4/agent.ts b/typescript/infra/config/environments/testnet4/agent.ts index 433147125e..662a9431f8 100644 --- a/typescript/infra/config/environments/testnet4/agent.ts +++ b/typescript/infra/config/environments/testnet4/agent.ts @@ -112,7 +112,7 @@ const neutron: RootAgentConfig = { context: Contexts.Neutron, rolesWithKeys: [Role.Relayer], contextChainNames: { - relayer: [Chains.neutrontestnet, Chains.goerli], + relayer: [Chains.goerli], validator: [], scraper: [], }, diff --git a/typescript/infra/config/environments/testnet4/validators.ts b/typescript/infra/config/environments/testnet4/validators.ts index da7ca5a070..44ddc20054 100644 --- a/typescript/infra/config/environments/testnet4/validators.ts +++ b/typescript/infra/config/environments/testnet4/validators.ts @@ -323,21 +323,5 @@ export const validatorChainConfig = ( // 'solanadevnet', // ), // }, - neutrontestnet: { - interval: 5, - reorgPeriod: chainMetadata.neutrontestnet.blocks!.reorgPeriod!, - validators: validatorsConfig( - { - [Contexts.Hyperlane]: [ - '0x5d2a99d67cd294a821de4fb25da6901ea8f89814', - '0xb57486243ce3bb3c38c50a582b8bbd20cb393589', - '0x661faee997654d14ead4ae48035883f05c3150cf', - ], - [Contexts.ReleaseCandidate]: [], - [Contexts.Neutron]: [], - }, - 'neutrontestnet', - ), - }, }; }; diff --git a/typescript/infra/scripts/check-rpc-urls.ts b/typescript/infra/scripts/check-rpc-urls.ts index 6ac91ef389..48e874de58 100644 --- a/typescript/infra/scripts/check-rpc-urls.ts +++ b/typescript/infra/scripts/check-rpc-urls.ts @@ -6,6 +6,8 @@ import { getSecretRpcEndpoint } from '../src/agents'; import { getArgs, getEnvironmentConfig } from './utils'; +// TODO remove this script as part of migration to CLI +// It's redundant with metadata-check.ts in the SDK async function main() { const { environment } = await getArgs().argv; const config = await getEnvironmentConfig(environment); diff --git a/typescript/sdk/package.json b/typescript/sdk/package.json index bf5428e1a5..8e8b0df601 100644 --- a/typescript/sdk/package.json +++ b/typescript/sdk/package.json @@ -62,7 +62,8 @@ "prettier": "prettier --write ./src", "test": "yarn test:unit && yarn test:hardhat", "test:unit": "mocha --config .mocharc.json './src/**/*.test.ts'", - "test:hardhat": "hardhat test $(find ./src -name \"*.hardhat-test.ts\")" + "test:hardhat": "hardhat test $(find ./src -name \"*.hardhat-test.ts\")", + "test:metadata": "ts-node ./src/test/metadata-check.ts" }, "types": "dist/index.d.ts", "peerDependencies": { diff --git a/typescript/sdk/src/consts/.eslintrc b/typescript/sdk/src/consts/.eslintrc new file mode 100644 index 0000000000..7242f12412 --- /dev/null +++ b/typescript/sdk/src/consts/.eslintrc @@ -0,0 +1,5 @@ +{ + "rules": { + "sort-keys": ["error"] + } +} diff --git a/typescript/sdk/src/consts/chainMetadata.ts b/typescript/sdk/src/consts/chainMetadata.ts index 6c4410d03d..11728d5ac0 100644 --- a/typescript/sdk/src/consts/chainMetadata.ts +++ b/typescript/sdk/src/consts/chainMetadata.ts @@ -11,103 +11,116 @@ import { Chains, Mainnets, Testnets } from './chains'; export const avaxToken = { decimals: 18, name: 'Avalanche', symbol: 'AVAX' }; export const bnbToken = { decimals: 18, name: 'BNB', symbol: 'BNB' }; export const celoToken = { decimals: 18, name: 'CELO', symbol: 'CELO' }; -export const etherToken = { name: 'Ether', symbol: 'ETH', decimals: 18 }; -export const maticToken = { name: 'MATIC', symbol: 'MATIC', decimals: 18 }; -export const xDaiToken = { name: 'xDai', symbol: 'xDai', decimals: 18 }; -export const solToken = { name: 'Sol', symbol: 'SOL', decimals: 9 }; - -/** - * Metadata for Ethereum chains - */ +export const etherToken = { decimals: 18, name: 'Ether', symbol: 'ETH' }; +export const maticToken = { decimals: 18, name: 'MATIC', symbol: 'MATIC' }; +export const xDaiToken = { decimals: 18, name: 'xDai', symbol: 'xDai' }; +export const solToken = { decimals: 9, name: 'Sol', symbol: 'SOL' }; export const alfajores: ChainMetadata = { - chainId: 44787, - domainId: 44787, - name: Chains.alfajores, - protocol: ProtocolType.Ethereum, - displayName: 'Alfajores', - nativeToken: celoToken, - rpcUrls: [{ http: 'https://alfajores-forno.celo-testnet.org' }], blockExplorers: [ { - name: 'CeloScan', - url: 'https://alfajores.celoscan.io', apiUrl: 'https://api-alfajores.celoscan.io/api', family: ExplorerFamily.Etherscan, + name: 'CeloScan', + url: 'https://alfajores.celoscan.io', }, { - name: 'Blockscout', - url: 'https://explorer.celo.org/alfajores', apiUrl: 'https://explorer.celo.org/alfajores/api', family: ExplorerFamily.Blockscout, + name: 'Blockscout', + url: 'https://explorer.celo.org/alfajores', }, ], blocks: { confirmations: 1, - reorgPeriod: 0, estimateBlockTime: 5, + reorgPeriod: 0, }, + chainId: 44787, + displayName: 'Alfajores', + domainId: 44787, isTestnet: true, + name: Chains.alfajores, + nativeToken: celoToken, + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'https://alfajores-forno.celo-testnet.org' }], }; export const arbitrum: ChainMetadata = { - chainId: 42161, - domainId: 42161, - name: Chains.arbitrum, - protocol: ProtocolType.Ethereum, - displayName: 'Arbitrum', - nativeToken: etherToken, - rpcUrls: [{ http: 'https://arb1.arbitrum.io/rpc' }], blockExplorers: [ { - name: 'Arbiscan', - url: 'https://arbiscan.io', apiUrl: 'https://api.arbiscan.io/api', family: ExplorerFamily.Etherscan, + name: 'Arbiscan', + url: 'https://arbiscan.io', }, ], blocks: { confirmations: 1, - reorgPeriod: 0, estimateBlockTime: 3, + reorgPeriod: 0, }, - gasCurrencyCoinGeckoId: 'ethereum', // ETH is used for gas + chainId: 42161, + displayName: 'Arbitrum', + domainId: 42161, + gasCurrencyCoinGeckoId: 'ethereum', + // ETH is used for gas gnosisSafeTransactionServiceUrl: 'https://safe-transaction-arbitrum.safe.global/', + name: Chains.arbitrum, + nativeToken: etherToken, + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'https://arb1.arbitrum.io/rpc' }], }; export const arbitrumgoerli: ChainMetadata = { - chainId: 421613, - domainId: 421613, - name: Chains.arbitrumgoerli, - protocol: ProtocolType.Ethereum, - displayName: 'Arbitrum Goerli', - displayNameShort: 'Arb. Goerli', - nativeToken: etherToken, - rpcUrls: [{ http: 'https://goerli-rollup.arbitrum.io/rpc' }], blockExplorers: [ { - name: 'Arbiscan', - url: 'https://goerli.arbiscan.io', apiUrl: 'https://api-goerli.arbiscan.io/api', family: ExplorerFamily.Etherscan, + name: 'Arbiscan', + url: 'https://goerli.arbiscan.io', }, ], blocks: { confirmations: 1, - reorgPeriod: 1, estimateBlockTime: 3, + reorgPeriod: 1, }, + chainId: 421613, + displayName: 'Arbitrum Goerli', + displayNameShort: 'Arb. Goerli', + domainId: 421613, isTestnet: true, + name: Chains.arbitrumgoerli, + nativeToken: etherToken, + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'https://goerli-rollup.arbitrum.io/rpc' }], }; export const avalanche: ChainMetadata = { + blockExplorers: [ + { + apiUrl: 'https://api.snowtrace.io/api', + family: ExplorerFamily.Other, + name: 'SnowTrace', + url: 'https://snowtrace.io', + }, + ], + blocks: { + confirmations: 3, + estimateBlockTime: 2, + reorgPeriod: 3, + }, chainId: 43114, + displayName: 'Avalanche', domainId: 43114, + gasCurrencyCoinGeckoId: 'avalanche-2', + gnosisSafeTransactionServiceUrl: + 'https://safe-transaction-avalanche.safe.global/', name: Chains.avalanche, - protocol: ProtocolType.Ethereum, - displayName: 'Avalanche', nativeToken: avaxToken, + protocol: ProtocolType.Ethereum, rpcUrls: [ { http: 'https://api.avax.network/ext/bc/C/rpc', @@ -117,453 +130,431 @@ export const avalanche: ChainMetadata = { }, }, ], +}; + +export const base: ChainMetadata = { blockExplorers: [ { - name: 'SnowTrace', - url: 'https://snowtrace.io', - apiUrl: 'https://api.snowtrace.io/api', - family: ExplorerFamily.Other, + apiUrl: 'https://api.basescan.org/api', + family: ExplorerFamily.Etherscan, + name: 'BaseScan', + url: 'https://basescan.org', }, ], + // ETH is used for gas blocks: { - confirmations: 3, - reorgPeriod: 3, + confirmations: 1, estimateBlockTime: 2, + reorgPeriod: 1, }, - gasCurrencyCoinGeckoId: 'avalanche-2', - gnosisSafeTransactionServiceUrl: - 'https://safe-transaction-avalanche.safe.global/', -}; - -export const base: ChainMetadata = { chainId: 8453, + displayName: 'Base', domainId: 8453, + gasCurrencyCoinGeckoId: 'ethereum', + gnosisSafeTransactionServiceUrl: 'https://safe-transaction-base.safe.global/', name: Chains.base, - protocol: ProtocolType.Ethereum, - displayName: 'Base', nativeToken: etherToken, + protocol: ProtocolType.Ethereum, rpcUrls: [ { http: 'https://base.publicnode.com/' }, { http: 'https://mainnet.base.org' }, { http: 'https://base.blockpi.network/v1/rpc/public' }, ], +}; + +export const basegoerli: ChainMetadata = { blockExplorers: [ { - name: 'BaseScan', - url: 'https://basescan.org', - apiUrl: 'https://api.basescan.org/api', + apiUrl: 'https://api-goerli.basescan.org/api', family: ExplorerFamily.Etherscan, + name: 'BaseScan', + url: 'https://goerli.basescan.org', }, ], - gasCurrencyCoinGeckoId: 'ethereum', // ETH is used for gas blocks: { confirmations: 1, + estimateBlockTime: 3, reorgPeriod: 1, - estimateBlockTime: 2, }, - gnosisSafeTransactionServiceUrl: 'https://safe-transaction-base.safe.global/', -}; - -export const basegoerli: ChainMetadata = { chainId: 84531, + displayName: 'Base Goerli', domainId: 84531, + isTestnet: true, name: Chains.basegoerli, - protocol: ProtocolType.Ethereum, - displayName: 'Base Goerli', nativeToken: etherToken, + protocol: ProtocolType.Ethereum, rpcUrls: [ { http: 'https://base-goerli.publicnode.com' }, { http: 'https://goerli.base.org' }, ], +}; + +export const bsc: ChainMetadata = { blockExplorers: [ { - name: 'BaseScan', - url: 'https://goerli.basescan.org', - apiUrl: 'https://api-goerli.basescan.org/api', + apiUrl: 'https://api.bscscan.com/api', family: ExplorerFamily.Etherscan, + name: 'BscScan', + url: 'https://bscscan.com', }, ], blocks: { confirmations: 1, - reorgPeriod: 1, estimateBlockTime: 3, + reorgPeriod: 15, }, - isTestnet: true, -}; - -export const bsc: ChainMetadata = { chainId: 56, - domainId: 56, - name: Chains.bsc, - protocol: ProtocolType.Ethereum, displayName: 'Binance Smart Chain', displayNameShort: 'Binance', + domainId: 56, + gasCurrencyCoinGeckoId: 'binancecoin', + gnosisSafeTransactionServiceUrl: 'https://safe-transaction-bsc.safe.global/', + name: Chains.bsc, nativeToken: bnbToken, - rpcUrls: [ - { http: 'https://bsc-dataseed.binance.org' }, - { http: 'https://rpc.ankr.com/bsc' }, - ], + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'https://rpc.ankr.com/bsc' }], +}; + +export const bsctestnet: ChainMetadata = { blockExplorers: [ { - name: 'BscScan', - url: 'https://bscscan.com', - apiUrl: 'https://api.bscscan.com/api', + apiUrl: 'https://api-testnet.bscscan.com/api', family: ExplorerFamily.Etherscan, + name: 'BscScan', + url: 'https://testnet.bscscan.com', }, ], blocks: { confirmations: 1, - reorgPeriod: 15, estimateBlockTime: 3, + reorgPeriod: 9, }, - gasCurrencyCoinGeckoId: 'binancecoin', - gnosisSafeTransactionServiceUrl: 'https://safe-transaction-bsc.safe.global/', -}; - -export const bsctestnet: ChainMetadata = { chainId: 97, + displayName: 'BSC Testnet', domainId: 97, + isTestnet: true, name: Chains.bsctestnet, - protocol: ProtocolType.Ethereum, - displayName: 'BSC Testnet', nativeToken: bnbToken, + protocol: ProtocolType.Ethereum, rpcUrls: [ { http: 'https://bsc-testnet.publicnode.com' }, - { http: 'https://bsc-testnet.public.blastapi.io' }, { http: 'https://bsc-testnet.blockpi.network/v1/rpc/public' }, ], +}; + +export const celo: ChainMetadata = { blockExplorers: [ { - name: 'BscScan', - url: 'https://testnet.bscscan.com', - apiUrl: 'https://api-testnet.bscscan.com/api', + apiUrl: 'https://api.celoscan.io/api', family: ExplorerFamily.Etherscan, + name: 'CeloScan', + url: 'https://celoscan.io', + }, + { + apiUrl: 'https://explorer.celo.org/mainnet/api', + family: ExplorerFamily.Blockscout, + name: 'Blockscout', + url: 'https://explorer.celo.org', }, ], blocks: { confirmations: 1, - reorgPeriod: 9, - estimateBlockTime: 3, + estimateBlockTime: 5, + reorgPeriod: 0, }, - isTestnet: true, + chainId: 42220, + displayName: 'Celo', + domainId: 42220, + // The official Gnosis safe URL `https://safe-transaction-celo.safe.global` doesn't work well + // with delegates on a multisig created with the old unofficial Celo tooling. + gnosisSafeTransactionServiceUrl: + 'https://mainnet-tx-svc.celo-safe-prod.celo-networks-dev.org/', + name: Chains.celo, + nativeToken: celoToken, + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'https://forno.celo.org' }], }; export const chiado: ChainMetadata = { - chainId: 10200, - domainId: 10200, - name: Chains.chiado, - protocol: ProtocolType.Ethereum, - displayName: 'Chiado', - nativeToken: xDaiToken, - rpcUrls: [{ http: 'https://gnosis-chiado.publicnode.com' }], blockExplorers: [ { - name: 'GnosisScan', - url: 'https://gnosis-chiado.blockscout.com', apiUrl: 'https://gnosis-chiado.blockscout.com/api', family: ExplorerFamily.Blockscout, + name: 'GnosisScan', + url: 'https://gnosis-chiado.blockscout.com', }, ], blocks: { confirmations: 1, - reorgPeriod: 14, estimateBlockTime: 5, + reorgPeriod: 14, }, + chainId: 10200, + displayName: 'Chiado', + domainId: 10200, isTestnet: true, + name: Chains.chiado, + nativeToken: xDaiToken, + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'https://gnosis-chiado.publicnode.com' }], }; -export const celo: ChainMetadata = { - chainId: 42220, - domainId: 42220, - name: Chains.celo, - protocol: ProtocolType.Ethereum, - displayName: 'Celo', - nativeToken: celoToken, - rpcUrls: [{ http: 'https://forno.celo.org' }], +export const ethereum: ChainMetadata = { blockExplorers: [ { - name: 'CeloScan', - url: 'https://celoscan.io', - apiUrl: 'https://api.celoscan.io/api', + apiUrl: 'https://api.etherscan.io/api', family: ExplorerFamily.Etherscan, + name: 'Etherscan', + url: 'https://etherscan.io', }, { - name: 'Blockscout', - url: 'https://explorer.celo.org', - apiUrl: 'https://explorer.celo.org/mainnet/api', + apiUrl: 'https://blockscout.com/eth/mainnet/api', family: ExplorerFamily.Blockscout, + name: 'Blockscout', + url: 'https://blockscout.com/eth/mainnet', }, ], blocks: { - confirmations: 1, - reorgPeriod: 0, - estimateBlockTime: 5, + confirmations: 7, + estimateBlockTime: 13, + reorgPeriod: 14, }, - // The official Gnosis safe URL `https://safe-transaction-celo.safe.global` doesn't work well - // with delegates on a multisig created with the old unofficial Celo tooling. - gnosisSafeTransactionServiceUrl: - 'https://mainnet-tx-svc.celo-safe-prod.celo-networks-dev.org/', -}; - -export const ethereum: ChainMetadata = { chainId: 1, + displayName: 'Ethereum', domainId: 1, + gnosisSafeTransactionServiceUrl: + 'https://safe-transaction-mainnet.safe.global/', name: Chains.ethereum, - protocol: ProtocolType.Ethereum, - displayName: 'Ethereum', nativeToken: etherToken, + protocol: ProtocolType.Ethereum, rpcUrls: [ { http: 'https://mainnet.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161' }, { http: 'https://cloudflare-eth.com' }, ], +}; + +export const fuji: ChainMetadata = { blockExplorers: [ { - name: 'Etherscan', - url: 'https://etherscan.io', - apiUrl: 'https://api.etherscan.io/api', + apiUrl: 'https://api-testnet.snowtrace.io/api', family: ExplorerFamily.Etherscan, - }, - { - name: 'Blockscout', - url: 'https://blockscout.com/eth/mainnet', - apiUrl: 'https://blockscout.com/eth/mainnet/api', - family: ExplorerFamily.Blockscout, + name: 'SnowTrace', + url: 'https://testnet.snowtrace.io', }, ], blocks: { - confirmations: 7, - reorgPeriod: 14, - estimateBlockTime: 13, + confirmations: 3, + estimateBlockTime: 2, + reorgPeriod: 3, }, - gnosisSafeTransactionServiceUrl: - 'https://safe-transaction-mainnet.safe.global/', -}; - -export const fuji: ChainMetadata = { chainId: 43113, + displayName: 'Fuji', domainId: 43113, + isTestnet: true, name: Chains.fuji, - protocol: ProtocolType.Ethereum, - displayName: 'Fuji', nativeToken: avaxToken, + protocol: ProtocolType.Ethereum, rpcUrls: [ { http: 'https://api.avax-test.network/ext/bc/C/rpc', pagination: { maxBlockRange: 2048 }, }, ], +}; + +export const goerli: ChainMetadata = { blockExplorers: [ { - name: 'SnowTrace', - url: 'https://testnet.snowtrace.io', - apiUrl: 'https://api-testnet.snowtrace.io/api', + apiUrl: 'https://api-goerli.etherscan.io/api', family: ExplorerFamily.Etherscan, + name: 'Etherscan', + url: 'https://goerli.etherscan.io', }, ], blocks: { - confirmations: 3, - reorgPeriod: 3, - estimateBlockTime: 2, + confirmations: 1, + estimateBlockTime: 13, + reorgPeriod: 2, }, - isTestnet: true, -}; - -export const goerli: ChainMetadata = { chainId: 5, + displayName: 'Goerli', domainId: 5, + isTestnet: true, name: Chains.goerli, - protocol: ProtocolType.Ethereum, - displayName: 'Goerli', nativeToken: etherToken, + protocol: ProtocolType.Ethereum, rpcUrls: [ { http: 'https://goerli.infura.io/v3/9aa3d95b3bc440fa88ea12eaa4456161' }, { http: 'https://rpc.ankr.com/eth_goerli' }, - { http: 'https://eth-goerli.public.blastapi.io' }, ], +}; + +export const gnosis: ChainMetadata = { blockExplorers: [ { - name: 'Etherscan', - url: 'https://goerli.etherscan.io', - apiUrl: 'https://api-goerli.etherscan.io/api', + apiUrl: 'https://api.gnosisscan.io/api', family: ExplorerFamily.Etherscan, + name: 'GnosisScan', + url: 'https://gnosisscan.io', }, ], blocks: { confirmations: 1, - reorgPeriod: 2, - estimateBlockTime: 13, + estimateBlockTime: 5, + reorgPeriod: 14, }, - isTestnet: true, -}; - -export const lineagoerli: ChainMetadata = { - chainId: 59140, - domainId: 59140, - name: Chains.lineagoerli, + chainId: 100, + displayName: 'Gnosis', + domainId: 100, + gasCurrencyCoinGeckoId: 'xdai', + gnosisSafeTransactionServiceUrl: + 'https://safe-transaction-gnosis-chain.safe.global/', + name: Chains.gnosis, + nativeToken: xDaiToken, protocol: ProtocolType.Ethereum, - displayName: 'Linea Goerli', - nativeToken: etherToken, - rpcUrls: [{ http: 'https://rpc.goerli.linea.build' }], - blockExplorers: [ + rpcUrls: [ { - name: 'Linea Explorer', - url: 'https://explorer.goerli.linea.build/', - apiUrl: 'https://explorer.goerli.linea.build/api', - family: ExplorerFamily.Blockscout, + http: 'https://rpc.gnosischain.com', + pagination: { + maxBlockRange: 10000, + minBlockNumber: 25997478, + }, }, ], - blocks: { - confirmations: 1, - reorgPeriod: 2, - estimateBlockTime: 12, - }, - isTestnet: true, }; -export const sepolia: ChainMetadata = { - chainId: 11155111, - domainId: 11155111, - name: Chains.sepolia, - protocol: ProtocolType.Ethereum, - displayName: 'Sepolia', - nativeToken: etherToken, - rpcUrls: [ - { http: 'https://ethereum-sepolia.blockpi.network/v1/rpc/public' }, - { http: 'https://eth-sepolia.g.alchemy.com/v2/demo' }, - { http: 'https://rpc.sepolia.org' }, - ], +export const lineagoerli: ChainMetadata = { blockExplorers: [ { - name: 'Etherscan', - url: 'https://sepolia.etherscan.io', - apiUrl: 'https://api-sepolia.etherscan.io/api', - family: ExplorerFamily.Etherscan, + apiUrl: 'https://explorer.goerli.linea.build/api', + family: ExplorerFamily.Blockscout, + name: 'Linea Explorer', + url: 'https://explorer.goerli.linea.build/', }, ], blocks: { confirmations: 1, + estimateBlockTime: 12, reorgPeriod: 2, - estimateBlockTime: 13, }, + chainId: 59140, + displayName: 'Linea Goerli', + domainId: 59140, isTestnet: true, + name: Chains.lineagoerli, + nativeToken: etherToken, + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'https://rpc.goerli.linea.build' }], }; -export const scroll: ChainMetadata = { - chainId: 534352, - domainId: 534352, - name: Chains.scroll, - protocol: ProtocolType.Ethereum, - displayName: 'Scroll', - nativeToken: etherToken, - rpcUrls: [ - { http: 'https://scroll.blockpi.network/v1/rpc/public' }, - { http: 'https://scroll-mainnet.public.blastapi.io' }, - ], +export const mantapacific: ChainMetadata = { blockExplorers: [ { - name: 'Scroll Explorer', - url: 'https://scrollscan.com/', - apiUrl: 'https://api.scrollscan.com/api', - family: ExplorerFamily.Etherscan, + apiUrl: 'https://pacific-explorer.manta.network/api', + family: ExplorerFamily.Blockscout, + name: 'Manta Pacific Explorer', + url: 'https://pacific-explorer.manta.network', }, ], - gasCurrencyCoinGeckoId: 'ethereum', // ETH is used for gas blocks: { confirmations: 1, - reorgPeriod: 1, estimateBlockTime: 3, + reorgPeriod: 0, + }, + chainId: 169, + displayName: 'Manta Pacific', + displayNameShort: 'Manta', + domainId: 169, + isTestnet: false, + name: Chains.mantapacific, + nativeToken: { + decimals: 18, + name: 'Ether', + symbol: 'ETH', }, + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'https://pacific-rpc.manta.network/http' }], }; -export const scrollsepolia: ChainMetadata = { - chainId: 534351, - domainId: 534351, - name: Chains.scrollsepolia, - protocol: ProtocolType.Ethereum, - displayName: 'Scroll Sepolia', - nativeToken: etherToken, - rpcUrls: [{ http: 'https://sepolia-rpc.scroll.io' }], +export const moonbasealpha: ChainMetadata = { blockExplorers: [ { - name: 'Scroll Explorer', - url: 'https://sepolia.scrollscan.dev/', - apiUrl: 'https://api-sepolia.scrollscan.com/api', + apiUrl: 'https://api-moonbase.moonscan.io/api', family: ExplorerFamily.Etherscan, + name: 'MoonScan', + url: 'https://moonbase.moonscan.io', }, ], blocks: { confirmations: 1, + estimateBlockTime: 12, reorgPeriod: 1, - estimateBlockTime: 3, }, - isTestnet: true, -}; - -export const moonbasealpha: ChainMetadata = { chainId: 1287, - domainId: 1287, - name: Chains.moonbasealpha, - protocol: ProtocolType.Ethereum, displayName: 'Moonbase Alpha', displayNameShort: 'Moonbase', + domainId: 1287, + isTestnet: true, + name: Chains.moonbasealpha, nativeToken: { decimals: 18, name: 'DEV', symbol: 'DEV', }, + protocol: ProtocolType.Ethereum, rpcUrls: [{ http: 'https://rpc.api.moonbase.moonbeam.network' }], +}; + +export const moonbeam: ChainMetadata = { blockExplorers: [ { - name: 'MoonScan', - url: 'https://moonbase.moonscan.io', - apiUrl: 'https://api-moonbase.moonscan.io/api', + apiUrl: 'https://api-moonbeam.moonscan.io/api', family: ExplorerFamily.Etherscan, + name: 'MoonScan', + url: 'https://moonscan.io', }, ], blocks: { - confirmations: 1, - reorgPeriod: 1, + confirmations: 2, estimateBlockTime: 12, + reorgPeriod: 2, }, - isTestnet: true, -}; - -export const moonbeam: ChainMetadata = { chainId: 1284, + displayName: 'Moonbeam', domainId: 1284, + gnosisSafeTransactionServiceUrl: + 'https://transaction.multisig.moonbeam.network', name: Chains.moonbeam, - protocol: ProtocolType.Ethereum, - displayName: 'Moonbeam', nativeToken: { decimals: 18, name: 'GLMR', symbol: 'GLMR', }, + protocol: ProtocolType.Ethereum, rpcUrls: [{ http: 'https://rpc.api.moonbeam.network' }], +}; + +export const mumbai: ChainMetadata = { blockExplorers: [ { - name: 'MoonScan', - url: 'https://moonscan.io', - apiUrl: 'https://api-moonbeam.moonscan.io/api', + apiUrl: 'https://api-testnet.polygonscan.com/api', family: ExplorerFamily.Etherscan, + name: 'PolygonScan', + url: 'https://mumbai.polygonscan.com', }, ], blocks: { - confirmations: 2, - reorgPeriod: 2, - estimateBlockTime: 12, + confirmations: 3, + estimateBlockTime: 5, + reorgPeriod: 32, }, - gnosisSafeTransactionServiceUrl: - 'https://transaction.multisig.moonbeam.network', -}; - -export const mumbai: ChainMetadata = { chainId: 80001, + displayName: 'Mumbai', domainId: 80001, + isTestnet: true, name: Chains.mumbai, - protocol: ProtocolType.Ethereum, - displayName: 'Mumbai', nativeToken: maticToken, + protocol: ProtocolType.Ethereum, rpcUrls: [ { http: 'https://rpc.ankr.com/polygon_mumbai', @@ -573,468 +564,426 @@ export const mumbai: ChainMetadata = { minBlockNumber: 22900000, }, }, + ], +}; + +export const nautilus: ChainMetadata = { + blocks: { + confirmations: 1, + estimateBlockTime: 1, + reorgPeriod: 1, + }, + chainId: 22222, + displayName: 'Nautilus', + domainId: 22222, + name: Chains.nautilus, + nativeToken: { + decimals: 18, + name: 'Zebec', + symbol: 'ZBC', + }, + protocol: ProtocolType.Ethereum, + rpcUrls: [ { - http: 'https://matic-mumbai.chainstacklabs.com', + http: 'https://api.nautilus.nautchain.xyz', }, ], +}; + +export const neutron: ChainMetadata = { + bech32Prefix: 'neutron', blockExplorers: [ { - name: 'PolygonScan', - url: 'https://mumbai.polygonscan.com', - apiUrl: 'https://api-testnet.polygonscan.com/api', - family: ExplorerFamily.Etherscan, + // TODO API not actually supported, using url to meet validation requirements + apiUrl: 'https://www.mintscan.io/neutron', + family: ExplorerFamily.Other, + name: 'Mintscan', + url: 'https://www.mintscan.io/neutron', }, ], blocks: { - confirmations: 3, - reorgPeriod: 32, - estimateBlockTime: 5, + confirmations: 1, + estimateBlockTime: 3, + reorgPeriod: 1, }, - isTestnet: true, + chainId: 'neutron-1', + displayName: 'Neutron', + domainId: 1853125230, + isTestnet: false, + name: Chains.neutron, + nativeToken: { + decimals: 6, + name: 'Neutron', + symbol: 'NTRN', + }, + protocol: ProtocolType.Cosmos, + rpcUrls: [{ http: 'https://rpc-kralum.neutron-1.neutron.org' }], + slip44: 118, }; export const optimism: ChainMetadata = { - chainId: 10, - domainId: 10, - name: Chains.optimism, - protocol: ProtocolType.Ethereum, - displayName: 'Optimism', - nativeToken: etherToken, - rpcUrls: [{ http: 'https://mainnet.optimism.io' }], blockExplorers: [ { - name: 'Etherscan', - url: 'https://optimistic.etherscan.io', apiUrl: 'https://api-optimistic.etherscan.io/api', family: ExplorerFamily.Etherscan, + name: 'Etherscan', + url: 'https://optimistic.etherscan.io', }, ], blocks: { confirmations: 1, - reorgPeriod: 0, estimateBlockTime: 3, + reorgPeriod: 0, }, - gasCurrencyCoinGeckoId: 'ethereum', // ETH is used for gas + chainId: 10, + displayName: 'Optimism', + domainId: 10, + gasCurrencyCoinGeckoId: 'ethereum', + // ETH is used for gas gnosisSafeTransactionServiceUrl: 'https://safe-transaction-optimism.safe.global/', + name: Chains.optimism, + nativeToken: etherToken, + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'https://mainnet.optimism.io' }], }; export const optimismgoerli: ChainMetadata = { - chainId: 420, - domainId: 420, - name: Chains.optimismgoerli, - protocol: ProtocolType.Ethereum, - displayName: 'Optimism Goerli', - displayNameShort: 'Opt. Goerli', - nativeToken: etherToken, - rpcUrls: [{ http: 'https://goerli.optimism.io' }], blockExplorers: [ { - name: 'Etherscan', - url: 'https://goerli-optimism.etherscan.io', apiUrl: 'https://api-goerli-optimism.etherscan.io/api', family: ExplorerFamily.Etherscan, + name: 'Etherscan', + url: 'https://goerli-optimism.etherscan.io', }, ], blocks: { confirmations: 1, - reorgPeriod: 1, estimateBlockTime: 3, + reorgPeriod: 1, }, + chainId: 420, + displayName: 'Optimism Goerli', + displayNameShort: 'Opt. Goerli', + domainId: 420, isTestnet: true, + name: Chains.optimismgoerli, + nativeToken: etherToken, + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'https://goerli.optimism.io' }], }; export const polygon: ChainMetadata = { - chainId: 137, - domainId: 137, - name: Chains.polygon, - protocol: ProtocolType.Ethereum, - displayName: 'Polygon', - nativeToken: etherToken, - rpcUrls: [ - { - http: 'https://rpc-mainnet.matic.quiknode.pro', - pagination: { - // Needs to be low to avoid RPC timeouts - maxBlockRange: 10000, - minBlockNumber: 19657100, - }, - }, - { http: 'https://polygon-rpc.com' }, - ], blockExplorers: [ { - name: 'PolygonScan', - url: 'https://polygonscan.com', apiUrl: 'https://api.polygonscan.com/api', family: ExplorerFamily.Etherscan, + name: 'PolygonScan', + url: 'https://polygonscan.com', }, ], blocks: { confirmations: 200, - reorgPeriod: 256, estimateBlockTime: 2, + reorgPeriod: 256, }, + chainId: 137, + displayName: 'Polygon', + domainId: 137, gasCurrencyCoinGeckoId: 'matic-network', gnosisSafeTransactionServiceUrl: 'https://safe-transaction-polygon.safe.global/', -}; - -export const gnosis: ChainMetadata = { - chainId: 100, - domainId: 100, - name: Chains.gnosis, + name: Chains.polygon, + nativeToken: etherToken, protocol: ProtocolType.Ethereum, - displayName: 'Gnosis', - nativeToken: xDaiToken, rpcUrls: [ { - http: 'https://rpc.gnosischain.com', + http: 'https://rpc-mainnet.matic.quiknode.pro', pagination: { + // Needs to be low to avoid RPC timeouts maxBlockRange: 10000, - minBlockNumber: 25997478, + minBlockNumber: 19657100, }, }, + { http: 'https://polygon-rpc.com' }, ], +}; + +export const polygonzkevm: ChainMetadata = { blockExplorers: [ { - name: 'GnosisScan', - url: 'https://gnosisscan.io', - apiUrl: 'https://api.gnosisscan.io/api', + apiUrl: 'https://api-zkevm.polygonscan.com/api', family: ExplorerFamily.Etherscan, + name: 'PolygonScan', + url: 'https://zkevm.polygonscan.com', }, ], + // ETH is used for gas blocks: { confirmations: 1, - reorgPeriod: 14, - estimateBlockTime: 5, + estimateBlockTime: 10, + reorgPeriod: 1, }, - gasCurrencyCoinGeckoId: 'xdai', - gnosisSafeTransactionServiceUrl: - 'https://safe-transaction-gnosis-chain.safe.global/', + chainId: 1101, + displayName: 'Polygon zkEVM', + displayNameShort: 'zkEVM', + domainId: 1101, + gasCurrencyCoinGeckoId: 'ethereum', + name: Chains.polygonzkevm, + nativeToken: etherToken, + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'https://rpc.ankr.com/polygon_zkevm' }], }; -// Testnet for Nautilus -export const proteustestnet: ChainMetadata = { - chainId: 88002, - domainId: 88002, - name: Chains.proteustestnet, - protocol: ProtocolType.Ethereum, - displayName: 'Proteus Testnet', - nativeToken: { - name: 'Zebec', - symbol: 'ZBC', - decimals: 18, - }, - rpcUrls: [ +export const polygonzkevmtestnet: ChainMetadata = { + blockExplorers: [ { - http: 'https://api.proteus.nautchain.xyz/solana', + apiUrl: 'https://api-testnet-zkevm.polygonscan.com/api', + family: ExplorerFamily.Etherscan, + name: 'PolygonScan', + url: 'https://testnet-zkevm.polygonscan.com', }, ], blocks: { confirmations: 1, + estimateBlockTime: 3, reorgPeriod: 1, - estimateBlockTime: 1, }, -}; - -export const mantapacific: ChainMetadata = { - chainId: 169, - domainId: 169, - name: Chains.mantapacific, + chainId: 1442, + displayName: 'Polygon zkEVM Testnet', + displayNameShort: 'ZkEvm Testnet', + domainId: 1442, + isTestnet: true, + name: Chains.polygonzkevmtestnet, + nativeToken: etherToken, protocol: ProtocolType.Ethereum, - displayName: 'Manta Pacific', - displayNameShort: 'Manta', - nativeToken: { - name: 'Ether', - symbol: 'ETH', - decimals: 18, - }, - blocks: { - confirmations: 1, - reorgPeriod: 0, - estimateBlockTime: 3, - }, - blockExplorers: [ - { - name: 'Manta Pacific Explorer', - url: 'https://pacific-explorer.manta.network/', - apiUrl: 'https://pacific-explorer.manta.network/api', - family: ExplorerFamily.Blockscout, - }, - ], - rpcUrls: [{ http: 'https://pacific-rpc.manta.network/http' }], - isTestnet: false, + rpcUrls: [{ http: 'https://rpc.public.zkevm-test.net' }], }; -export const nautilus: ChainMetadata = { - chainId: 22222, - domainId: 22222, - name: Chains.nautilus, - protocol: ProtocolType.Ethereum, - displayName: 'Nautilus', - nativeToken: { - name: 'Zebec', - symbol: 'ZBC', - decimals: 18, - }, - rpcUrls: [ - { - http: 'https://api.nautilus.nautchain.xyz', - }, - ], +// Testnet for Nautilus +export const proteustestnet: ChainMetadata = { blocks: { confirmations: 1, - reorgPeriod: 1, estimateBlockTime: 1, + reorgPeriod: 1, }, -}; - -export const neutron: ChainMetadata = { - chainId: 'neutron-1', - domainId: 1853125230, - name: Chains.neutron, - protocol: ProtocolType.Cosmos, - displayName: 'Neutron', - bech32Prefix: 'neutron', - slip44: 118, + chainId: 88002, + displayName: 'Proteus Testnet', + domainId: 88002, + name: Chains.proteustestnet, nativeToken: { - name: 'Neutron', - symbol: 'NTRN', - decimals: 6, + decimals: 18, + name: 'Zebec', + symbol: 'ZBC', }, + protocol: ProtocolType.Ethereum, rpcUrls: [ - { http: 'https://rpc-kralum.neutron-1.neutron.org' }, - { http: 'grpc-kralum.neutron-1.neutron.org:80' }, - ], - blocks: { - confirmations: 1, - reorgPeriod: 1, - estimateBlockTime: 3, - }, - blockExplorers: [ - { - name: 'Mintscan', - url: 'https://www.mintscan.io/neutron', - // TODO API not actually supported, using url to meet validation requirements - apiUrl: 'https://www.mintscan.io/neutron', - family: ExplorerFamily.Other, + { + http: 'https://api.proteus.nautchain.xyz/solana', }, ], - isTestnet: false, }; -/** - * Metadata for local test chains - */ - -export const test1: ChainMetadata = { - chainId: 13371, - domainId: 13371, - name: Chains.test1, - protocol: ProtocolType.Ethereum, - displayName: 'Test 1', - nativeToken: etherToken, - rpcUrls: [{ http: 'http://127.0.0.1:8545' }], - blockExplorers: [], +export const scroll: ChainMetadata = { + blockExplorers: [ + { + apiUrl: 'https://api.scrollscan.com/api', + family: ExplorerFamily.Etherscan, + name: 'Scroll Explorer', + url: 'https://scrollscan.com/', + }, + ], + // ETH is used for gas blocks: { confirmations: 1, - reorgPeriod: 0, estimateBlockTime: 3, + reorgPeriod: 1, }, - isTestnet: true, + chainId: 534352, + displayName: 'Scroll', + domainId: 534352, + gasCurrencyCoinGeckoId: 'ethereum', + name: Chains.scroll, + nativeToken: etherToken, + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'https://scroll.blockpi.network/v1/rpc/public' }], }; -export const test2: ChainMetadata = { - chainId: 13372, - domainId: 13372, - name: Chains.test2, - protocol: ProtocolType.Ethereum, - displayName: 'Test 2', - nativeToken: etherToken, - rpcUrls: [{ http: 'http://127.0.0.1:8545' }], - blockExplorers: [], +export const scrollsepolia: ChainMetadata = { + blockExplorers: [ + { + apiUrl: 'https://api-sepolia.scrollscan.com/api', + family: ExplorerFamily.Etherscan, + name: 'Scroll Explorer', + url: 'https://sepolia.scrollscan.dev/', + }, + ], blocks: { confirmations: 1, - reorgPeriod: 1, estimateBlockTime: 3, + reorgPeriod: 1, }, + chainId: 534351, + displayName: 'Scroll Sepolia', + domainId: 534351, isTestnet: true, + name: Chains.scrollsepolia, + nativeToken: etherToken, + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'https://sepolia-rpc.scroll.io' }], }; -export const test3: ChainMetadata = { - chainId: 13373, - domainId: 13373, - name: Chains.test3, - protocol: ProtocolType.Ethereum, - displayName: 'Test 3', - nativeToken: etherToken, - rpcUrls: [{ http: 'http://127.0.0.1:8545' }], - blockExplorers: [], +export const sepolia: ChainMetadata = { + blockExplorers: [ + { + apiUrl: 'https://api-sepolia.etherscan.io/api', + family: ExplorerFamily.Etherscan, + name: 'Etherscan', + url: 'https://sepolia.etherscan.io', + }, + ], blocks: { confirmations: 1, + estimateBlockTime: 13, reorgPeriod: 2, - estimateBlockTime: 3, }, + chainId: 11155111, + displayName: 'Sepolia', + domainId: 11155111, isTestnet: true, + name: Chains.sepolia, + nativeToken: etherToken, + protocol: ProtocolType.Ethereum, + rpcUrls: [ + { http: 'https://ethereum-sepolia.blockpi.network/v1/rpc/public' }, + { http: 'https://rpc.sepolia.org' }, + ], }; -/** - * Metadata for Sealevel chains - */ - export const solana: ChainMetadata = { - protocol: ProtocolType.Sealevel, - // Uses the same ChainId as https://www.alchemy.com/chain-connect/chain/solana - chainId: 1399811149, - domainId: 1399811149, - name: 'solana', - displayName: 'Solana', - nativeToken: solToken, - rpcUrls: [{ http: 'https://api.mainnet-beta.solana.com' }], blockExplorers: [ { - name: 'Solana Explorer', - url: 'https://explorer.solana.com', apiUrl: 'https://explorer.solana.com', family: ExplorerFamily.Other, + name: 'Solana Explorer', + url: 'https://explorer.solana.com', }, ], + blocks: { confirmations: 1, - reorgPeriod: 0, estimateBlockTime: 0.4, + reorgPeriod: 0, }, + // Uses the same ChainId as https://www.alchemy.com/chain-connect/chain/solana + chainId: 1399811149, + displayName: 'Solana', + domainId: 1399811149, + name: 'solana', + nativeToken: solToken, + protocol: ProtocolType.Sealevel, + rpcUrls: [{ http: 'https://api.mainnet-beta.solana.com' }], }; export const solanatestnet: ChainMetadata = { - protocol: ProtocolType.Sealevel, - chainId: 1399811150, - domainId: 1399811150, - name: 'solanatestnet', - displayName: 'Solana Testnet', - displayNameShort: 'Sol Testnet', - nativeToken: solToken, - rpcUrls: [{ http: 'https://api.testnet.solana.com' }], blockExplorers: [ { - name: 'Solana Explorer', - url: 'https://explorer.solana.com', apiUrl: 'https://explorer.solana.com', family: ExplorerFamily.Other, + name: 'Solana Explorer', + url: 'https://explorer.solana.com', }, ], blocks: { confirmations: 1, - reorgPeriod: 0, estimateBlockTime: 0.4, + reorgPeriod: 0, }, + chainId: 1399811150, + displayName: 'Solana Testnet', + displayNameShort: 'Sol Testnet', + domainId: 1399811150, isTestnet: true, + name: 'solanatestnet', + nativeToken: solToken, + protocol: ProtocolType.Sealevel, + rpcUrls: [{ http: 'https://api.testnet.solana.com' }], }; export const solanadevnet: ChainMetadata = { - protocol: ProtocolType.Sealevel, - chainId: 1399811151, - domainId: 1399811151, - name: 'solanadevnet', - displayName: 'Solana Devnet', - displayNameShort: 'Sol Devnet', - nativeToken: solToken, - rpcUrls: [{ http: 'https://api.devnet.solana.com' }], blockExplorers: [ { - name: 'Solana Explorer', - url: 'https://explorer.solana.com', apiUrl: 'https://explorer.solana.com', family: ExplorerFamily.Other, + name: 'Solana Explorer', + url: 'https://explorer.solana.com', }, ], blocks: { confirmations: 1, - reorgPeriod: 0, estimateBlockTime: 0.4, + reorgPeriod: 0, }, + chainId: 1399811151, + displayName: 'Solana Devnet', + displayNameShort: 'Sol Devnet', + domainId: 1399811151, isTestnet: true, + name: 'solanadevnet', + nativeToken: solToken, + protocol: ProtocolType.Sealevel, + rpcUrls: [{ http: 'https://api.devnet.solana.com' }], }; -export const polygonzkevmtestnet: ChainMetadata = { - protocol: ProtocolType.Ethereum, - chainId: 1442, - domainId: 1442, - name: Chains.polygonzkevmtestnet, - displayName: 'Polygon zkEVM Testnet', - displayNameShort: 'ZkEvm Testnet', - nativeToken: etherToken, - rpcUrls: [{ http: 'https://rpc.public.zkevm-test.net' }], - blockExplorers: [ - { - name: 'PolygonScan', - url: 'https://testnet-zkevm.polygonscan.com/', - apiUrl: 'https://api-testnet-zkevm.polygonscan.com/api', - family: ExplorerFamily.Etherscan, - }, - ], +export const test1: ChainMetadata = { + blockExplorers: [], blocks: { confirmations: 1, - reorgPeriod: 1, estimateBlockTime: 3, + reorgPeriod: 0, }, + chainId: 13371, + displayName: 'Test 1', + domainId: 13371, isTestnet: true, + name: Chains.test1, + nativeToken: etherToken, + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'http://127.0.0.1:8545' }], }; -export const polygonzkevm: ChainMetadata = { - protocol: ProtocolType.Ethereum, - chainId: 1101, - domainId: 1101, - name: Chains.polygonzkevm, - displayName: 'Polygon zkEVM', - displayNameShort: 'zkEVM', - nativeToken: etherToken, - rpcUrls: [ - { http: 'https://polygonzkevm-mainnet.g.alchemy.com/v2/demo' }, - { http: 'https://rpc.ankr.com/polygon_zkevm' }, - { http: 'https://zkevm.polygonscan.com/' }, - ], - blockExplorers: [ - { - name: 'PolygonScan', - url: 'https://zkevm.polygonscan.com/', - apiUrl: 'https://api-zkevm.polygonscan.com/api', - family: ExplorerFamily.Etherscan, - }, - ], - gasCurrencyCoinGeckoId: 'ethereum', // ETH is used for gas +export const test2: ChainMetadata = { + blockExplorers: [], blocks: { confirmations: 1, + estimateBlockTime: 3, reorgPeriod: 1, - estimateBlockTime: 10, }, + chainId: 13372, + displayName: 'Test 2', + domainId: 13372, + isTestnet: true, + name: Chains.test2, + nativeToken: etherToken, + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'http://127.0.0.1:8545' }], }; -export const neutrontestnet: ChainMetadata = { - protocol: ProtocolType.Cosmos, - domainId: 33333, - chainId: 'duality-devnet', - name: Chains.neutrontestnet, - displayName: 'Neutron Testnet', - nativeToken: { - name: 'Neutron', - symbol: 'NTRN', - decimals: 6, - }, +export const test3: ChainMetadata = { + blockExplorers: [], blocks: { confirmations: 1, - reorgPeriod: 1, estimateBlockTime: 3, + reorgPeriod: 2, }, - // First URL RPC, second REST - rpcUrls: [ - { http: 'http://54.149.31.83:26657' }, - { http: 'http://54.149.31.83:1317' }, - ], - bech32Prefix: 'dual', - slip44: 118, + chainId: 13373, + displayName: 'Test 3', + domainId: 13373, isTestnet: true, + name: Chains.test3, + nativeToken: etherToken, + protocol: ProtocolType.Ethereum, + rpcUrls: [{ http: 'http://127.0.0.1:8545' }], }; /** @@ -1052,39 +1001,38 @@ export const chainMetadata: ChainMap = { basegoerli, bsc, bsctestnet, - chiado, celo, + chiado, ethereum, fuji, + gnosis, goerli, lineagoerli, - scroll, - scrollsepolia, - sepolia, mantapacific, moonbasealpha, moonbeam, mumbai, + nautilus, neutron, optimism, optimismgoerli, polygon, polygonzkevm, polygonzkevmtestnet, - gnosis, proteustestnet, + scroll, + scrollsepolia, + sepolia, + solana, + solanadevnet, + solanatestnet, test1, test2, test3, - solana, - solanatestnet, - solanadevnet, - nautilus, - neutrontestnet, }; export const chainIdToMetadata = Object.values(chainMetadata).reduce< - ChainMap + Record >((result, chain) => { result[chain.chainId] = chain; return result; @@ -1099,6 +1047,6 @@ export const testnetChainsMetadata: Array = Testnets.map( export const solanaChainToClusterName: ChainMap = { solana: 'mainnet-beta', - solanatestnet: 'testnet', solanadevnet: 'devnet', + solanatestnet: 'testnet', }; diff --git a/typescript/sdk/src/consts/chains.ts b/typescript/sdk/src/consts/chains.ts index 65bd49820e..7139c057af 100644 --- a/typescript/sdk/src/consts/chains.ts +++ b/typescript/sdk/src/consts/chains.ts @@ -18,9 +18,6 @@ export enum Chains { gnosis = 'gnosis', goerli = 'goerli', lineagoerli = 'lineagoerli', - scroll = 'scroll', - scrollsepolia = 'scrollsepolia', - sepolia = 'sepolia', mantapacific = 'mantapacific', moonbasealpha = 'moonbasealpha', moonbeam = 'moonbeam', @@ -33,9 +30,11 @@ export enum Chains { polygonzkevm = 'polygonzkevm', polygonzkevmtestnet = 'polygonzkevmtestnet', proteustestnet = 'proteustestnet', + scroll = 'scroll', + scrollsepolia = 'scrollsepolia', + sepolia = 'sepolia', solana = 'solana', solanadevnet = 'solanadevnet', - neutrontestnet = 'neutrontestnet', test1 = 'test1', test2 = 'test2', test3 = 'test3', @@ -50,6 +49,7 @@ export enum DeprecatedChains { rinkeby = 'rinkeby', optimismkovan = 'optimismkovan', optimismrinkeby = 'optimismrinkeby', + neutrontestnet = 'neutrontestnet', } export const AllDeprecatedChains = Object.keys(DeprecatedChains) as string[]; diff --git a/typescript/sdk/src/consts/environments/index.ts b/typescript/sdk/src/consts/environments/index.ts index a1c6d8b525..96a308cbda 100644 --- a/typescript/sdk/src/consts/environments/index.ts +++ b/typescript/sdk/src/consts/environments/index.ts @@ -6,7 +6,7 @@ import { CoreChainName } from '../chains'; import mainnet from './mainnet.json'; import testnet from './testnet.json'; -export const hyperlaneEnvironments = { testnet, mainnet }; +export const hyperlaneEnvironments = { mainnet, testnet }; export type HyperlaneEnvironment = keyof typeof hyperlaneEnvironments; export type HyperlaneEnvironmentChain = Extract< diff --git a/typescript/sdk/src/consts/multisigIsm.ts b/typescript/sdk/src/consts/multisigIsm.ts index 695fe2ab45..069ad70b53 100644 --- a/typescript/sdk/src/consts/multisigIsm.ts +++ b/typescript/sdk/src/consts/multisigIsm.ts @@ -2,24 +2,34 @@ import { MultisigConfig } from '../ism/types'; import { ChainMap } from '../types'; export const defaultMultisigConfigs: ChainMap = { - // ----------------- Mainnets ----------------- - celo: { + alfajores: { threshold: 2, validators: [ - '0x63478422679303c3e4fc611b771fa4a707ef7f4a', - '0x622e43baf06ad808ca8399360d9a2d9a1a12688b', // dsrv - '0xf2c1e3888eb618f1f1a071ef3618f134715a9a49', // everstake + '0x2233a5ce12f814bd64c9cdd73410bb8693124d40', + '0xba279f965489d90f90490e3c49e860e0b43c2ae6', + '0x86485dcec5f7bb8478dd251676372d054dea6653', ], }, - ethereum: { + + arbitrum: { threshold: 3, validators: [ - '0x03c842db86a6a3e524d4a6615390c1ea8e2b9541', - '0x94438a7de38d4548ae54df5c6010c4ebc5239eae', // dsrv + '0x4d966438fe9e2b1e7124c87bbb90cb4f0f6c59a1', + '0xec68258a7c882ac2fc46b81ce80380054ffb4ef2', // dsrv '0x5450447aee7b544c462c9352bef7cad049b0c2dc', // zeeprime - '0xce327111035dd38698c92c3778884dbbb0ca8103', // everstake + '0x092e1c19da58e87ea65198785ee83867fe4bb418', // everstake ], }, + + arbitrumgoerli: { + threshold: 2, + validators: [ + '0x071c8d135845ae5a2cb73f98d681d519014c0a8b', + '0x1bcf03360989f15cbeb174c188288f2c6d2760d7', + '0xc1590eaaeaf380e7859564c5ebcdcc87e8369e0d', + ], + }, + avalanche: { threshold: 2, validators: [ @@ -28,14 +38,25 @@ export const defaultMultisigConfigs: ChainMap = { '0x716a1d4d3166c6151b05ce0450e0d77d94588eac', // everstake ], }, - polygon: { + + base: { threshold: 2, validators: [ - '0x12ecb319c7f4e8ac5eb5226662aeb8528c5cefac', - '0x8dd8f8d34b5ecaa5f66de24b01acd7b8461c3916', - '0x722aa4d45387009684582bca8281440d16b8b40f', // everstake + '0xb9453d675e0fa3c178a17b4ce1ad5b1a279b3af9', + '0x4512985a574cb127b2af2d4bb676876ce804e3f8', + '0x41188cb5a5493a961c467ba38a3f8b1f1d35ee63', // everstake ], }, + + basegoerli: { + threshold: 2, + validators: [ + '0xf6eddda696dcd3bf10f7ce8a02db31ef2e775a03', + '0x5a7d05cebf5db4dde9b2fedcefa76fb58fa05071', + '0x9260a6c7d54cbcbed28f8668679cd1fa3a203b25', + ], + }, + bsc: { threshold: 2, validators: [ @@ -44,63 +65,80 @@ export const defaultMultisigConfigs: ChainMap = { '0xeaf5cf9100f36a4baeea779f8745dda86159103c', // everstake ], }, - arbitrum: { - threshold: 3, + + bsctestnet: { + threshold: 2, validators: [ - '0x4d966438fe9e2b1e7124c87bbb90cb4f0f6c59a1', - '0xec68258a7c882ac2fc46b81ce80380054ffb4ef2', // dsrv - '0x5450447aee7b544c462c9352bef7cad049b0c2dc', // zeeprime - '0x092e1c19da58e87ea65198785ee83867fe4bb418', // everstake + '0x242d8a855a8c932dec51f7999ae7d1e48b10c95e', + '0xf620f5e3d25a3ae848fec74bccae5de3edcd8796', + '0x1f030345963c54ff8229720dd3a711c15c554aeb', ], }, - optimism: { + + celo: { threshold: 2, validators: [ - '0x20349eadc6c72e94ce38268b96692b1a5c20de4f', - '0x5b7d47b76c69740462432f6a5a0ca5005e014157', // dsrv - '0x22b1ad4322cdb5f2c76ebf4e5a93803d480fcf0d', // everstake + '0x63478422679303c3e4fc611b771fa4a707ef7f4a', + '0x622e43baf06ad808ca8399360d9a2d9a1a12688b', // dsrv + '0xf2c1e3888eb618f1f1a071ef3618f134715a9a49', // everstake ], }, - moonbeam: { + + chiado: { threshold: 2, validators: [ - '0x2225e2f4e9221049456da93b71d2de41f3b6b2a8', - '0x645428d198d2e76cbd9c1647f5c80740bb750b97', // dsrv - '0xaed886392df07897743d8e272d438f00c4c9a2ae', // everstake + '0x06c3757a4b7a912828e523bb8a5f980ddc297356', + '0x0874967a145d70b799ebe9ed861ab7c93faef95a', + '0xd767ea1206b8295d7e1267ddd00e56d34f278db6', ], }, - gnosis: { + + ethereum: { + threshold: 3, + validators: [ + '0x03c842db86a6a3e524d4a6615390c1ea8e2b9541', + '0x94438a7de38d4548ae54df5c6010c4ebc5239eae', // dsrv + '0x5450447aee7b544c462c9352bef7cad049b0c2dc', // zeeprime + '0xce327111035dd38698c92c3778884dbbb0ca8103', // everstake + ], + }, + + fuji: { threshold: 2, validators: [ - '0xd4df66a859585678f2ea8357161d896be19cc1ca', - '0x19fb7e04a1be6b39b6966a0b0c60b929a93ed672', // dsrv - '0xdb96116d13a2fadde9742d7cc88474a5ed39a03a', // everstake + '0xd8154f73d04cc7f7f0c332793692e6e6f6b2402e', + '0x895ae30bc83ff1493b9cf7781b0b813d23659857', + '0x43e915573d9f1383cbf482049e4a012290759e7f', ], }, - base: { + + gnosis: { threshold: 2, validators: [ - '0xb9453d675e0fa3c178a17b4ce1ad5b1a279b3af9', - '0x4512985a574cb127b2af2d4bb676876ce804e3f8', - '0x41188cb5a5493a961c467ba38a3f8b1f1d35ee63', // everstake + '0xd4df66a859585678f2ea8357161d896be19cc1ca', + '0x19fb7e04a1be6b39b6966a0b0c60b929a93ed672', // dsrv + '0xdb96116d13a2fadde9742d7cc88474a5ed39a03a', // everstake ], }, - scroll: { + + goerli: { threshold: 2, validators: [ - '0xad557170a9f2f21c35e03de07cb30dcbcc3dff63', - '0xb37fe43a9f47b7024c2d5ae22526cc66b5261533', - '0x276de8e2b88e659c4e5ad30d62d9de42c3da3403', // everstake + '0x05a9b5efe9f61f9142453d8e9f61565f333c6768', + '0x43a96c7dfbd8187c95013d6ee8665650cbdb2673', + '0x7940a12c050e24e1839c21ecb12f65afd84e8c5b', ], }, - polygonzkevm: { + + lineagoerli: { threshold: 2, validators: [ - '0x86f2a44592bb98da766e880cfd70d3bbb295e61a', - '0xc84076030bdabaabb9e61161d833dd84b700afda', - '0x57231619fea13d85270ca6943298046c75a6dd01', // everstake + '0xd767ea1206b8295d7e1267ddd00e56d34f278db6', + '0x4a5d7085ca93c22fbc994dd97857c98fcc745674', + '0x8327779c3c31fa1ffc7f0c9ffae33e4d804bbd8f', ], }, + mantapacific: { threshold: 5, validators: [ @@ -113,47 +151,25 @@ export const defaultMultisigConfigs: ChainMap = { '0x42b6de2edbaa62c2ea2309ad85d20b3e37d38acf', //sg-1 ], }, - // ----------------- Testnets ----------------- - alfajores: { - threshold: 2, - validators: [ - '0x2233a5ce12f814bd64c9cdd73410bb8693124d40', - '0xba279f965489d90f90490e3c49e860e0b43c2ae6', - '0x86485dcec5f7bb8478dd251676372d054dea6653', - ], - }, - basegoerli: { - threshold: 2, - validators: [ - '0xf6eddda696dcd3bf10f7ce8a02db31ef2e775a03', - '0x5a7d05cebf5db4dde9b2fedcefa76fb58fa05071', - '0x9260a6c7d54cbcbed28f8668679cd1fa3a203b25', - ], - }, - fuji: { - threshold: 2, - validators: [ - '0xd8154f73d04cc7f7f0c332793692e6e6f6b2402e', - '0x895ae30bc83ff1493b9cf7781b0b813d23659857', - '0x43e915573d9f1383cbf482049e4a012290759e7f', - ], - }, - chiado: { + + moonbasealpha: { threshold: 2, validators: [ - '0x06c3757a4b7a912828e523bb8a5f980ddc297356', - '0x0874967a145d70b799ebe9ed861ab7c93faef95a', - '0xd767ea1206b8295d7e1267ddd00e56d34f278db6', + '0x521877064bd7ac7500d300f162c8c47c256a2f9c', + '0xbc1c70f58ae0459d4b8a013245420a893837d568', + '0x01e42c2c44af81dda1ac16fec76fea2a7a54a44c', ], }, - lineagoerli: { + + moonbeam: { threshold: 2, validators: [ - '0xd767ea1206b8295d7e1267ddd00e56d34f278db6', - '0x4a5d7085ca93c22fbc994dd97857c98fcc745674', - '0x8327779c3c31fa1ffc7f0c9ffae33e4d804bbd8f', + '0x2225e2f4e9221049456da93b71d2de41f3b6b2a8', + '0x645428d198d2e76cbd9c1647f5c80740bb750b97', // dsrv + '0xaed886392df07897743d8e272d438f00c4c9a2ae', // everstake ], }, + mumbai: { threshold: 2, validators: [ @@ -162,46 +178,33 @@ export const defaultMultisigConfigs: ChainMap = { '0x17517c98358c5937c5d9ee47ce1f5b4c2b7fc9f5', ], }, - bsctestnet: { - threshold: 2, - validators: [ - '0x242d8a855a8c932dec51f7999ae7d1e48b10c95e', - '0xf620f5e3d25a3ae848fec74bccae5de3edcd8796', - '0x1f030345963c54ff8229720dd3a711c15c554aeb', - ], - }, - goerli: { - threshold: 2, - validators: [ - '0x05a9b5efe9f61f9142453d8e9f61565f333c6768', - '0x43a96c7dfbd8187c95013d6ee8665650cbdb2673', - '0x7940a12c050e24e1839c21ecb12f65afd84e8c5b', - ], - }, - scrollsepolia: { + + neutron: { threshold: 2, validators: [ - '0xbe18dbd758afb367180260b524e6d4bcd1cb6d05', - '0x9a11ed23ae962974018ab45bc133caabff7b3271', - '0x7867bea3c9761fe64e6d124b171f91fd5dd79644', + '0xa9b8c1f4998f781f958c63cfcd1708d02f004ff0', + '0x60e890b34cb44ce3fa52f38684f613f31b47a1a6', + '0x7885fae56dbcf5176657f54adbbd881dc6714132', ], }, - sepolia: { + neutrontestnet: { threshold: 2, validators: [ - '0xb22b65f202558adf86a8bb2847b76ae1036686a5', - '0x469f0940684d147defc44f3647146cb90dd0bc8e', - '0xd3c75dcf15056012a4d74c483a0c6ea11d8c2b83', + '0x5d2a99d67cd294a821de4fb25da6901ea8f89814', + '0xb57486243ce3bb3c38c50a582b8bbd20cb393589', + '0x661faee997654d14ead4ae48035883f05c3150cf', ], }, - moonbasealpha: { + + optimism: { threshold: 2, validators: [ - '0x521877064bd7ac7500d300f162c8c47c256a2f9c', - '0xbc1c70f58ae0459d4b8a013245420a893837d568', - '0x01e42c2c44af81dda1ac16fec76fea2a7a54a44c', + '0x20349eadc6c72e94ce38268b96692b1a5c20de4f', + '0x5b7d47b76c69740462432f6a5a0ca5005e014157', // dsrv + '0x22b1ad4322cdb5f2c76ebf4e5a93803d480fcf0d', // everstake ], }, + optimismgoerli: { threshold: 2, validators: [ @@ -210,12 +213,22 @@ export const defaultMultisigConfigs: ChainMap = { '0xf3d2fb4d53c2bb6a88cec040e0d87430fcee4e40', ], }, - arbitrumgoerli: { + + polygon: { threshold: 2, validators: [ - '0x071c8d135845ae5a2cb73f98d681d519014c0a8b', - '0x1bcf03360989f15cbeb174c188288f2c6d2760d7', - '0xc1590eaaeaf380e7859564c5ebcdcc87e8369e0d', + '0x12ecb319c7f4e8ac5eb5226662aeb8528c5cefac', + '0x8dd8f8d34b5ecaa5f66de24b01acd7b8461c3916', + '0x722aa4d45387009684582bca8281440d16b8b40f', // everstake + ], + }, + + polygonzkevm: { + threshold: 2, + validators: [ + '0x86f2a44592bb98da766e880cfd70d3bbb295e61a', + '0xc84076030bdabaabb9e61161d833dd84b700afda', + '0x57231619fea13d85270ca6943298046c75a6dd01', // everstake ], }, @@ -227,28 +240,40 @@ export const defaultMultisigConfigs: ChainMap = { '0xd476548222f43206d0abaa30e46e28670aa7859c', ], }, - solanadevnet: { + + scroll: { threshold: 2, validators: [ - '0xec0f73dbc5b1962a20f7dcbe07c98414025b0c43', - '0x9c20a149dfa09ea9f77f5a7ca09ed44f9c025133', - '0x967c5ecdf2625ae86580bd203b630abaaf85cd62', + '0xad557170a9f2f21c35e03de07cb30dcbcc3dff63', + '0xb37fe43a9f47b7024c2d5ae22526cc66b5261533', + '0x276de8e2b88e659c4e5ad30d62d9de42c3da3403', // everstake ], }, - neutrontestnet: { + + scrollsepolia: { threshold: 2, validators: [ - '0x5d2a99d67cd294a821de4fb25da6901ea8f89814', - '0xb57486243ce3bb3c38c50a582b8bbd20cb393589', - '0x661faee997654d14ead4ae48035883f05c3150cf', + '0xbe18dbd758afb367180260b524e6d4bcd1cb6d05', + '0x9a11ed23ae962974018ab45bc133caabff7b3271', + '0x7867bea3c9761fe64e6d124b171f91fd5dd79644', ], }, - neutron: { + + sepolia: { threshold: 2, validators: [ - '0xa9b8c1f4998f781f958c63cfcd1708d02f004ff0', - '0x60e890b34cb44ce3fa52f38684f613f31b47a1a6', - '0x7885fae56dbcf5176657f54adbbd881dc6714132', + '0xb22b65f202558adf86a8bb2847b76ae1036686a5', + '0x469f0940684d147defc44f3647146cb90dd0bc8e', + '0xd3c75dcf15056012a4d74c483a0c6ea11d8c2b83', + ], + }, + + solanadevnet: { + threshold: 2, + validators: [ + '0xec0f73dbc5b1962a20f7dcbe07c98414025b0c43', + '0x9c20a149dfa09ea9f77f5a7ca09ed44f9c025133', + '0x967c5ecdf2625ae86580bd203b630abaaf85cd62', ], }, }; diff --git a/typescript/sdk/src/index.ts b/typescript/sdk/src/index.ts index 2e76f69772..17692072e0 100644 --- a/typescript/sdk/src/index.ts +++ b/typescript/sdk/src/index.ts @@ -167,6 +167,7 @@ export { buildAgentConfig, } from './metadata/agentConfig'; export { + BlockExplorer, ChainMetadata, ChainMetadataSchema, ChainMetadataSchemaObject, diff --git a/typescript/sdk/src/logger.ts b/typescript/sdk/src/logger.ts new file mode 100644 index 0000000000..0e0a2c910f --- /dev/null +++ b/typescript/sdk/src/logger.ts @@ -0,0 +1,5 @@ +import debug from 'debug'; + +// Default logger for use in utils/scripts +// For classes, prefer to create loggers with more specific namespaces +export const logger = debug('hyperlane'); diff --git a/typescript/sdk/src/metadata/ChainMetadataManager.ts b/typescript/sdk/src/metadata/ChainMetadataManager.ts index c9bf172fcc..97d5fbaedd 100644 --- a/typescript/sdk/src/metadata/ChainMetadataManager.ts +++ b/typescript/sdk/src/metadata/ChainMetadataManager.ts @@ -2,12 +2,15 @@ import { Debugger, debug } from 'debug'; import { ProtocolType, exclude, pick } from '@hyperlane-xyz/utils'; -import { - chainMetadata as defaultChainMetadata, - solanaChainToClusterName, -} from '../consts/chainMetadata'; +import { chainMetadata as defaultChainMetadata } from '../consts/chainMetadata'; import { ChainMap, ChainName } from '../types'; +import { + getExplorerAddressUrl, + getExplorerApiUrl, + getExplorerBaseUrl, + getExplorerTxUrl, +} from './blockExplorer'; import { ChainMetadata, getDomainId, @@ -228,17 +231,8 @@ export class ChainMetadataManager { */ tryGetExplorerUrl(chainNameOrId: ChainName | number): string | null { const metadata = this.tryGetChainMetadata(chainNameOrId); - if (!metadata?.blockExplorers?.length) return null; - const url = new URL(metadata.blockExplorers[0].url); - // TODO move handling of these chain/protocol specific quirks to ChainMetadata - if ( - metadata.protocol === ProtocolType.Sealevel && - solanaChainToClusterName[metadata.name] - ) { - url.searchParams.set('cluster', solanaChainToClusterName[metadata.name]); - } - // TODO cosmos support here - return url.toString(); + if (!metadata) return null; + return getExplorerBaseUrl(metadata); } /** @@ -256,14 +250,8 @@ export class ChainMetadataManager { */ tryGetExplorerApiUrl(chainNameOrId: ChainName | number): string | null { const metadata = this.tryGetChainMetadata(chainNameOrId); - const { protocol, blockExplorers } = metadata || {}; - if (protocol !== ProtocolType.Ethereum) return null; - if (!blockExplorers?.length || !blockExplorers[0].apiUrl) return null; - const { apiUrl, apiKey } = blockExplorers[0]; - if (!apiKey) return apiUrl; - const url = new URL(apiUrl); - url.searchParams.set('apikey', apiKey); - return url.toString(); + if (!metadata) return null; + return getExplorerApiUrl(metadata); } /** @@ -283,15 +271,20 @@ export class ChainMetadataManager { chainNameOrId: ChainName | number, response: { hash: string }, ): string | null { - const baseUrl = this.tryGetExplorerUrl(chainNameOrId); - if (!baseUrl) return null; - const chainName = this.getChainName(chainNameOrId); - const urlPathStub = ['nautilus', 'proteustestnet'].includes(chainName) - ? 'transaction' - : 'tx'; - const url = new URL(baseUrl); - url.pathname += `/${urlPathStub}/${response.hash}`; - return url.toString(); + const metadata = this.tryGetChainMetadata(chainNameOrId); + if (!metadata) return null; + return getExplorerTxUrl(metadata, response.hash); + } + + /** + * Get a block explorer URL for given chain's tx + * @throws if chain's metadata or block explorer data has no been set + */ + getExplorerTxUrl( + chainNameOrId: ChainName | number, + response: { hash: string }, + ): string { + return `${this.getExplorerUrl(chainNameOrId)}/tx/${response.hash}`; } /** @@ -301,12 +294,9 @@ export class ChainMetadataManager { chainNameOrId: ChainName | number, address?: string, ): Promise { - if (!address) return null; - const baseUrl = this.tryGetExplorerUrl(chainNameOrId); - if (!baseUrl) return null; - const url = new URL(baseUrl); - url.pathname += `/address/${address}`; - return url.toString(); + const metadata = this.tryGetChainMetadata(chainNameOrId); + if (!metadata || !address) return null; + return getExplorerAddressUrl(metadata, address); } /** @@ -323,17 +313,6 @@ export class ChainMetadataManager { return url; } - /** - * Get a block explorer URL for given chain's tx - * @throws if chain's metadata or block explorer data has no been set - */ - getExplorerTxUrl( - chainNameOrId: ChainName | number, - response: { hash: string }, - ): string { - return `${this.getExplorerUrl(chainNameOrId)}/tx/${response.hash}`; - } - /** * Creates a new ChainMetadataManager with the extended metadata * @param additionalMetadata extra fields to add to the metadata for each chain diff --git a/typescript/sdk/src/metadata/blockExplorer.ts b/typescript/sdk/src/metadata/blockExplorer.ts new file mode 100644 index 0000000000..fbc616cc18 --- /dev/null +++ b/typescript/sdk/src/metadata/blockExplorer.ts @@ -0,0 +1,61 @@ +import { ProtocolType } from '@hyperlane-xyz/utils'; + +import { solanaChainToClusterName } from '../consts/chainMetadata'; + +import { ChainMetadata } from './chainMetadataTypes'; + +export function getExplorerBaseUrl(metadata: ChainMetadata): string | null { + if (!metadata?.blockExplorers?.length) return null; + const url = new URL(metadata.blockExplorers[0].url); + // TODO consider move handling of these chain/protocol specific quirks to ChainMetadata + if ( + metadata.protocol === ProtocolType.Sealevel && + solanaChainToClusterName[metadata.name] + ) { + url.searchParams.set('cluster', solanaChainToClusterName[metadata.name]); + } + return url.toString(); +} + +export function getExplorerApiUrl(metadata: ChainMetadata): string | null { + const { protocol, blockExplorers } = metadata; + // TODO solana + cosmos support here as needed + if (protocol !== ProtocolType.Ethereum) return null; + if (!blockExplorers?.length || !blockExplorers[0].apiUrl) return null; + const { apiUrl, apiKey } = blockExplorers[0]; + if (!apiKey) return apiUrl; + const url = new URL(apiUrl); + url.searchParams.set('apikey', apiKey); + return url.toString(); +} + +export function getExplorerTxUrl( + metadata: ChainMetadata, + hash: string, +): string | null { + const baseUrl = getExplorerBaseUrl(metadata); + if (!baseUrl) return null; + const chainName = metadata.name; + // TODO consider move handling of these chain/protocol specific quirks to ChainMetadata + const urlPathStub = ['nautilus', 'proteustestnet'].includes(chainName) + ? 'transaction' + : 'tx'; + return appendToPath(baseUrl, `${urlPathStub}/${hash}`).toString(); +} + +export function getExplorerAddressUrl( + metadata: ChainMetadata, + address: string, +): string | null { + const baseUrl = getExplorerBaseUrl(metadata); + if (!baseUrl) return null; + return appendToPath(baseUrl, `address/${address}`).toString(); +} + +function appendToPath(baseUrl: string, pathExtension: string) { + const base = new URL(baseUrl); + let currentPath = base.pathname; + if (currentPath.endsWith('/')) currentPath = currentPath.slice(0, -1); + const newPath = `${currentPath}/${pathExtension}`; + return new URL(newPath, base); +} diff --git a/typescript/sdk/src/metadata/chainMetadataTypes.ts b/typescript/sdk/src/metadata/chainMetadataTypes.ts index 7e185878ee..9418beb6d9 100644 --- a/typescript/sdk/src/metadata/chainMetadataTypes.ts +++ b/typescript/sdk/src/metadata/chainMetadataTypes.ts @@ -217,6 +217,11 @@ export const ChainMetadataSchema = ChainMetadataSchemaObject.refine( export type ChainMetadata = z.infer & Ext; +export type BlockExplorer = Exclude< + ChainMetadata['blockExplorers'], + undefined +>[number]; + export function isValidChainMetadata(c: ChainMetadata): boolean { return ChainMetadataSchema.safeParse(c).success; } diff --git a/typescript/sdk/src/metadata/health.ts b/typescript/sdk/src/metadata/health.ts new file mode 100644 index 0000000000..f030894ff7 --- /dev/null +++ b/typescript/sdk/src/metadata/health.ts @@ -0,0 +1,152 @@ +import { Mailbox__factory } from '@hyperlane-xyz/core'; +import { Address, ProtocolType, timeout } from '@hyperlane-xyz/utils'; + +import { chainIdToMetadata } from '../consts/chainMetadata'; +import { CoreChainName } from '../consts/chains'; +import { hyperlaneContractAddresses } from '../consts/environments'; +import { logger } from '../logger'; +import { + CosmJsProvider, + CosmJsWasmProvider, + EthersV5Provider, + ProviderType, + SolanaWeb3Provider, +} from '../providers/ProviderType'; +import { protocolToDefaultProviderBuilder } from '../providers/providerBuilders'; + +import { + getExplorerAddressUrl, + getExplorerBaseUrl, + getExplorerTxUrl, +} from './blockExplorer'; +import { ChainMetadata, RpcUrl } from './chainMetadataTypes'; + +const HEALTH_CHECK_TIMEOUT = 5000; // 5s + +export async function isRpcHealthy( + rpc: RpcUrl, + chainId: string | number, + protocol: ProtocolType, +): Promise { + try { + const builder = protocolToDefaultProviderBuilder[protocol]; + const provider = builder([rpc], chainId); + let resultPromise; + if (provider.type === ProviderType.EthersV5) + resultPromise = isEthersV5ProviderHealthy(provider.provider, chainId); + else if (provider.type === ProviderType.SolanaWeb3) + resultPromise = isSolanaWeb3ProviderHealthy(provider.provider, chainId); + else if ( + provider.type === ProviderType.CosmJsWasm || + provider.type === ProviderType.CosmJs + ) + resultPromise = isCosmJsProviderHealthy(provider.provider, chainId); + else + throw new Error( + `Unsupported provider type ${provider.type}, new health check required`, + ); + const result = await timeout( + resultPromise, + HEALTH_CHECK_TIMEOUT, + 'RPC health check timed out', + ); + return result; + } catch (error) { + logger(`Provider error for ${rpc.http}`, error); + return false; + } +} + +export async function isEthersV5ProviderHealthy( + provider: EthersV5Provider['provider'], + chainId: string | number, +): Promise { + logger(`Checking ethers provider for ${chainId}`); + const blockNumber = await provider.getBlockNumber(); + if (!blockNumber || blockNumber < 0) return false; + logger(`Block number is okay for ${chainId}`); + + const chainName = chainIdToMetadata[chainId]?.name as CoreChainName; + if (chainName && hyperlaneContractAddresses[chainName]) { + const mailboxAddr = hyperlaneContractAddresses[chainName].mailbox; + const mailbox = Mailbox__factory.createInterface(); + const topics = mailbox.encodeFilterTopics( + mailbox.events['DispatchId(bytes32)'], + [], + ); + logger(`Checking mailbox logs for ${chainId}`); + const mailboxLogs = await provider.getLogs({ + address: mailboxAddr, + topics, + fromBlock: blockNumber - 99, + toBlock: blockNumber, + }); + if (!mailboxLogs) return false; + logger(`Mailbox logs okay for ${chainId}`); + } + return true; +} + +export async function isSolanaWeb3ProviderHealthy( + provider: SolanaWeb3Provider['provider'], + chainId: string | number, +): Promise { + logger(`Checking solana provider for ${chainId}`); + const blockNumber = await provider.getBlockHeight(); + if (!blockNumber || blockNumber < 0) return false; + logger(`Block number is okay for ${chainId}`); + return true; +} + +export async function isCosmJsProviderHealthy( + provider: CosmJsProvider['provider'] | CosmJsWasmProvider['provider'], + chainId: string | number, +): Promise { + const readyProvider = await provider; + const blockNumber = await readyProvider.getHeight(); + if (!blockNumber || blockNumber < 0) return false; + logger(`Block number is okay for ${chainId}`); + return true; +} + +export async function isBlockExplorerHealthy( + chainMetadata: ChainMetadata, + address?: Address, + txHash?: string, +): Promise { + try { + const baseUrl = getExplorerBaseUrl(chainMetadata); + if (!baseUrl) return false; + logger(`Got base url: ${baseUrl}`); + + logger(`Checking explorer home for ${chainMetadata.name}`); + const homeReq = await fetch(baseUrl); + if (!homeReq.ok) return false; + logger(`Explorer home okay for ${chainMetadata.name}`); + + if (address) { + logger(`Checking explorer address page for ${chainMetadata.name}`); + const addressUrl = getExplorerAddressUrl(chainMetadata, address); + if (!addressUrl) return false; + logger(`Got address url: ${addressUrl}`); + const addressReq = await fetch(addressUrl); + if (!addressReq.ok) return false; + logger(`Explorer address page okay for ${chainMetadata.name}`); + } + + if (txHash) { + logger(`Checking explorer tx page for ${chainMetadata.name}`); + const txUrl = getExplorerTxUrl(chainMetadata, txHash); + if (!txUrl) return false; + logger(`Got tx url: ${txUrl}`); + const txReq = await fetch(txUrl); + if (!txReq.ok) return false; + logger(`Explorer tx page okay for ${chainMetadata.name}`); + } + + return true; + } catch (error) { + logger(`Explorer error for ${chainMetadata.name}`, error); + return false; + } +} diff --git a/typescript/sdk/src/test/metadata-check.ts b/typescript/sdk/src/test/metadata-check.ts new file mode 100644 index 0000000000..020f5c47f2 --- /dev/null +++ b/typescript/sdk/src/test/metadata-check.ts @@ -0,0 +1,170 @@ +/* eslint-disable no-console */ +import { ethers } from 'ethers'; + +import { Address, ProtocolType } from '@hyperlane-xyz/utils'; + +import { chainMetadata } from '../consts/chainMetadata'; +import { Chains, CoreChainName, TestChains } from '../consts/chains'; +import { isBlockExplorerHealthy, isRpcHealthy } from '../metadata/health'; +import { ChainMap } from '../types'; + +const PROTOCOL_TO_ADDRESS: Record = { + [ProtocolType.Ethereum]: ethers.constants.AddressZero, + [ProtocolType.Sealevel]: '00000000000000000000000000000000000000000000', + [ProtocolType.Cosmos]: 'cosmos100000000000000000000000000000000000000', + [ProtocolType.Fuel]: '', +}; + +// A random tx hash for each chain, used to test explorer link +const CHAIN_TO_TX_HASH: Record = { + [Chains.alfajores]: + '0xf566f1ba4af5ac53081dc4b22fcac29fe5e9a25f5e134ca5464231ed7d2ffc81', + [Chains.arbitrum]: + '0x30093a67a823ca6b024eb5ca6f7d5cf7e967557662155e783827efcfeb29690f', + [Chains.arbitrumgoerli]: + '0xa86668384160a1b580bdbeabfce212524663143e94b68eb1e7fc48f20bbedc8c', + [Chains.avalanche]: + '0x244ae94a424906c88b2f7fc7697ce78f26fbfc74bee5040d63e1a1c6ef9eb84b', + [Chains.base]: + '0x27c0d75d1a38c0a31b0f41fd20d28a62be4ac83999abdf4f6ea607379b3f3d0d', + [Chains.basegoerli]: + '0xea6274abba0ad633d0155fc6cb5d25edb24bb7005c7b4aed33390716cf773c32', + [Chains.bsc]: + '0x18bd183cd2dc56a462b27331b8d28cddabde0c556698da29d69ee04c1b8b2c9c', + [Chains.bsctestnet]: + '0xcfa8f9c0b601913ddf0f99e03e0e2c211ef59bde7eba72eb8f7df739f913466f', + [Chains.celo]: + '0xb217245342d224c96876849bc2964cac6c648b7b054fd0b0278c5e98e540843b', + [Chains.chiado]: + '0x29d0828c8d1852097736220dd439716ec342caceb41d9edf4be9fda598c837df', + [Chains.ethereum]: + '0xf2f0373bdbdff84640b6d7f37ea999746715df499190b7a1095266066d1b7356', + [Chains.fuji]: + '0xb1b93727cea040b3164056d0b97785e8f0e4b7a749b0a56f9d1c2cf37bec0455', + [Chains.gnosis]: + '0x9f6d46b6be0adbcf6fa4517c6897a11763d4a5aa5e31e6b6b66a0463de958c25', + [Chains.goerli]: + '0xf9eeb8068f02d086fe100bc420af57384eff0fdc4f88e68e4e17e1985a7e2bf0', + [Chains.lineagoerli]: + '0xe0fad79e60d6178452bc07cf15c07cdda97deccd2b197af7790e978a8e5835ac', + [Chains.mantapacific]: + '0x045adb06cae25de2c90be0a8610f7adc226c34d0b03d4383ce3cb2157561d656', + [Chains.moonbasealpha]: + '0xe6711bc12bc1cef88f29e3bbabd9fbb050cfca086a5449f7d4da3819bdc77859', + [Chains.moonbeam]: + '0xf387fa67cf7f4a33d30c0c53900d21c4eff7867f5457a8b9f54802087a07eb96', + [Chains.mumbai]: + '0xeff94a58c83814e3c0fbaa721e95cd76f2dd00274ab547ed2e7d9a78b029c62a', + [Chains.nautilus]: '', + [Chains.neutron]: + '4663DAD97C53850A2BAE898514971BACC8EB8B3C1FE9DBA3E62F5AC86D600E73', + [Chains.optimism]: + '0x139b9beec241a1258630367a2ec0c6567bfd5ce23cfc0c189fbd26b5eb657a33', + [Chains.optimismgoerli]: + '0xd84ae2271533f83c2adea10bd1bebcb97a9bad70ccfb7d771b4159ab0cadfda3', + [Chains.polygon]: + '0x7cf70156dbf12005875f73f48e903e40914d9a69a9487f0834e2d79132ec22f3', + [Chains.polygonzkevm]: + '0xf3fd1213a7b8db63031e83de929169896cbfae33004bb7a55234a1f72cb53d5f', + [Chains.polygonzkevmtestnet]: + '0xf758cfe7f83c9556300f687b01e0f9fcb15156f70406cb54122a0531394ce496', + [Chains.proteustestnet]: '', + [Chains.scroll]: + '0x262a4c4ee74f1a81ed414ffad3a8e2046ad2521252b2091f1acb053239aab5b7', + [Chains.scrollsepolia]: + '0xe2093b1a4c6a0d9d34e6441b449e7cb4e7a785a41e5df2df9a981968888813ae', + [Chains.sepolia]: + '0xdacc9d206b55ba553afc42e2c207e355aacaf96855845b3a746f294fefd4f39d', + [Chains.solana]: + '23346vC32nGAaq4ADj8zJqzVv9DGcY6oqnEbM7g1d1Ydqh8wziEgavKXx1qNqqqHMwKq3LRqaGwMMH7wK9UhAuz4', + [Chains.solanadevnet]: + '58XxWq2AD5Hw58cJxbhLNXsbycHUmHhkUpdacZWBTTz5kFW4dstTHVcb8MKJMRxiG4eVsnmb3Qhbf3TVriuCad4n', + [Chains.test1]: '', + [Chains.test2]: '', + [Chains.test3]: '', +}; + +// Note: run with DEBUG=hyperlane for more detailed logs +async function main() { + const results: ChainMap<{ + goodRpcs: number; + badRpcs: number; + goodExplorers: number; + badExplorers: number; + }> = {}; + const badList: string[] = []; + + for (const metadata of Object.values(chainMetadata)) { + if (TestChains.includes(metadata.name as CoreChainName)) continue; + + console.log(`Checking metadata health for ${metadata.name}`); + if (!metadata.rpcUrls) { + console.error(`No rpcUrls for ${metadata.name}, invalid chain metadata`); + } + if (!metadata.blockExplorers?.length) { + console.warn( + `No block explorers for ${metadata.name}, consider adding one`, + ); + } + + results[metadata.name] = { + goodRpcs: 0, + badRpcs: 0, + goodExplorers: 0, + badExplorers: 0, + }; + + for (const rpc of metadata.rpcUrls) { + const isHealthy = await isRpcHealthy( + rpc, + metadata.chainId, + metadata.protocol, + ); + if (!isHealthy) { + console.error(`RPC ${rpc.http} for ${metadata.name} is not healthy`); + results[metadata.name].badRpcs += 1; + badList.push(rpc.http); + } else { + results[metadata.name].goodRpcs += 1; + } + } + + if (!metadata.blockExplorers?.length) continue; + // This only tests the first explorer since that's + // what the related utils use anyway + const firstExplorerUrl = metadata.blockExplorers[0].url; + const isHealthy = await isBlockExplorerHealthy( + metadata, + PROTOCOL_TO_ADDRESS[metadata.protocol], + CHAIN_TO_TX_HASH[metadata.name], + ); + if (!isHealthy) { + console.error( + `Explorer ${firstExplorerUrl} for ${metadata.name} is not healthy`, + ); + results[metadata.name].badExplorers += 1; + badList.push(firstExplorerUrl); + } else { + results[metadata.name].goodExplorers += 1; + } + } + + console.table(results); + console.log('The bad ones:\n==============='); + console.log(badList); + + if (badList.length) { + console.error('Some RPCs or block explorers are unhealthy'); + process.exit(1); + } +} + +main() + .then(() => { + console.log('Done'); + process.exit(0); + }) + .catch((err) => { + console.error('Unhandled error running test:', err); + process.exit(1); + });