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
pull/1473/head
J M Rossy 2 years ago committed by GitHub
parent ff8ac2eabd
commit 343225a7c1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 14
      typescript/infra/config/environments/testnet3/agent.ts
  2. 1
      typescript/infra/config/environments/testnet3/chains.ts
  3. 2
      typescript/infra/src/core/deploy.ts
  4. 1
      typescript/sdk/package.json
  5. 210
      typescript/sdk/src/consts/chainConnectionConfigs.ts
  6. 643
      typescript/sdk/src/consts/chainMetadata.ts
  7. 49
      typescript/sdk/src/consts/chains.ts
  8. 46
      typescript/sdk/src/consts/metamask.ts
  9. 36
      typescript/sdk/src/events.ts
  10. 2
      typescript/sdk/src/gas/calculator.ts
  11. 18
      typescript/sdk/src/index.ts
  12. 9
      typescript/sdk/src/providers/ChainConnection.ts
  13. 8
      typescript/sdk/src/types.ts
  14. 8
      yarn.lock

@ -1,3 +1,5 @@
import { chainMetadata } from '@hyperlane-xyz/sdk';
import { ALL_KEY_ROLES } from '../../../src/agents/roles'; import { ALL_KEY_ROLES } from '../../../src/agents/roles';
import { AgentConfig } from '../../../src/config'; import { AgentConfig } from '../../../src/config';
import { import {
@ -44,22 +46,22 @@ export const hyperlane: AgentConfig<TestnetChains> = {
}, },
chainOverrides: { chainOverrides: {
alfajores: { alfajores: {
reorgPeriod: 0, reorgPeriod: chainMetadata.alfajores.blocks.reorgPeriod,
}, },
fuji: { fuji: {
reorgPeriod: 3, reorgPeriod: chainMetadata.fuji.blocks.reorgPeriod,
}, },
mumbai: { mumbai: {
reorgPeriod: 32, reorgPeriod: chainMetadata.mumbai.blocks.reorgPeriod,
}, },
bsctestnet: { bsctestnet: {
reorgPeriod: 9, reorgPeriod: chainMetadata.bsctestnet.blocks.reorgPeriod,
}, },
goerli: { goerli: {
reorgPeriod: 3, reorgPeriod: chainMetadata.goerli.blocks.reorgPeriod,
}, },
moonbasealpha: { moonbasealpha: {
reorgPeriod: 0, reorgPeriod: chainMetadata.moonbasealpha.blocks.reorgPeriod,
}, },
}, },
}, },

