UNPKG

@hyperlane-xyz/cli

Version:

A command-line utility for common Hyperlane operations

272 lines 10.2 kB
import { stringify as yamlStringify } from 'yaml'; import { ChainSubmissionStrategySchema, expandWarpDeployConfig, getRouterAddressesFromWarpCoreConfig, } from '@hyperlane-xyz/sdk'; import { assert, objFilter } from '@hyperlane-xyz/utils'; import { runWarpRouteCheck } from '../check/warp.js'; import { createWarpRouteDeployConfig, readWarpRouteDeployConfig, } from '../config/warp.js'; import { evaluateIfDryRunFailure } from '../deploy/dry-run.js'; import { runWarpRouteApply, runWarpRouteDeploy } from '../deploy/warp.js'; import { log, logBlue, logCommandHeader, logGreen } from '../logger.js'; import { runWarpRouteRead } from '../read/warp.js'; import { sendTestTransfer } from '../send/transfer.js'; import { runSingleChainSelectionStep } from '../utils/chains.js'; import { indentYamlOrJson, readYamlOrJson, removeEndingSlash, writeYamlOrJson, } from '../utils/files.js'; import { selectRegistryWarpRoute } from '../utils/tokens.js'; import { getWarpCoreConfigOrExit } from '../utils/warp.js'; import { runVerifyWarpRoute } from '../verify/warp.js'; import { DEFAULT_WARP_ROUTE_DEPLOYMENT_CONFIG_PATH, addressCommandOption, chainCommandOption, dryRunCommandOption, fromAddressCommandOption, inputFileCommandOption, outputFileCommandOption, strategyCommandOption, symbolCommandOption, warpCoreConfigCommandOption, warpDeploymentConfigCommandOption, } from './options.js'; import { messageSendOptions } from './send.js'; /** * Parent command */ export const warpCommand = { command: 'warp', describe: 'Manage Hyperlane warp routes', builder: (yargs) => yargs .command(apply) .command(check) .command(deploy) .command(init) .command(read) .command(send) .command(verify) .version(false) .demandCommand(), handler: () => log('Command required'), }; export const apply = { command: 'apply', describe: 'Update Warp Route contracts', builder: { config: warpDeploymentConfigCommandOption, symbol: { ...symbolCommandOption, demandOption: false, }, warp: { ...warpCoreConfigCommandOption, demandOption: false, }, strategy: { ...strategyCommandOption, demandOption: false }, 'receipts-dir': { type: 'string', description: 'The directory to output transaction receipts.', default: './generated/transactions', coerce: (dir) => removeEndingSlash(dir), }, }, handler: async ({ context, config, symbol, warp, strategy: strategyUrl, receiptsDir, }) => { logCommandHeader('Hyperlane Warp Apply'); const warpCoreConfig = await getWarpCoreConfigOrExit({ symbol, warp, context, }); if (strategyUrl) ChainSubmissionStrategySchema.parse(readYamlOrJson(strategyUrl)); const warpDeployConfig = await readWarpRouteDeployConfig(config, context); await runWarpRouteApply({ context, warpDeployConfig, warpCoreConfig, strategyUrl, receiptsDir, }); process.exit(0); }, }; export const deploy = { command: 'deploy', describe: 'Deploy Warp Route contracts', builder: { config: warpDeploymentConfigCommandOption, 'dry-run': dryRunCommandOption, 'from-address': fromAddressCommandOption, }, handler: async ({ context, config, dryRun }) => { logCommandHeader(`Hyperlane Warp Route Deployment${dryRun ? ' Dry-Run' : ''}`); try { await runWarpRouteDeploy({ context, warpRouteDeploymentConfigPath: config, }); } catch (error) { evaluateIfDryRunFailure(error, dryRun); throw error; } process.exit(0); }, }; export const init = { command: 'init', describe: 'Create a warp route configuration.', builder: { advanced: { type: 'boolean', describe: 'Create an advanced ISM', default: false, }, out: outputFileCommandOption(DEFAULT_WARP_ROUTE_DEPLOYMENT_CONFIG_PATH), }, handler: async ({ context, advanced, out }) => { logCommandHeader('Hyperlane Warp Configure'); await createWarpRouteDeployConfig({ context, outPath: out, advanced, }); process.exit(0); }, }; export const read = { command: 'read', describe: 'Derive the warp route config from onchain artifacts', builder: { symbol: { ...symbolCommandOption, demandOption: false, }, chain: { ...chainCommandOption, demandOption: false, }, address: addressCommandOption('Address of the router contract to read.', false), config: outputFileCommandOption(DEFAULT_WARP_ROUTE_DEPLOYMENT_CONFIG_PATH, false, 'The path to output a Warp Config JSON or YAML file.'), }, handler: async ({ context, chain, address, config: configFilePath, symbol, }) => { logCommandHeader('Hyperlane Warp Reader'); const config = await runWarpRouteRead({ context, chain, address, symbol, }); if (configFilePath) { writeYamlOrJson(configFilePath, config, 'yaml'); logGreen(`✅ Warp route config written successfully to ${configFilePath}:\n`); } else { logGreen(`✅ Warp route config read successfully:\n`); } log(indentYamlOrJson(yamlStringify(config, null, 2), 4)); process.exit(0); }, }; const send = { command: 'send', describe: 'Send a test token transfer on a warp route', builder: { ...messageSendOptions, symbol: { ...symbolCommandOption, demandOption: false, }, warp: { ...warpCoreConfigCommandOption, demandOption: false, }, amount: { type: 'string', description: 'Amount to send (in smallest unit)', default: 1, }, recipient: { type: 'string', description: 'Token recipient address (defaults to sender)', }, }, handler: async ({ context, origin, destination, timeout, quick, relay, symbol, warp, amount, recipient, roundTrip, }) => { const warpCoreConfig = await getWarpCoreConfigOrExit({ symbol, warp, context, }); let chains = warpCoreConfig.tokens.map((t) => t.chainName); if (roundTrip) { // Appends the reverse of the array, excluding the 1st (e.g. [1,2,3] becomes [1,2,3,2,1]) const reversed = [...chains].reverse().slice(1, chains.length + 1); // We make a copy because .reverse() is mutating chains.push(...reversed); } else { // Assume we want to use use `--origin` and `--destination` params, prompt as needed. const chainMetadata = objFilter(context.chainMetadata, (key, _metadata) => chains.includes(key)); if (!origin) origin = await runSingleChainSelectionStep(chainMetadata, 'Select the origin chain:'); if (!destination) destination = await runSingleChainSelectionStep(chainMetadata, 'Select the destination chain:'); chains = [origin, destination].filter((c) => chains.includes(c)); assert(chains.length === 2, `Origin (${origin}) or destination (${destination}) are not part of the warp route.`); } logBlue(`🚀 Sending a message for chains: ${chains.join(' ➡️ ')}`); await sendTestTransfer({ context, warpCoreConfig, chains, amount, recipient, timeoutSec: timeout, skipWaitForDelivery: quick, selfRelay: relay, }); logGreen(`✅ Successfully sent messages for chains: ${chains.join(' ➡️ ')}`); process.exit(0); }, }; export const check = { command: 'check', describe: 'Verifies that a warp route configuration matches the on chain configuration.', builder: { symbol: { ...symbolCommandOption, demandOption: false, }, warp: { ...warpCoreConfigCommandOption, demandOption: false, }, config: inputFileCommandOption({ defaultPath: DEFAULT_WARP_ROUTE_DEPLOYMENT_CONFIG_PATH, description: 'The path to a warp route deployment configuration file', }), }, handler: async ({ context, config, symbol, warp }) => { logCommandHeader('Hyperlane Warp Check'); const warpRouteConfig = await readWarpRouteDeployConfig(config, context); const onChainWarpConfig = await runWarpRouteRead({ context, warp, symbol, }); const warpCoreConfig = context.warpCoreConfig ?? (await getWarpCoreConfigOrExit({ context, warp, symbol, })); if (!warpCoreConfig) { throw new Error('No warp core config found'); } const expandedWarpDeployConfig = await expandWarpDeployConfig(context.multiProvider, warpRouteConfig, getRouterAddressesFromWarpCoreConfig(warpCoreConfig)); await runWarpRouteCheck({ onChainWarpConfig, warpRouteConfig: expandedWarpDeployConfig, }); process.exit(0); }, }; export const verify = { command: 'verify', describe: 'Verify deployed contracts on explorers', builder: { symbol: { ...symbolCommandOption, demandOption: false, }, }, handler: async ({ context, symbol }) => { logCommandHeader('Hyperlane Warp Verify'); const warpCoreConfig = await selectRegistryWarpRoute(context.registry, symbol); return runVerifyWarpRoute({ context, warpCoreConfig }); }, }; //# sourceMappingURL=warp.js.map