cosmic-interchain-cli
Version:
A command-line utility for Cosmic Wire's interchain messaging protocol
198 lines • 8.16 kB
JavaScript
import { confirm, input, select } from '@inquirer/prompts';
import { ethers } from 'ethers';
import { stringify as yamlStringify } from 'yaml';
import { ChainMetadataSchema, ExplorerFamily, ZChainName, } from '@hyperlane-xyz/sdk';
import { ProtocolType } from '@hyperlane-xyz/utils';
import { errorRed, log, logBlue, logGreen } from '../logger.js';
import { indentYamlOrJson, readYamlOrJson } from '../utils/files.js';
import { detectAndConfirmOrPrompt } from '../utils/input.js';
export function readChainConfigs(filePath) {
log(`Reading file configs in ${filePath}`);
const chainMetadata = readYamlOrJson(filePath);
if (!chainMetadata ||
typeof chainMetadata !== 'object' ||
!Object.keys(chainMetadata).length) {
errorRed(`No configs found in ${filePath}`);
process.exit(1);
}
// Validate configs from file and merge in core configs as needed
const parseResult = ChainMetadataSchema.safeParse(chainMetadata);
if (!parseResult.success) {
errorRed(`Chain config for ${filePath} is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/chain-config.yaml for an example`);
errorRed(JSON.stringify(parseResult.error.errors));
process.exit(1);
}
return chainMetadata;
}
export async function createChainConfig({ context, }) {
logBlue('Creating a new chain config');
const rpcUrl = await detectAndConfirmOrPrompt(async () => {
await new ethers.providers.JsonRpcProvider().getNetwork();
return ethers.providers.JsonRpcProvider.defaultUrl();
}, 'Enter http or https', 'rpc url', 'JSON RPC provider');
const provider = new ethers.providers.JsonRpcProvider(rpcUrl);
const name = await input({
message: 'Enter chain name (one word, lower case)',
validate: (chainName) => ZChainName.safeParse(chainName).success,
});
const displayName = await input({
message: 'Enter chain display name',
default: name[0].toUpperCase() + name.slice(1),
});
const chainId = parseInt(await detectAndConfirmOrPrompt(async () => {
const network = await provider.getNetwork();
return network.chainId.toString();
}, 'Enter a (number)', 'chain id', 'JSON RPC provider'), 10);
const isTestnet = await confirm({
message: 'Is this chain a testnet (a chain used for testing & development)?',
});
const metadata = {
name,
displayName,
chainId,
domainId: chainId,
protocol: ProtocolType.Ethereum,
rpcUrls: [{ http: rpcUrl }],
isTestnet,
};
await addBlockExplorerConfig(metadata);
await addBlockOrGasConfig(metadata);
await addNativeTokenConfig(metadata);
const parseResult = ChainMetadataSchema.safeParse(metadata);
if (parseResult.success) {
logGreen(`Chain config is valid, writing unsorted to registry:`);
const metadataYaml = yamlStringify(metadata, {
indent: 2,
sortMapEntries: true,
});
log(indentYamlOrJson(metadataYaml, 4));
await context.registry.updateChain({ chainName: metadata.name, metadata });
}
else {
errorRed(`Chain config is invalid, please see https://github.com/hyperlane-xyz/hyperlane-monorepo/blob/main/typescript/cli/examples/chain-config.yaml for an example`);
errorRed(JSON.stringify(parseResult.error.errors));
throw new Error('Invalid chain config');
}
}
async function addBlockExplorerConfig(metadata) {
const wantBlockExplorerConfig = await confirm({
default: false,
message: 'Do you want to add a block explorer config for this chain',
});
if (wantBlockExplorerConfig) {
const name = await input({
message: 'Enter a human readable name for the explorer:',
});
const url = await input({
message: 'Enter the base URL for the explorer:',
});
const apiUrl = await input({
message: 'Enter the base URL for requests to the explorer API:',
});
const family = (await select({
message: 'Select the type (family) of block explorer:',
choices: Object.entries(ExplorerFamily).map(([_, value]) => ({ value })),
pageSize: 10,
}));
const apiKey = (await input({
message: "Optional: Provide an API key for the explorer, or press 'enter' to skip. Please be sure to remove this field if you intend to add your config to the Hyperlane registry:",
})) ?? undefined;
metadata.blockExplorers = [];
metadata.blockExplorers[0] = {
name,
url,
apiUrl,
family,
};
if (apiKey)
metadata.blockExplorers[0].apiKey = apiKey;
}
}
async function addBlockOrGasConfig(metadata) {
const wantBlockOrGasConfig = await confirm({
default: false,
message: 'Do you want to set block or gas properties for this chain config',
});
if (wantBlockOrGasConfig) {
await addBlockConfig(metadata);
await addGasConfig(metadata);
}
}
async function addBlockConfig(metadata) {
const wantBlockConfig = await confirm({
message: 'Do you want to add block config for this chain',
});
if (wantBlockConfig) {
const blockConfirmation = await input({
message: 'Enter no. of blocks to wait before considering a transaction confirmed (0-500):',
validate: (value) => parseInt(value) >= 0 && parseInt(value) <= 500,
});
const blockReorgPeriod = await input({
message: 'Enter no. of blocks before a transaction has a near-zero chance of reverting (0-500):',
validate: (value) => parseInt(value) >= 0 && parseInt(value) <= 500,
});
const blockTimeEstimate = await input({
message: 'Enter the rough estimate of time per block in seconds (0-20):',
validate: (value) => parseInt(value) >= 0 && parseInt(value) <= 20,
});
metadata.blocks = {
confirmations: parseInt(blockConfirmation, 10),
reorgPeriod: parseInt(blockReorgPeriod, 10),
estimateBlockTime: parseInt(blockTimeEstimate, 10),
};
}
}
async function addGasConfig(metadata) {
const wantGasConfig = await confirm({
message: 'Do you want to add gas config for this chain',
});
if (wantGasConfig) {
const isEIP1559 = await confirm({
message: 'Is your chain an EIP1559 enabled',
});
if (isEIP1559) {
const maxFeePerGas = await input({
message: 'Enter the max fee per gas (gwei):',
});
const maxPriorityFeePerGas = await input({
message: 'Enter the max priority fee per gas (gwei):',
});
metadata.transactionOverrides = {
maxFeePerGas: BigInt(maxFeePerGas) * BigInt(10 ** 9),
maxPriorityFeePerGas: BigInt(maxPriorityFeePerGas) * BigInt(10 ** 9),
};
}
else {
const gasPrice = await input({
message: 'Enter the gas price (gwei):',
});
metadata.transactionOverrides = {
gasPrice: BigInt(gasPrice) * BigInt(10 ** 9),
};
}
}
}
async function addNativeTokenConfig(metadata) {
const wantNativeConfig = await confirm({
default: false,
message: 'Do you want to set native token properties for this chain config (defaults to ETH)',
});
let symbol, name, decimals;
if (wantNativeConfig) {
symbol = await input({
message: "Enter the native token's symbol:",
});
name = await input({
message: `Enter the native token's name:`,
});
decimals = await input({
message: "Enter the native token's decimals:",
});
}
metadata.nativeToken = {
symbol: symbol ?? 'ETH',
name: name ?? 'Ether',
decimals: decimals ? parseInt(decimals, 10) : 18,
};
}
//# sourceMappingURL=chain.js.map