@ -5,7 +5,6 @@ export const testnetConfigs = {
fuji: chainConnectionConfigs.fuji, fuji: chainConnectionConfigs.fuji,
mumbai: { mumbai: {
...chainConnectionConfigs.mumbai, ...chainConnectionConfigs.mumbai,
confirmations: 3,
overrides: { overrides: {
maxFeePerGas: 70 * 10 ** 9, // 70 gwei maxFeePerGas: 70 * 10 ** 9, // 70 gwei
maxPriorityFeePerGas: 40 * 10 ** 9, // 40 gwei maxPriorityFeePerGas: 40 * 10 ** 9, // 40 gwei

@ -102,7 +102,7 @@ export class HyperlaneCoreInfraDeployer<
multisigIsm: contracts.multisigIsm.address, multisigIsm: contracts.multisigIsm.address,
}, },
rpcStyle: 'ethereum', rpcStyle: 'ethereum',
finalityBlocks: metadata.finalityBlocks.toString(), finalityBlocks: metadata.blocks.reorgPeriod.toString(),
connection: { connection: {
type: ConnectionType.Http, type: ConnectionType.Http,
url: '', url: '',

@ -17,6 +17,7 @@
"@nomiclabs/hardhat-waffle": "^2.0.2", "@nomiclabs/hardhat-waffle": "^2.0.2",
"@types/coingecko-api": "^1.0.10", "@types/coingecko-api": "^1.0.10",
"@types/node": "^16.9.1", "@types/node": "^16.9.1",
"@wagmi/chains": "^0.1.3",
"chai": "^4.3.6", "chai": "^4.3.6",
"dotenv": "^10.0.0", "dotenv": "^10.0.0",
"ethereum-waffle": "^3.4.4", "ethereum-waffle": "^3.4.4",

@ -3,206 +3,40 @@ import { ethers } from 'ethers';
import { StaticCeloJsonRpcProvider } from '@hyperlane-xyz/celo-ethers-provider'; import { StaticCeloJsonRpcProvider } from '@hyperlane-xyz/celo-ethers-provider';
import { ChainMap, ChainName, IChainConnection } from '../types'; import { ChainMap, ChainName, IChainConnection } from '../types';
import { objMap } from '../utils/objects';
export const ethereum: IChainConnection = { import { chainMetadata } from './chainMetadata';
provider: new ethers.providers.JsonRpcProvider( import { Chains, TestChains } from './chains';
'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 = { function testChainConnection() {
provider: new ethers.providers.JsonRpcProvider( return {
'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( provider: new ethers.providers.JsonRpcProvider(
'http://localhost:8545', 'http://localhost:8545',
31337, 31337,
), ),
confirmations: 1, confirmations: 1,
}; };
}
export const test2: IChainConnection = { export const chainConnectionConfigs: ChainMap<ChainName, IChainConnection> =
provider: new ethers.providers.JsonRpcProvider( objMap(chainMetadata, (chainName, metadata) => {
'http://localhost:8545', if (TestChains.includes(chainName)) return testChainConnection();
31337,
),
confirmations: 1,
};
export const test3: IChainConnection = { const providerClass =
provider: new ethers.providers.JsonRpcProvider( chainName === Chains.alfajores || chainName === Chains.celo
'http://localhost:8545', ? StaticCeloJsonRpcProvider
31337, : ethers.providers.JsonRpcProvider;
),
confirmations: 1,
};
export const chainConnectionConfigs: ChainMap<ChainName, IChainConnection> = { return {
arbitrum, provider: new providerClass(metadata.publicRpcUrls[0].http, metadata.id),
bsc, confirmations: metadata.blocks.confirmations,
ethereum, blockExplorerUrl: metadata.blockExplorers[0].url,
celo, blockExplorerApiUrl: metadata.blockExplorers[0].apiUrl,
polygon,
avalanche,
alfajores,
fuji,
goerli,
mumbai,
bsctestnet,
optimism,
moonbasealpha,
moonbeam,
optimismgoerli,
arbitrumgoerli,
test1,
test2,
test3,
}; };
});
export const testChainConnectionConfigs = { export const testChainConnectionConfigs = {
test1, test1: testChainConnection(),
test2, test2: testChainConnection(),
test3, test3: testChainConnection(),
}; };

@ -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; id: number;
finalityBlocks: number; name: ChainName;
nativeTokenDecimals?: number; /** Human-readable name */
paginate?: RpcPagination; displayName: string;
// The CoinGecko API expects, in some cases, IDs that do not match /** Shorter human-readable name */
// ChainNames. 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; gasCurrencyCoinGeckoId?: string;
// URL of the gnosis safe transaction service. // URL of the gnosis safe transaction service.
gnosisSafeTransactionServiceUrl?: string; gnosisSafeTransactionServiceUrl?: string;
}; }
/**
* RPC Pagination information
*/
export interface RpcPagination { export interface RpcPagination {
blocks: number; blocks: number;
from: 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 = { const avaxToken = {
id: 42220, decimals: 18,
finalityBlocks: 0, name: 'Avalanche',
gnosisSafeTransactionServiceUrl: symbol: 'AVAX',
'https://transaction-service.gnosis-safe-staging.celo-networks-dev.org', };
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, * Chain metadata
finalityBlocks: 20, */
gnosisSafeTransactionServiceUrl: 'https://safe-transaction.gnosis.io',
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 = { export const arbitrum: ChainMetadata = {
id: 42161, 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 gasCurrencyCoinGeckoId: 'ethereum', // ETH is used for gas
gnosisSafeTransactionServiceUrl: gnosisSafeTransactionServiceUrl:
'https://safe-transaction.arbitrum.gnosis.io/', 'https://safe-transaction.arbitrum.gnosis.io/',
}; };
export const optimism: ChainMetadata = { export const arbitrumgoerli: ChainMetadata = {
id: 10, id: 421613,
finalityBlocks: 0, name: Chains.arbitrumgoerli,
gasCurrencyCoinGeckoId: 'ethereum', // ETH is used for gas displayName: 'Arbitrum Goerli',
gnosisSafeTransactionServiceUrl: displayNameShort: 'Arb. Goerli',
'https://safe-transaction.optimism.gnosis.io/', nativeToken: etherToken,
}; publicRpcUrls: [{ http: 'https://goerli-rollup.arbitrum.io/rpc' }],
blockExplorers: [
export const bsc: ChainMetadata = { {
id: 56, name: 'Arbiscan',
finalityBlocks: 15, url: 'https://goerli.arbiscan.io/',
gasCurrencyCoinGeckoId: 'binancecoin', apiUrl: 'https://api-goerli.arbiscan.io',
gnosisSafeTransactionServiceUrl: 'https://safe-transaction.bsc.gnosis.io/', family: ExplorerFamily.Etherscan,
},
],
blocks: {
confirmations: 1,
reorgPeriod: 1,
estimateBlockTime: 3,
},
}; };
export const avalanche: ChainMetadata = { export const avalanche: ChainMetadata = {
id: 43114, id: 43114,
finalityBlocks: 3, name: Chains.avalanche,
paginate: { displayName: 'Avalanche',
// Needs to be low to avoid RPC timeouts nativeToken: avaxToken,
publicRpcUrls: [
{
http: 'https://api.avax.network/ext/bc/C/rpc',
pagination: {
blocks: 100000, blocks: 100000,
from: 6765067, 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', gasCurrencyCoinGeckoId: 'avalanche-2',
gnosisSafeTransactionServiceUrl: gnosisSafeTransactionServiceUrl:
'https://safe-transaction.avalanche.gnosis.io/', 'https://safe-transaction.avalanche.gnosis.io/',
}; };
export const polygon: ChainMetadata = { export const bsc: ChainMetadata = {
id: 137, id: 56,
finalityBlocks: 256, name: Chains.bsc,
paginate: { displayName: 'Binance Smart Chain',
// Needs to be low to avoid RPC timeouts displayNameShort: 'Binance',
blocks: 10000, nativeToken: bnbToken,
from: 19657100, 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 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,
},
};
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: gnosisSafeTransactionServiceUrl:
'https://safe-transaction.polygon.gnosis.io/', 'https://transaction-service.gnosis-safe-staging.celo-networks-dev.org',
}; };
/** export const ethereum: ChainMetadata = {
* Testnets id: 1,
*/ name: Chains.ethereum,
export const alfajores: ChainMetadata = { displayName: 'Ethereum',
id: 44787, nativeToken: etherToken,
finalityBlocks: 0, 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 = { export const fuji: ChainMetadata = {
id: 43113, 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 = { export const goerli: ChainMetadata = {
id: 5, id: 5,
finalityBlocks: 2, name: Chains.goerli,
}; displayName: 'Goerli',
nativeToken: etherToken,
export const optimismgoerli: ChainMetadata = { publicRpcUrls: [{ http: 'https://rpc.ankr.com/eth_goerli' }],
id: 420, blockExplorers: [
finalityBlocks: 1, {
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 = { export const moonbasealpha: ChainMetadata = {
id: 421613, id: 1287,
finalityBlocks: 1, 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 = { export const moonbeam: ChainMetadata = {
id: 280, id: 1284,
finalityBlocks: 1, 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 = { export const mumbai: ChainMetadata = {
id: 80001, id: 80001,
finalityBlocks: 32, name: Chains.mumbai,
paginate: { 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 // eth_getLogs and eth_newFilter are limited to a 10,000 blocks range
blocks: 10000, blocks: 10000,
from: 22900000, 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 = { export const optimism: ChainMetadata = {
test1: { id: 10,
id: 13371, name: Chains.optimism,
finalityBlocks: 0, 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,
}, },
test2: { ],
id: 13372, blocks: {
finalityBlocks: 1, confirmations: 1,
reorgPeriod: 0,
estimateBlockTime: 3,
}, },
test3: { gasCurrencyCoinGeckoId: 'ethereum', // ETH is used for gas
id: 13373, gnosisSafeTransactionServiceUrl:
finalityBlocks: 2, '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,
}, },
}; };
export const bsctestnet: ChainMetadata = { export const polygon: ChainMetadata = {
id: 97, id: 137,
finalityBlocks: 9, 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 moonbasealpha: ChainMetadata = { export const test1: ChainMetadata = {
id: 1287, id: 13371,
finalityBlocks: 1, name: Chains.test1,
displayName: 'Test 1',
nativeToken: etherToken,
publicRpcUrls: [{ http: 'http://localhost:8545' }],
blockExplorers: [],
blocks: {
confirmations: 1,
reorgPeriod: 0,
estimateBlockTime: 3,
},
}; };
export const moonbeam: ChainMetadata = { export const test2: ChainMetadata = {
id: 1284, id: 13372,
finalityBlocks: 1, name: Chains.test2,
displayName: 'Test 2',
nativeToken: etherToken,
publicRpcUrls: [{ http: 'http://localhost:8545' }],
blockExplorers: [],
blocks: {
confirmations: 1,
reorgPeriod: 1,
estimateBlockTime: 3,
},
}; };
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 = { export const chainMetadata = {
alfajores,
arbitrum, arbitrum,
arbitrumgoerli,
avalanche,
bsc, bsc,
bsctestnet,
celo, celo,
ethereum, ethereum,
avalanche,
optimism,
polygon,
alfajores,
fuji, fuji,
goerli, goerli,
mumbai,
bsctestnet,
moonbasealpha, moonbasealpha,
moonbeam, moonbeam,
mumbai,
optimism,
optimismgoerli, optimismgoerli,
arbitrumgoerli, polygon,
zksync2testnet, test1,
...testChains, test2,
test3,
} as Record<ChainName, ChainMetadata>; } as Record<ChainName, ChainMetadata>;
// For convenient use in wagmi-based apps
export const wagmiChainMetadata: Record<ChainName, WagmiChain> = 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<number, ChainMetadata>
>((result, chain) => {
result[chain.id] = chain;
return result;
}, {});
export const mainnetChainsMetadata: Array<ChainMetadata> = Mainnets.map(
(chainName) => chainMetadata[chainName],
);
export const testnetChainsMetadata: Array<ChainMetadata> = 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<ChainName, PartialChainMetadata> =
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,
}));

@ -1,38 +1,40 @@
import { ChainName } from '../types';
/** /**
* Enumeration of Hyperlane supported chains * 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 export enum Chains {
arbitrum = 'arbitrum',
alfajores = 'alfajores', alfajores = 'alfajores',
arbitrum = 'arbitrum',
arbitrumgoerli = 'arbitrumgoerli',
avalanche = 'avalanche',
bsc = 'bsc', bsc = 'bsc',
mumbai = 'mumbai', bsctestnet = 'bsctestnet',
goerli = 'goerli',
fuji = 'fuji',
celo = 'celo', celo = 'celo',
ethereum = 'ethereum', ethereum = 'ethereum',
avalanche = 'avalanche', fuji = 'fuji',
optimism = 'optimism', goerli = 'goerli',
polygon = 'polygon',
bsctestnet = 'bsctestnet',
moonbasealpha = 'moonbasealpha', moonbasealpha = 'moonbasealpha',
moonbeam = 'moonbeam', moonbeam = 'moonbeam',
mumbai = 'mumbai',
optimism = 'optimism',
optimismgoerli = 'optimismgoerli', optimismgoerli = 'optimismgoerli',
arbitrumgoerli = 'arbitrumgoerli', polygon = 'polygon',
test1 = 'test1', test1 = 'test1',
test2 = 'test2', test2 = 'test2',
test3 = 'test3', test3 = 'test3',
} }
export type ChainName = keyof typeof Chains;
export enum DeprecatedChains { export enum DeprecatedChains {
rinkeby = 'rinkeby', arbitrumkovan = 'arbitrumkovan',
optimismrinkeby = 'optimismrinkeby',
arbitrumrinkeby = 'arbitrumrinkeby', arbitrumrinkeby = 'arbitrumrinkeby',
kovan = 'kovan', kovan = 'kovan',
rinkeby = 'rinkeby',
optimismkovan = 'optimismkovan', optimismkovan = 'optimismkovan',
arbitrumkovan = 'arbitrumkovan', optimismrinkeby = 'optimismrinkeby',
} }
export const AllDeprecatedChains = Object.keys(DeprecatedChains) as string[]; export const AllDeprecatedChains = Object.keys(DeprecatedChains) as string[];
export const Mainnets = [ export const Mainnets = [
@ -46,4 +48,21 @@ export const Mainnets = [
Chains.moonbeam, Chains.moonbeam,
] as Array<ChainName>; ] as Array<ChainName>;
export const Testnets = [
Chains.alfajores,
Chains.arbitrumgoerli,
Chains.bsctestnet,
Chains.fuji,
Chains.goerli,
Chains.moonbasealpha,
Chains.mumbai,
Chains.optimismgoerli,
] as Array<ChainName>;
export const TestChains = [
Chains.test1,
Chains.test2,
Chains.test3,
] as Array<ChainName>;
export const AllChains = Object.keys(Chains) as Array<ChainName>; export const AllChains = Object.keys(Chains) as Array<ChainName>;

@ -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<void> {
// 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],
});
}
}

@ -123,8 +123,8 @@ export async function getEvents<T extends TypedEvent>(
startBlock?: number, startBlock?: number,
endBlock?: number, endBlock?: number,
): Promise<Array<T>> { ): Promise<Array<T>> {
const domain = chainMetadata[chain]; const mustPaginate = !!chainMetadata[chain].publicRpcUrls[0].pagination;
if (domain.paginate) { if (mustPaginate) {
return getPaginatedEvents( return getPaginatedEvents(
multiprovider, multiprovider,
chain, chain,
@ -144,8 +144,8 @@ export async function findEvent<T extends TypedEvent>(
filter: TypedEventFilter<T>, filter: TypedEventFilter<T>,
startBlock?: number, startBlock?: number,
): Promise<Array<T>> { ): Promise<Array<T>> {
const domain = chainMetadata[chain]; const mustPaginate = !!chainMetadata[chain].publicRpcUrls[0].pagination;
if (domain.paginate) { if (mustPaginate) {
return findFromPaginatedEvents( return findFromPaginatedEvents(
multiprovider, multiprovider,
chain, chain,
@ -165,15 +165,15 @@ async function getPaginatedEvents<T extends TypedEvent>(
startBlock?: number, startBlock?: number,
endBlock?: number, endBlock?: number,
): Promise<Array<T>> { ): Promise<Array<T>> {
const domain = chainMetadata[chain]; const pagination = chainMetadata[chain].publicRpcUrls[0].pagination;
if (!domain.paginate) { if (!pagination) {
throw new Error('Domain need not be paginated'); throw new Error('Domain need not be paginated');
} }
// get the first block by params // get the first block by params
// or domain deployment block // or domain deployment block
const firstBlock = startBlock const firstBlock = startBlock
? Math.max(startBlock, domain.paginate.from) ? Math.max(startBlock, pagination.from)
: domain.paginate.from; : pagination.from;
// get the last block by params // get the last block by params
// or current block number // or current block number
let lastBlock; let lastBlock;
@ -185,12 +185,8 @@ async function getPaginatedEvents<T extends TypedEvent>(
} }
// query domain pagination limit at a time, concurrently // query domain pagination limit at a time, concurrently
const eventArrayPromises = []; const eventArrayPromises = [];
for ( for (let from = firstBlock; from <= lastBlock; from += pagination.blocks) {
let from = firstBlock; const nextFrom = from + pagination.blocks;
from <= lastBlock;
from += domain.paginate.blocks
) {
const nextFrom = from + domain.paginate.blocks;
const to = Math.min(nextFrom, lastBlock); const to = Math.min(nextFrom, lastBlock);
const eventArrayPromise = contract.queryFilter(filter, from, to); const eventArrayPromise = contract.queryFilter(filter, from, to);
eventArrayPromises.push(eventArrayPromise); eventArrayPromises.push(eventArrayPromise);
@ -212,15 +208,15 @@ async function findFromPaginatedEvents<T extends TypedEvent>(
startBlock?: number, startBlock?: number,
endBlock?: number, endBlock?: number,
): Promise<Array<T>> { ): Promise<Array<T>> {
const domain = chainMetadata[chain]; const pagination = chainMetadata[chain].publicRpcUrls[0].pagination;
if (!domain.paginate) { if (!pagination) {
throw new Error('Domain need not be paginated'); throw new Error('Domain need not be paginated');
} }
// get the first block by params // get the first block by params
// or domain deployment block // or domain deployment block
const firstBlock = startBlock const firstBlock = startBlock
? Math.max(startBlock, domain.paginate.from) ? Math.max(startBlock, pagination.from)
: domain.paginate.from; : pagination.from;
// get the last block by params // get the last block by params
// or current block number // or current block number
let lastBlock; let lastBlock;
@ -232,8 +228,8 @@ async function findFromPaginatedEvents<T extends TypedEvent>(
} }
// query domain pagination limit at a time, concurrently // query domain pagination limit at a time, concurrently
// eslint-disable-next-line for-direction // eslint-disable-next-line for-direction
for (let end = lastBlock; end > firstBlock; end -= domain.paginate.blocks) { for (let end = lastBlock; end > firstBlock; end -= pagination.blocks) {
const nextEnd = end - domain.paginate.blocks; const nextEnd = end - pagination.blocks;
const from = Math.max(nextEnd, firstBlock); const from = Math.max(nextEnd, firstBlock);
const queriedEvents = await contract.queryFilter(filter, from, end); const queriedEvents = await contract.queryFilter(filter, from, end);
if (queriedEvents.length > 0) { if (queriedEvents.length > 0) {

@ -252,7 +252,7 @@ export class InterchainGasCalculator<Chain extends ChainName> {
* @returns The number of decimals of `chain`'s native token. * @returns The number of decimals of `chain`'s native token.
*/ */
protected tokenDecimals(chain: Chain): number { protected tokenDecimals(chain: Chain): number {
return chainMetadata[chain].nativeTokenDecimals ?? DEFAULT_TOKEN_DECIMALS; return chainMetadata[chain].nativeToken.decimals ?? DEFAULT_TOKEN_DECIMALS;
} }
/** /**

@ -1,10 +1,23 @@
export { export {
AllChains,
Chains, Chains,
ChainName,
DeprecatedChains,
AllChains,
Mainnets, Mainnets,
AllDeprecatedChains, AllDeprecatedChains,
} from './consts/chains'; } from './consts/chains';
export { chainMetadata } from './consts/chainMetadata'; export {
ChainMetadata,
RpcPagination,
ExplorerFamily,
chainMetadata,
chainIdToMetadata,
mainnetChainsMetadata,
testnetChainsMetadata,
wagmiChainMetadata,
PartialChainMetadata,
partialChainMetadata,
} from './consts/chainMetadata';
export { export {
chainConnectionConfigs, chainConnectionConfigs,
testChainConnectionConfigs, testChainConnectionConfigs,
@ -16,7 +29,6 @@ export {
export { export {
ChainMap, ChainMap,
ChainName,
CompleteChainMap, CompleteChainMap,
Connection, Connection,
NameOrDomain, NameOrDomain,

@ -9,7 +9,7 @@ export class ChainConnection {
overrides: ethers.Overrides; overrides: ethers.Overrides;
confirmations: number; confirmations: number;
blockExplorerUrl: string; blockExplorerUrl: string;
apiPrefix: string; blockExplorerApiUrl: string;
logger: Debugger; logger: Debugger;
constructor(dc: IChainConnection) { constructor(dc: IChainConnection) {
@ -18,7 +18,7 @@ export class ChainConnection {
this.overrides = dc.overrides ?? {}; this.overrides = dc.overrides ?? {};
this.confirmations = dc.confirmations ?? 0; this.confirmations = dc.confirmations ?? 0;
this.blockExplorerUrl = dc.blockExplorerUrl ?? 'UNKNOWN_EXPLORER'; this.blockExplorerUrl = dc.blockExplorerUrl ?? 'UNKNOWN_EXPLORER';
this.apiPrefix = dc.apiPrefix ?? 'api.'; this.blockExplorerApiUrl = dc.blockExplorerApiUrl ?? this.blockExplorerUrl;
this.logger = debug('hyperlane:ChainConnection'); this.logger = debug('hyperlane:ChainConnection');
} }
@ -38,10 +38,7 @@ export class ChainConnection {
} }
getApiUrl(): string { getApiUrl(): string {
const prefix = 'https://'; return this.blockExplorerApiUrl;
return `${prefix}${this.apiPrefix}${this.blockExplorerUrl.slice(
prefix.length,
)}/api`;
} }
async handleTx( async handleTx(

@ -1,9 +1,9 @@
import type { ethers } from 'ethers'; 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 // Re-export ChainName for convenience
export type ChainName = keyof typeof Chains; export { ChainName };
// A full object map of all chains to a value type // A full object map of all chains to a value type
export type CompleteChainMap<Value> = Record<ChainName, Value>; export type CompleteChainMap<Value> = Record<ChainName, Value>;
export type LooseChainMap<Value> = Record<ChainName | string, Value>; export type LooseChainMap<Value> = Record<ChainName | string, Value>;
@ -35,5 +35,5 @@ export interface IChainConnection {
overrides?: ethers.Overrides; overrides?: ethers.Overrides;
confirmations?: number; confirmations?: number;
blockExplorerUrl?: string; blockExplorerUrl?: string;
apiPrefix?: string; blockExplorerApiUrl?: string;
} }

@ -3846,6 +3846,7 @@ __metadata:
"@types/coingecko-api": ^1.0.10 "@types/coingecko-api": ^1.0.10
"@types/debug": ^4.1.7 "@types/debug": ^4.1.7
"@types/node": ^16.9.1 "@types/node": ^16.9.1
"@wagmi/chains": ^0.1.3
chai: ^4.3.6 chai: ^4.3.6
coingecko-api: ^1.0.10 coingecko-api: ^1.0.10
cross-fetch: ^3.1.5 cross-fetch: ^3.1.5
@ -4945,6 +4946,13 @@ __metadata:
languageName: node languageName: node
linkType: hard 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": "@yarnpkg/lockfile@npm:^1.1.0":
version: 1.1.0 version: 1.1.0
resolution: "@yarnpkg/lockfile@npm:1.1.0" resolution: "@yarnpkg/lockfile@npm:1.1.0"

Loading…
Cancel
Save