From 35f8699505535bc78d1e093b5a546f36efbbba68 Mon Sep 17 00:00:00 2001 From: Mohammed Hussan Date: Thu, 13 Jun 2024 12:34:59 +0100 Subject: [PATCH] feat(cli): Support creating agent configs from CLI (#3938) ### Description - Add support for creating agent configs using the CLI - registry agent-config command with a required --chains option - This will pick up local registry data Example usage: `hyperlane registry agent-config --chains anvil8545` ### Drive-by changes ### Related issues - Fixes #[3720](https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3720) ### Backward compatibility Yes ### Testing Manual --- .changeset/lovely-boxes-bow.md | 5 ++ typescript/cli/package.json | 3 +- typescript/cli/src/commands/options.ts | 7 +++ typescript/cli/src/commands/registry.ts | 60 ++++++++++++++++++-- typescript/cli/src/commands/types.ts | 2 + typescript/cli/src/config/agent.ts | 75 +++++++++++++++++++++++++ yarn.lock | 10 ++++ 7 files changed, 157 insertions(+), 5 deletions(-) create mode 100644 .changeset/lovely-boxes-bow.md create mode 100644 typescript/cli/src/commands/types.ts create mode 100644 typescript/cli/src/config/agent.ts diff --git a/.changeset/lovely-boxes-bow.md b/.changeset/lovely-boxes-bow.md new file mode 100644 index 0000000000..38ee46ae59 --- /dev/null +++ b/.changeset/lovely-boxes-bow.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/cli': minor +--- + +Add command to support creating agent configs diff --git a/typescript/cli/package.json b/typescript/cli/package.json index ed84f11551..68dbb24a85 100644 --- a/typescript/cli/package.json +++ b/typescript/cli/package.json @@ -18,7 +18,8 @@ "tsx": "^4.7.1", "yaml": "^2.4.1", "yargs": "^17.7.2", - "zod": "^3.21.2" + "zod": "^3.21.2", + "zod-validation-error": "^3.3.0" }, "devDependencies": { "@types/mocha": "^10.0.1", diff --git a/typescript/cli/src/commands/options.ts b/typescript/cli/src/commands/options.ts index 878a585853..3726ecb71f 100644 --- a/typescript/cli/src/commands/options.ts +++ b/typescript/cli/src/commands/options.ts @@ -106,6 +106,13 @@ export const agentConfigCommandOption = ( default: defaultPath, }); +export const chainTargetsCommandOption: Options = { + type: 'string', + description: 'Comma-separated list of chain names', + alias: 'c', + demandOption: true, +}; + export const outputFileCommandOption = ( defaultPath?: string, demandOption = false, diff --git a/typescript/cli/src/commands/registry.ts b/typescript/cli/src/commands/registry.ts index e6847a3a37..d2679f4967 100644 --- a/typescript/cli/src/commands/registry.ts +++ b/typescript/cli/src/commands/registry.ts @@ -1,10 +1,14 @@ import { CommandModule } from 'yargs'; -import { CommandModuleWithContext } from '../context/types.js'; -import { log, logBlue, logGray, logTable } from '../logger.js'; +import { createAgentConfig } from '../config/agent.js'; +import { CommandContext, CommandModuleWithContext } from '../context/types.js'; +import { log, logBlue, logGray, logRed, logTable } from '../logger.js'; -const ChainTypes = ['mainnet', 'testnet']; -type ChainType = (typeof ChainTypes)[number]; +import { + chainTargetsCommandOption, + outputFileCommandOption, +} from './options.js'; +import { ChainType, ChainTypes } from './types.js'; /** * Parent command @@ -16,6 +20,7 @@ export const registryCommand: CommandModule = { yargs .command(listCommand) .command(addressesCommand) + .command(createAgentConfigCommand) .version(false) .demandCommand(), handler: () => log('Command required'), @@ -88,3 +93,50 @@ const addressesCommand: CommandModuleWithContext<{ name: string }> = { } }, }; + +/** + * agent-config command + */ +const createAgentConfigCommand: CommandModuleWithContext<{ + chains: string; + out: string; +}> = { + command: 'agent-config', + describe: 'Create a new agent config', + + builder: { + chains: chainTargetsCommandOption, + out: outputFileCommandOption( + './configs/agent-config.json', + false, + 'The path to output an agent config JSON file.', + ), + }, + handler: async ({ + context, + chains, + out, + }: { + context: CommandContext; + chains: string; + out: string; + }) => { + const { multiProvider } = context; + + const chainNames = chains.split(','); + const invalidChainNames = chainNames.filter( + (chainName) => !multiProvider.hasChain(chainName), + ); + if (invalidChainNames.length > 0) { + logRed( + `Invalid chain names: ${invalidChainNames + .join(', ') + .replace(/, $/, '')}`, + ); + process.exit(1); + } + + await createAgentConfig({ context, chains: chainNames, out }); + process.exit(0); + }, +}; diff --git a/typescript/cli/src/commands/types.ts b/typescript/cli/src/commands/types.ts new file mode 100644 index 0000000000..bc017069aa --- /dev/null +++ b/typescript/cli/src/commands/types.ts @@ -0,0 +1,2 @@ +export const ChainTypes = ['mainnet', 'testnet']; +export type ChainType = (typeof ChainTypes)[number]; diff --git a/typescript/cli/src/config/agent.ts b/typescript/cli/src/config/agent.ts new file mode 100644 index 0000000000..a9d1be46fc --- /dev/null +++ b/typescript/cli/src/config/agent.ts @@ -0,0 +1,75 @@ +import { fromError } from 'zod-validation-error'; + +import { + AgentConfigSchema, + ChainMap, + HyperlaneCore, + HyperlaneDeploymentArtifacts, + buildAgentConfig, +} from '@hyperlane-xyz/sdk'; +import { objMap, promiseObjAll } from '@hyperlane-xyz/utils'; + +import { CommandContext } from '../context/types.js'; +import { logBlue, logGreen, logRed } from '../logger.js'; +import { writeYamlOrJson } from '../utils/files.js'; + +export async function createAgentConfig({ + context, + chains, + out, +}: { + context: CommandContext; + chains: string[]; + out: string; +}) { + logBlue('\nCreating agent config...'); + + const { registry, multiProvider, chainMetadata } = context; + const addresses = await registry.getAddresses(); + + const core = HyperlaneCore.fromAddressesMap(addresses, multiProvider); + + const startBlocks = await promiseObjAll( + objMap(addresses, async (chain, _) => { + // If the index.from is specified in the chain metadata, use that. + const indexFrom = chainMetadata[chain].index?.from; + if (indexFrom !== undefined) { + return indexFrom; + } + + const mailbox = core.getContracts(chain).mailbox; + try { + const deployedBlock = await mailbox.deployedBlock(); + return deployedBlock.toNumber(); + } catch (err) { + logRed( + `Failed to get deployed block to set an index for ${chain}, this is potentially an issue with rpc provider or a misconfiguration`, + ); + process.exit(1); + } + }), + ); + + // @TODO: consider adding additional config used to pass in gas prices for Cosmos chains + const agentConfig = buildAgentConfig( + chains, + multiProvider, + addresses as ChainMap, + startBlocks, + ); + + try { + AgentConfigSchema.parse(agentConfig); + } catch (e) { + logRed( + `Agent config is invalid, this is possibly due to required contracts not being deployed. See details below:\n${fromError( + e, + ).toString()}`, + ); + process.exit(1); + } + + logBlue(`Agent config is valid, writing to file ${out}`); + writeYamlOrJson(out, agentConfig, 'json'); + logGreen(`✅ Agent config successfully written to ${out}`); +} diff --git a/yarn.lock b/yarn.lock index 2dfb37d5f7..e8bf9eb8c1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5715,6 +5715,7 @@ __metadata: yaml: "npm:^2.4.1" yargs: "npm:^17.7.2" zod: "npm:^3.21.2" + zod-validation-error: "npm:^3.3.0" bin: hyperlane: ./dist/cli.js languageName: unknown @@ -26173,6 +26174,15 @@ __metadata: languageName: node linkType: hard +"zod-validation-error@npm:^3.3.0": + version: 3.3.0 + resolution: "zod-validation-error@npm:3.3.0" + peerDependencies: + zod: ^3.18.0 + checksum: 19574cbc453c7a41105de572546e95191958f459dd93440f541a42c0ff209b56f1cd54e8f8ab1899430dd7c183e11cd16e8cace0bd4fc5d356ef772645210792 + languageName: node + linkType: hard + "zod@npm:^3.21.2": version: 3.21.2 resolution: "zod@npm:3.21.2"