From ac9eac6ececa837ae4c64f67b5052c8f2687297d Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 30 Nov 2023 13:40:20 -0500 Subject: [PATCH 1/6] Improve shared context utility with conditional typing and core artifact handling --- typescript/cli/src/config/artifacts.ts | 14 ++++--- typescript/cli/src/config/chain.ts | 4 +- typescript/cli/src/context.test.ts | 23 ++++++++++++ typescript/cli/src/context.ts | 52 +++++++++++++++++++++----- typescript/cli/src/deploy/agent.ts | 2 +- typescript/cli/src/deploy/core.ts | 11 +++--- typescript/cli/src/deploy/warp.ts | 25 +++++-------- typescript/cli/src/send/message.ts | 15 +++----- typescript/cli/src/send/transfer.ts | 23 +++++------- typescript/cli/src/status/message.ts | 9 ++--- 10 files changed, 112 insertions(+), 66 deletions(-) create mode 100644 typescript/cli/src/context.test.ts diff --git a/typescript/cli/src/config/artifacts.ts b/typescript/cli/src/config/artifacts.ts index d5643c94f1..5b54b169f4 100644 --- a/typescript/cli/src/config/artifacts.ts +++ b/typescript/cli/src/config/artifacts.ts @@ -3,7 +3,7 @@ import { ZodTypeAny, z } from 'zod'; import { ChainName, HyperlaneContractsMap } from '@hyperlane-xyz/sdk'; -import { log, logBlue, logRed } from '../../logger.js'; +import { log, logBlue } from '../../logger.js'; import { readYamlOrJson, runFileSelectionStep } from '../utils/files.js'; const RecursiveObjectSchema: ZodTypeAny = z.lazy(() => @@ -37,7 +37,9 @@ export async function runDeploymentArtifactStep( artifactsPath?: string, message?: string, selectedChains?: ChainName[], -) { + defaultArtifactsPath = './artifacts', + defaultArtifactsNamePattern = 'core-deployment', +): Promise | undefined> { if (!artifactsPath) { const useArtifacts = await confirm({ message: message || 'Do you want use some existing contract addresses?', @@ -45,9 +47,9 @@ export async function runDeploymentArtifactStep( if (!useArtifacts) return undefined; artifactsPath = await runFileSelectionStep( - './artifacts', - 'contract artifacts', - 'core-deployment', + defaultArtifactsPath, + 'contract deployment artifacts', + defaultArtifactsNamePattern, ); } const artifacts = readDeploymentArtifacts(artifactsPath); @@ -57,7 +59,7 @@ export async function runDeploymentArtifactStep( selectedChains.includes(c), ); if (artifactChains.length === 0) { - logRed('No artifacts found for selected chains'); + log('No artifacts found for selected chains'); } else { log(`Found existing artifacts for chains: ${artifactChains.join(', ')}`); } diff --git a/typescript/cli/src/config/chain.ts b/typescript/cli/src/config/chain.ts index 7a1f2509e8..2f33292bcb 100644 --- a/typescript/cli/src/config/chain.ts +++ b/typescript/cli/src/config/chain.ts @@ -60,8 +60,8 @@ export function readChainConfigs(filePath: string) { return chainToMetadata; } -export function readChainConfigsIfExists(filePath: string) { - if (!isFile(filePath)) { +export function readChainConfigsIfExists(filePath?: string) { + if (!filePath || !isFile(filePath)) { log('No chain config file provided'); return {}; } else { diff --git a/typescript/cli/src/context.test.ts b/typescript/cli/src/context.test.ts new file mode 100644 index 0000000000..a89f3ea8a9 --- /dev/null +++ b/typescript/cli/src/context.test.ts @@ -0,0 +1,23 @@ +import { expect } from 'chai'; +import { ethers } from 'ethers'; + +import { getContext } from './context.js'; + +describe('context', () => { + it('Gets minimal read-only context correctly', async () => { + const context = await getContext({ chainConfigPath: './fakePath' }); + expect(!!context.multiProvider).to.be.true; + expect(context.customChains).to.eql({}); + }); + + it('Handles conditional type correctly', async () => { + const randomWallet = ethers.Wallet.createRandom(); + const context = await getContext({ + chainConfigPath: './fakePath', + key: randomWallet.privateKey, + }); + expect(!!context.multiProvider).to.be.true; + expect(context.customChains).to.eql({}); + expect(await context.signer.getAddress()).to.eql(randomWallet.address); + }); +}); diff --git a/typescript/cli/src/context.ts b/typescript/cli/src/context.ts index 1de98f981b..2f28c43ea9 100644 --- a/typescript/cli/src/context.ts +++ b/typescript/cli/src/context.ts @@ -11,6 +11,7 @@ import { } from '@hyperlane-xyz/sdk'; import { objFilter, objMap, objMerge } from '@hyperlane-xyz/utils'; +import { runDeploymentArtifactStep } from './config/artifacts.js'; import { readChainConfigsIfExists } from './config/chain.js'; import { keyToSigner } from './utils/keys.js'; @@ -43,17 +44,50 @@ export function getMergedContractAddresses( ) as HyperlaneContractsMap; } -export function getContext(chainConfigPath: string) { - const customChains = readChainConfigsIfExists(chainConfigPath); - const multiProvider = getMultiProvider(customChains); - return { customChains, multiProvider }; +interface ContextSettings { + chainConfigPath?: string; + coreConfig?: { + coreArtifactsPath?: string; + promptMessage?: string; + }; + key?: string; +} + +interface CommandContextBase { + customChains: ChainMap; + multiProvider: MultiProvider; } -export function getContextWithSigner(key: string, chainConfigPath: string) { - const signer = keyToSigner(key); - const customChains = readChainConfigsIfExists(chainConfigPath); - const multiProvider = getMultiProvider(customChains, signer); - return { signer, customChains, multiProvider }; +type CommandContext

= CommandContextBase & + (P extends { key: string } + ? { signer: ethers.Signer } + : { signer: undefined }) & + (P extends { coreConfig: object } + ? { coreArtifacts: HyperlaneContractsMap } + : { coreArtifacts: undefined }); + +export async function getContext

( + settings: P, +): Promise> { + const customChains = readChainConfigsIfExists(settings.chainConfigPath); + const multiProvider = getMultiProvider(customChains); + const context: any = { + customChains, + multiProvider, + signer: undefined, + coreArtifacts: undefined, + }; + if (settings.key) { + context.signer = keyToSigner(settings.key); + } + if (settings.coreConfig) { + context.coreArtifacts = await runDeploymentArtifactStep( + settings.coreConfig.coreArtifactsPath, + settings.coreConfig.promptMessage || + 'Do you want to use some core deployment address artifacts? This is required for warp deployments to PI chains (non-core chains).', + ); + } + return context; } export function getMultiProvider( diff --git a/typescript/cli/src/deploy/agent.ts b/typescript/cli/src/deploy/agent.ts index 8628611af4..7fbf97ade0 100644 --- a/typescript/cli/src/deploy/agent.ts +++ b/typescript/cli/src/deploy/agent.ts @@ -21,7 +21,7 @@ export async function runKurtosisAgentDeploy({ chainConfigPath: string; agentConfigurationPath: string; }) { - const { customChains } = getContext(chainConfigPath); + const { customChains } = await getContext({ chainConfigPath }); if (!originChain) { originChain = await runSingleChainSelectionStep( diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index 6f0255e6e6..5a6f94457a 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -37,7 +37,7 @@ import { readIsmConfig } from '../config/ism.js'; import { readMultisigConfig } from '../config/multisig.js'; import { MINIMUM_CORE_DEPLOY_GAS } from '../consts.js'; import { - getContextWithSigner, + getContext, getMergedContractAddresses, sdkContractAddressesMap, } from '../context.js'; @@ -77,10 +77,10 @@ export async function runCoreDeploy({ outPath: string; skipConfirmation: boolean; }) { - const { customChains, multiProvider, signer } = getContextWithSigner( - key, + const { customChains, multiProvider, signer } = await getContext({ chainConfigPath, - ); + key, + }); if (!chains?.length) { chains = await runMultiChainSelectionStep( @@ -120,8 +120,7 @@ export async function runCoreDeploy({ function runArtifactStep(selectedChains: ChainName[], artifactsPath?: string) { logBlue( - '\n', - 'Deployments can be totally new or can use some existing contract addresses.', + '\nDeployments can be totally new or can use some existing contract addresses.', ); return runDeploymentArtifactStep(artifactsPath, undefined, selectedChains); } diff --git a/typescript/cli/src/deploy/warp.ts b/typescript/cli/src/deploy/warp.ts index 9d3f8fecb6..eb9fd89425 100644 --- a/typescript/cli/src/deploy/warp.ts +++ b/typescript/cli/src/deploy/warp.ts @@ -20,13 +20,9 @@ import { import { Address, ProtocolType, objMap } from '@hyperlane-xyz/utils'; import { log, logBlue, logGray, logGreen } from '../../logger.js'; -import { runDeploymentArtifactStep } from '../config/artifacts.js'; import { WarpRouteConfig, readWarpRouteConfig } from '../config/warp.js'; import { MINIMUM_WARP_DEPLOY_GAS } from '../consts.js'; -import { - getContextWithSigner, - getMergedContractAddresses, -} from '../context.js'; +import { getContext, getMergedContractAddresses } from '../context.js'; import { isFile, prepNewArtifactsFiles, @@ -52,7 +48,11 @@ export async function runWarpDeploy({ outPath: string; skipConfirmation: boolean; }) { - const { multiProvider, signer } = getContextWithSigner(key, chainConfigPath); + const { multiProvider, signer, coreArtifacts } = await getContext({ + chainConfigPath, + coreConfig: { coreArtifactsPath }, + key, + }); if (!warpConfigPath || !isFile(warpConfigPath)) { warpConfigPath = await runFileSelectionStep( @@ -65,14 +65,9 @@ export async function runWarpDeploy({ } const warpRouteConfig = readWarpRouteConfig(warpConfigPath); - const artifacts = await runDeploymentArtifactStep( - coreArtifactsPath, - 'Do you want use some core deployment address artifacts? This is required for warp deployments to PI chains (non-core chains).', - ); - const configs = await runBuildConfigStep({ warpRouteConfig, - artifacts, + coreArtifacts, multiProvider, signer, }); @@ -99,12 +94,12 @@ async function runBuildConfigStep({ warpRouteConfig, multiProvider, signer, - artifacts, + coreArtifacts, }: { warpRouteConfig: WarpRouteConfig; multiProvider: MultiProvider; signer: ethers.Signer; - artifacts?: HyperlaneContractsMap; + coreArtifacts?: HyperlaneContractsMap; }) { log('Assembling token configs'); const { base, synthetics } = warpRouteConfig; @@ -118,7 +113,7 @@ async function runBuildConfigStep({ ); const mergedContractAddrs = getMergedContractAddresses( - artifacts, + coreArtifacts, Object.keys(warpRouteConfig), ); diff --git a/typescript/cli/src/send/message.ts b/typescript/cli/src/send/message.ts index 09d1028522..f9c27aa76e 100644 --- a/typescript/cli/src/send/message.ts +++ b/typescript/cli/src/send/message.ts @@ -9,12 +9,8 @@ import { import { addressToBytes32, timeout } from '@hyperlane-xyz/utils'; import { errorRed, log, logBlue, logGreen } from '../../logger.js'; -import { readDeploymentArtifacts } from '../config/artifacts.js'; import { MINIMUM_TEST_SEND_GAS } from '../consts.js'; -import { - getContextWithSigner, - getMergedContractAddresses, -} from '../context.js'; +import { getContext, getMergedContractAddresses } from '../context.js'; import { runPreflightChecks } from '../deploy/utils.js'; const MESSAGE_BODY = '0x48656c6c6f21'; // Hello!' @@ -38,10 +34,11 @@ export async function sendTestMessage({ timeoutSec: number; skipWaitForDelivery: boolean; }) { - const { signer, multiProvider } = getContextWithSigner(key, chainConfigPath); - const coreArtifacts = coreArtifactsPath - ? readDeploymentArtifacts(coreArtifactsPath) - : undefined; + const { signer, multiProvider, coreArtifacts } = await getContext({ + chainConfigPath, + coreConfig: { coreArtifactsPath }, + key, + }); await runPreflightChecks({ origin, diff --git a/typescript/cli/src/send/transfer.ts b/typescript/cli/src/send/transfer.ts index 5e413309ba..6005107a32 100644 --- a/typescript/cli/src/send/transfer.ts +++ b/typescript/cli/src/send/transfer.ts @@ -16,12 +16,8 @@ import { import { Address, timeout } from '@hyperlane-xyz/utils'; import { log, logBlue, logGreen } from '../../logger.js'; -import { readDeploymentArtifacts } from '../config/artifacts.js'; import { MINIMUM_TEST_SEND_GAS } from '../consts.js'; -import { - getContextWithSigner, - getMergedContractAddresses, -} from '../context.js'; +import { getContext, getMergedContractAddresses } from '../context.js'; import { runPreflightChecks } from '../deploy/utils.js'; import { assertNativeBalances, assertTokenBalance } from '../utils/balances.js'; @@ -52,10 +48,11 @@ export async function sendTestTransfer({ timeoutSec: number; skipWaitForDelivery: boolean; }) { - const { signer, multiProvider } = getContextWithSigner(key, chainConfigPath); - const artifacts = coreArtifactsPath - ? readDeploymentArtifacts(coreArtifactsPath) - : undefined; + const { signer, multiProvider, coreArtifacts } = await getContext({ + chainConfigPath, + coreConfig: { coreArtifactsPath }, + key, + }); if (tokenType === TokenType.collateral) { await assertTokenBalance( @@ -91,7 +88,7 @@ export async function sendTestTransfer({ recipient, signer, multiProvider, - artifacts, + coreArtifacts, skipWaitForDelivery, }), timeoutSec * 1000, @@ -108,7 +105,7 @@ async function executeDelivery({ recipient, multiProvider, signer, - artifacts, + coreArtifacts, skipWaitForDelivery, }: { origin: ChainName; @@ -119,13 +116,13 @@ async function executeDelivery({ recipient?: string; multiProvider: MultiProvider; signer: ethers.Signer; - artifacts?: HyperlaneContractsMap; + coreArtifacts?: HyperlaneContractsMap; skipWaitForDelivery: boolean; }) { const signerAddress = await signer.getAddress(); recipient ||= signerAddress; - const mergedContractAddrs = getMergedContractAddresses(artifacts); + const mergedContractAddrs = getMergedContractAddresses(coreArtifacts); const core = HyperlaneCore.fromAddressesMap( mergedContractAddrs, diff --git a/typescript/cli/src/status/message.ts b/typescript/cli/src/status/message.ts index 933b75cea2..38c333f464 100644 --- a/typescript/cli/src/status/message.ts +++ b/typescript/cli/src/status/message.ts @@ -1,7 +1,6 @@ import { ChainName, HyperlaneCore } from '@hyperlane-xyz/sdk'; import { log, logBlue, logGreen } from '../../logger.js'; -import { readDeploymentArtifacts } from '../config/artifacts.js'; import { getContext, getMergedContractAddresses } from '../context.js'; export async function checkMessageStatus({ @@ -15,10 +14,10 @@ export async function checkMessageStatus({ messageId: string; destination: ChainName; }) { - const { multiProvider } = getContext(chainConfigPath); - const coreArtifacts = coreArtifactsPath - ? readDeploymentArtifacts(coreArtifactsPath) - : undefined; + const { multiProvider, coreArtifacts } = await getContext({ + chainConfigPath, + coreConfig: { coreArtifactsPath }, + }); const mergedContractAddrs = getMergedContractAddresses(coreArtifacts); const core = HyperlaneCore.fromAddressesMap( From 9fd6484104049b59a3ee5f20596b22795e8130c8 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 30 Nov 2023 13:57:01 -0500 Subject: [PATCH 2/6] Improve UX of send and status commands --- .changeset/thin-dolls-deliver.md | 5 ++++ typescript/cli/src/commands/send.ts | 20 ++++++------- typescript/cli/src/commands/status.ts | 8 ++---- typescript/cli/src/context.ts | 11 +++---- typescript/cli/src/send/message.ts | 34 +++++++++++++++------- typescript/cli/src/send/transfer.ts | 41 +++++++++++++++++++++------ typescript/cli/src/status/message.ts | 24 +++++++++++++--- 7 files changed, 99 insertions(+), 44 deletions(-) create mode 100644 .changeset/thin-dolls-deliver.md diff --git a/.changeset/thin-dolls-deliver.md b/.changeset/thin-dolls-deliver.md new file mode 100644 index 0000000000..99601354e9 --- /dev/null +++ b/.changeset/thin-dolls-deliver.md @@ -0,0 +1,5 @@ +--- +'@hyperlane-xyz/cli': patch +--- + +Improve UX of the send and status commands diff --git a/typescript/cli/src/commands/send.ts b/typescript/cli/src/commands/send.ts index 90bd7e526e..42491c90dd 100644 --- a/typescript/cli/src/commands/send.ts +++ b/typescript/cli/src/commands/send.ts @@ -35,15 +35,13 @@ const messageOptions: { [k: string]: Options } = { origin: { type: 'string', description: 'Origin chain to send message from', - demandOption: true, }, destination: { type: 'string', description: 'Destination chain to send message to', - demandOption: true, }, - core: coreArtifactsOption, chains: chainsCommandOption, + core: coreArtifactsOption, timeout: { type: 'number', description: 'Timeout in seconds', @@ -63,9 +61,9 @@ const messageCommand: CommandModule = { handler: async (argv: any) => { const key: string = argv.key || process.env.HYP_KEY; const chainConfigPath: string = argv.chains; - const coreArtifactsPath: string = argv.core; - const origin: string = argv.origin; - const destination: string = argv.destination; + const coreArtifactsPath: string | undefined = argv.core; + const origin: string | undefined = argv.origin; + const destination: string | undefined = argv.destination; const timeoutSec: number = argv.timeout; const skipWaitForDelivery: boolean = argv.quick; await sendTestMessage({ @@ -97,7 +95,7 @@ const transferCommand: CommandModule = { }, type: { type: 'string', - description: 'Warp token type (native of collateral)', + description: 'Warp token type (native or collateral)', default: TokenType.collateral, choices: [TokenType.collateral, TokenType.native], }, @@ -114,11 +112,11 @@ const transferCommand: CommandModule = { handler: async (argv: any) => { const key: string = argv.key || process.env.HYP_KEY; const chainConfigPath: string = argv.chains; - const coreArtifactsPath: string = argv.core; - const origin: string = argv.origin; - const destination: string = argv.destination; + const coreArtifactsPath: string | undefined = argv.core; + const origin: string | undefined = argv.origin; + const destination: string | undefined = argv.destination; const timeoutSec: number = argv.timeout; - const routerAddress: string = argv.router; + const routerAddress: string | undefined = argv.router; const tokenType: TokenType = argv.type; const wei: string = argv.wei; const recipient: string | undefined = argv.recipient; diff --git a/typescript/cli/src/commands/status.ts b/typescript/cli/src/commands/status.ts index 6cc99db458..a7a671a63b 100644 --- a/typescript/cli/src/commands/status.ts +++ b/typescript/cli/src/commands/status.ts @@ -12,21 +12,19 @@ export const statusCommand: CommandModule = { id: { type: 'string', description: 'Message ID', - demandOption: true, }, destination: { type: 'string', description: 'Destination chain name', - demandOption: true, }, chains: chainsCommandOption, core: coreArtifactsOption, }), handler: async (argv: any) => { const chainConfigPath: string = argv.chains; - const coreArtifactsPath: string = argv.core; - const messageId: string = argv.id; - const destination: string = argv.destination; + const coreArtifactsPath: string | undefined = argv.core; + const messageId: string | undefined = argv.id; + const destination: string | undefined = argv.destination; await checkMessageStatus({ chainConfigPath, coreArtifactsPath, diff --git a/typescript/cli/src/context.ts b/typescript/cli/src/context.ts index 2f28c43ea9..26a4b9464f 100644 --- a/typescript/cli/src/context.ts +++ b/typescript/cli/src/context.ts @@ -81,11 +81,12 @@ export async function getContext

( context.signer = keyToSigner(settings.key); } if (settings.coreConfig) { - context.coreArtifacts = await runDeploymentArtifactStep( - settings.coreConfig.coreArtifactsPath, - settings.coreConfig.promptMessage || - 'Do you want to use some core deployment address artifacts? This is required for warp deployments to PI chains (non-core chains).', - ); + context.coreArtifacts = + (await runDeploymentArtifactStep( + settings.coreConfig.coreArtifactsPath, + settings.coreConfig.promptMessage || + 'Do you want to use some core deployment address artifacts? This is required for warp deployments to PI chains (non-core chains).', + )) || {}; } return context; } diff --git a/typescript/cli/src/send/message.ts b/typescript/cli/src/send/message.ts index f9c27aa76e..573c90a6b6 100644 --- a/typescript/cli/src/send/message.ts +++ b/typescript/cli/src/send/message.ts @@ -12,11 +12,10 @@ import { errorRed, log, logBlue, logGreen } from '../../logger.js'; import { MINIMUM_TEST_SEND_GAS } from '../consts.js'; import { getContext, getMergedContractAddresses } from '../context.js'; import { runPreflightChecks } from '../deploy/utils.js'; +import { runSingleChainSelectionStep } from '../utils/chains.js'; const MESSAGE_BODY = '0x48656c6c6f21'; // Hello!' -// TODO improve the UX here by making params optional and -// prompting for missing values export async function sendTestMessage({ key, chainConfigPath, @@ -28,17 +27,32 @@ export async function sendTestMessage({ }: { key: string; chainConfigPath: string; - coreArtifactsPath: string; - origin: ChainName; - destination: ChainName; + coreArtifactsPath?: string; + origin?: ChainName; + destination?: ChainName; timeoutSec: number; skipWaitForDelivery: boolean; }) { - const { signer, multiProvider, coreArtifacts } = await getContext({ - chainConfigPath, - coreConfig: { coreArtifactsPath }, - key, - }); + const { signer, multiProvider, customChains, coreArtifacts } = + await getContext({ + chainConfigPath, + coreConfig: { coreArtifactsPath }, + key, + }); + + if (!origin) { + origin = await runSingleChainSelectionStep( + customChains, + 'Select the origin chain', + ); + } + + if (!destination) { + destination = await runSingleChainSelectionStep( + customChains, + 'Select the destination chain', + ); + } await runPreflightChecks({ origin, diff --git a/typescript/cli/src/send/transfer.ts b/typescript/cli/src/send/transfer.ts index 6005107a32..c7968c9d5e 100644 --- a/typescript/cli/src/send/transfer.ts +++ b/typescript/cli/src/send/transfer.ts @@ -1,3 +1,4 @@ +import { input } from '@inquirer/prompts'; import { BigNumber, ethers } from 'ethers'; import { @@ -20,6 +21,7 @@ import { MINIMUM_TEST_SEND_GAS } from '../consts.js'; import { getContext, getMergedContractAddresses } from '../context.js'; import { runPreflightChecks } from '../deploy/utils.js'; import { assertNativeBalances, assertTokenBalance } from '../utils/balances.js'; +import { runSingleChainSelectionStep } from '../utils/chains.js'; // TODO improve the UX here by making params optional and // prompting for missing values @@ -38,21 +40,42 @@ export async function sendTestTransfer({ }: { key: string; chainConfigPath: string; - coreArtifactsPath: string; - origin: ChainName; - destination: ChainName; - routerAddress: Address; + coreArtifactsPath?: string; + origin?: ChainName; + destination?: ChainName; + routerAddress?: Address; tokenType: TokenType; wei: string; recipient?: string; timeoutSec: number; skipWaitForDelivery: boolean; }) { - const { signer, multiProvider, coreArtifacts } = await getContext({ - chainConfigPath, - coreConfig: { coreArtifactsPath }, - key, - }); + const { signer, multiProvider, customChains, coreArtifacts } = + await getContext({ + chainConfigPath, + coreConfig: { coreArtifactsPath }, + key, + }); + + if (!origin) { + origin = await runSingleChainSelectionStep( + customChains, + 'Select the origin chain', + ); + } + + if (!destination) { + destination = await runSingleChainSelectionStep( + customChains, + 'Select the destination chain', + ); + } + + if (!routerAddress) { + routerAddress = await input({ + message: 'Please specify the router address', + }); + } if (tokenType === TokenType.collateral) { await assertTokenBalance( diff --git a/typescript/cli/src/status/message.ts b/typescript/cli/src/status/message.ts index 38c333f464..0001f9be0e 100644 --- a/typescript/cli/src/status/message.ts +++ b/typescript/cli/src/status/message.ts @@ -1,7 +1,10 @@ +import { input } from '@inquirer/prompts'; + import { ChainName, HyperlaneCore } from '@hyperlane-xyz/sdk'; import { log, logBlue, logGreen } from '../../logger.js'; import { getContext, getMergedContractAddresses } from '../context.js'; +import { runSingleChainSelectionStep } from '../utils/chains.js'; export async function checkMessageStatus({ chainConfigPath, @@ -10,15 +13,28 @@ export async function checkMessageStatus({ destination, }: { chainConfigPath: string; - coreArtifactsPath: string; - messageId: string; - destination: ChainName; + coreArtifactsPath?: string; + messageId?: string; + destination?: ChainName; }) { - const { multiProvider, coreArtifacts } = await getContext({ + const { multiProvider, customChains, coreArtifacts } = await getContext({ chainConfigPath, coreConfig: { coreArtifactsPath }, }); + if (!destination) { + destination = await runSingleChainSelectionStep( + customChains, + 'Select the destination chain', + ); + } + + if (!messageId) { + messageId = await input({ + message: 'Please specify the message id', + }); + } + const mergedContractAddrs = getMergedContractAddresses(coreArtifacts); const core = HyperlaneCore.fromAddressesMap( mergedContractAddrs, From b96e69fe79d930d7bedf290cc916b82571c8777f Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 30 Nov 2023 14:09:06 -0500 Subject: [PATCH 3/6] Build MP with signer --- typescript/cli/src/context.ts | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/typescript/cli/src/context.ts b/typescript/cli/src/context.ts index 26a4b9464f..b9a06292f6 100644 --- a/typescript/cli/src/context.ts +++ b/typescript/cli/src/context.ts @@ -70,25 +70,25 @@ export async function getContext

( settings: P, ): Promise> { const customChains = readChainConfigsIfExists(settings.chainConfigPath); - const multiProvider = getMultiProvider(customChains); - const context: any = { - customChains, - multiProvider, - signer: undefined, - coreArtifacts: undefined, - }; - if (settings.key) { - context.signer = keyToSigner(settings.key); - } + const signer = settings.key ? keyToSigner(settings.key) : undefined; + const multiProvider = getMultiProvider(customChains, signer); + + let coreArtifacts = undefined; if (settings.coreConfig) { - context.coreArtifacts = + coreArtifacts = (await runDeploymentArtifactStep( settings.coreConfig.coreArtifactsPath, settings.coreConfig.promptMessage || 'Do you want to use some core deployment address artifacts? This is required for warp deployments to PI chains (non-core chains).', )) || {}; } - return context; + + return { + customChains, + signer, + multiProvider, + coreArtifacts, + } as CommandContext

; } export function getMultiProvider( From 9418ca5ff75b6902be981677571b8733b095c0af Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Thu, 30 Nov 2023 14:27:43 -0500 Subject: [PATCH 4/6] Prompt for key if not provided --- typescript/cli/src/context.test.ts | 2 +- typescript/cli/src/context.ts | 42 ++++++++++++++++++++--------- typescript/cli/src/deploy/core.ts | 2 +- typescript/cli/src/deploy/warp.ts | 2 +- typescript/cli/src/send/message.ts | 2 +- typescript/cli/src/send/transfer.ts | 2 +- 6 files changed, 35 insertions(+), 17 deletions(-) diff --git a/typescript/cli/src/context.test.ts b/typescript/cli/src/context.test.ts index a89f3ea8a9..41805197e4 100644 --- a/typescript/cli/src/context.test.ts +++ b/typescript/cli/src/context.test.ts @@ -14,7 +14,7 @@ describe('context', () => { const randomWallet = ethers.Wallet.createRandom(); const context = await getContext({ chainConfigPath: './fakePath', - key: randomWallet.privateKey, + keyConfig: { key: randomWallet.privateKey }, }); expect(!!context.multiProvider).to.be.true; expect(context.customChains).to.eql({}); diff --git a/typescript/cli/src/context.ts b/typescript/cli/src/context.ts index b9a06292f6..56d10923fe 100644 --- a/typescript/cli/src/context.ts +++ b/typescript/cli/src/context.ts @@ -1,3 +1,4 @@ +import { input } from '@inquirer/prompts'; import { ethers } from 'ethers'; import { @@ -50,7 +51,10 @@ interface ContextSettings { coreArtifactsPath?: string; promptMessage?: string; }; - key?: string; + keyConfig?: { + key?: string; + promptMessage?: string; + }; } interface CommandContextBase { @@ -59,30 +63,44 @@ interface CommandContextBase { } type CommandContext

= CommandContextBase & - (P extends { key: string } + (P extends { keyConfig: object } ? { signer: ethers.Signer } : { signer: undefined }) & (P extends { coreConfig: object } ? { coreArtifacts: HyperlaneContractsMap } : { coreArtifacts: undefined }); -export async function getContext

( - settings: P, -): Promise> { - const customChains = readChainConfigsIfExists(settings.chainConfigPath); - const signer = settings.key ? keyToSigner(settings.key) : undefined; - const multiProvider = getMultiProvider(customChains, signer); +export async function getContext

({ + chainConfigPath, + coreConfig, + keyConfig, +}: P): Promise> { + const customChains = readChainConfigsIfExists(chainConfigPath); + + let signer = undefined; + if (keyConfig) { + const key = + keyConfig.key || + (await input({ + message: + keyConfig.promptMessage || + 'Please enter a private key or use the HYP_KEY environment variable', + })); + signer = keyToSigner(key); + } let coreArtifacts = undefined; - if (settings.coreConfig) { + if (coreConfig) { coreArtifacts = (await runDeploymentArtifactStep( - settings.coreConfig.coreArtifactsPath, - settings.coreConfig.promptMessage || - 'Do you want to use some core deployment address artifacts? This is required for warp deployments to PI chains (non-core chains).', + coreConfig.coreArtifactsPath, + coreConfig.promptMessage || + 'Do you want to use some core deployment address artifacts? This is required for PI chains (non-core chains).', )) || {}; } + const multiProvider = getMultiProvider(customChains, signer); + return { customChains, signer, diff --git a/typescript/cli/src/deploy/core.ts b/typescript/cli/src/deploy/core.ts index 448659bdf6..d346f04a61 100644 --- a/typescript/cli/src/deploy/core.ts +++ b/typescript/cli/src/deploy/core.ts @@ -78,7 +78,7 @@ export async function runCoreDeploy({ }) { const { customChains, multiProvider, signer } = await getContext({ chainConfigPath, - key, + keyConfig: { key }, }); if (!chains?.length) { diff --git a/typescript/cli/src/deploy/warp.ts b/typescript/cli/src/deploy/warp.ts index eb9fd89425..778160edc3 100644 --- a/typescript/cli/src/deploy/warp.ts +++ b/typescript/cli/src/deploy/warp.ts @@ -51,7 +51,7 @@ export async function runWarpDeploy({ const { multiProvider, signer, coreArtifacts } = await getContext({ chainConfigPath, coreConfig: { coreArtifactsPath }, - key, + keyConfig: { key }, }); if (!warpConfigPath || !isFile(warpConfigPath)) { diff --git a/typescript/cli/src/send/message.ts b/typescript/cli/src/send/message.ts index 573c90a6b6..dc0a2f29cd 100644 --- a/typescript/cli/src/send/message.ts +++ b/typescript/cli/src/send/message.ts @@ -37,7 +37,7 @@ export async function sendTestMessage({ await getContext({ chainConfigPath, coreConfig: { coreArtifactsPath }, - key, + keyConfig: { key }, }); if (!origin) { diff --git a/typescript/cli/src/send/transfer.ts b/typescript/cli/src/send/transfer.ts index c7968c9d5e..73ff614dd4 100644 --- a/typescript/cli/src/send/transfer.ts +++ b/typescript/cli/src/send/transfer.ts @@ -54,7 +54,7 @@ export async function sendTestTransfer({ await getContext({ chainConfigPath, coreConfig: { coreArtifactsPath }, - key, + keyConfig: { key }, }); if (!origin) { From 6a8e2bcad80e7a0e767454cf1775432a2a7eeb18 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Fri, 1 Dec 2023 10:57:56 -0500 Subject: [PATCH 5/6] Comments --- typescript/cli/src/context.ts | 1 + typescript/cli/src/send/transfer.ts | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/typescript/cli/src/context.ts b/typescript/cli/src/context.ts index 56d10923fe..d3aa6d58e2 100644 --- a/typescript/cli/src/context.ts +++ b/typescript/cli/src/context.ts @@ -62,6 +62,7 @@ interface CommandContextBase { multiProvider: MultiProvider; } +// This makes return type dynamic based on the input settings type CommandContext

= CommandContextBase & (P extends { keyConfig: object } ? { signer: ethers.Signer } diff --git a/typescript/cli/src/send/transfer.ts b/typescript/cli/src/send/transfer.ts index 73ff614dd4..b1a54a5cfb 100644 --- a/typescript/cli/src/send/transfer.ts +++ b/typescript/cli/src/send/transfer.ts @@ -23,8 +23,6 @@ import { runPreflightChecks } from '../deploy/utils.js'; import { assertNativeBalances, assertTokenBalance } from '../utils/balances.js'; import { runSingleChainSelectionStep } from '../utils/chains.js'; -// TODO improve the UX here by making params optional and -// prompting for missing values export async function sendTestTransfer({ key, chainConfigPath, From 4099a744174ee56b99f7cef48db2eeabe60c36e1 Mon Sep 17 00:00:00 2001 From: J M Rossy Date: Fri, 1 Dec 2023 15:58:20 -0500 Subject: [PATCH 6/6] Update token readme --- solidity/contracts/token/README.md | 30 +++++------------------------- 1 file changed, 5 insertions(+), 25 deletions(-) diff --git a/solidity/contracts/token/README.md b/solidity/contracts/token/README.md index ef7e12efba..3d8c900820 100644 --- a/solidity/contracts/token/README.md +++ b/solidity/contracts/token/README.md @@ -1,8 +1,8 @@ # Hyperlane Tokens and Warp Routes -This repo contains contracts and SDK tooling for Hyperlane-connected ERC20 and ERC721 tokens. The contracts herein can be used to create [Hyperlane Warp Routes](https://docs.hyperlane.xyz/docs/deploy/deploy-warp-route) across different chains. +This repo contains contracts and SDK tooling for Hyperlane-connected ERC20 and ERC721 tokens. The contracts herein can be used to create [Hyperlane Warp Routes](https://docs.hyperlane.xyz/docs/reference/applications/warp-routes) across different chains. -For instructions on deploying Warp Routes, see [the deployment documentation](https://docs.hyperlane.xyz/docs/deploy/deploy-warp-route/deploy-a-warp-route) and the [Hyperlane-Deploy repository](https://github.com/hyperlane-xyz/hyperlane-deploy). +For instructions on deploying Warp Routes, see [the deployment documentation](https://docs.hyperlane.xyz/docs/deploy-hyperlane#deploy-a-warp-route) and the [Hyperlane CLI](https://www.npmjs.com/package/@hyperlane-xyz/cli). ## Warp Route Architecture @@ -51,7 +51,7 @@ The Token Router contract comes in several flavors and a warp route can be compo ## Interchain Security Models -Warp routes are unique amongst token bridging solutions because they provide modular security. Because the `TokenRouter` implements the `IMessageRecipient` interface, it can be configured with a custom interchain security module. Please refer to the relevant guide to specifying interchain security modules on the [Messaging API receive docs](https://docs.hyperlane.xyz/docs/apis/messaging-api/receive#interchain-security-modules). +Warp routes are unique amongst token bridging solutions because they provide modular security. Because the `TokenRouter` implements the `IMessageRecipient` interface, it can be configured with a custom interchain security module. Please refer to the relevant guide to specifying interchain security modules on the [Messaging API receive docs](https://docs.hyperlane.xyz/docs/reference/messaging/messaging-interface). ## Remote Transfer Lifecycle Diagrams @@ -67,7 +67,7 @@ interface TokenRouter { } ``` -**NOTE:** The [Relayer](https://docs.hyperlane.xyz/docs/protocol/agents/relayer) shown below must be compensated. Please refer to the relevant guide on [paying for interchain gas](https://docs.hyperlane.xyz/docs/build-with-hyperlane/guides/paying-for-interchain-gas) on the `messageID` returned from the `transferRemote` call. +**NOTE:** The [Relayer](https://docs.hyperlane.xyz/docs/operate/relayer/run-relayer) shown below must be compensated. Please refer to the details on [paying for interchain gas](https://docs.hyperlane.xyz/docs/protocol/interchain-gas-payment). Depending on the flavor of TokenRouter on the source and destination chain, this flow looks slightly different. The following diagrams illustrate these differences. @@ -227,26 +227,6 @@ graph TB | [audit-v2-remediation]() | 2023-02-15 | Hyperlane V2 Audit remediation | | [main]() | ~ | Bleeding edge | -## Setup for local development - -```sh -# Install dependencies -yarn - -# Build source and generate types -yarn build:dev -``` - -## Unit testing - -```sh -# Run all unit tests -yarn test - -# Lint check code -yarn lint -``` - ## Learn more -For more information, see the [Hyperlane introduction documentation](https://docs.hyperlane.xyz/docs/introduction/readme) or the [details about Warp Routes](https://docs.hyperlane.xyz/docs/deploy/deploy-warp-route). +For more information, see the [Hyperlane introduction documentation](https://docs.hyperlane.xyz/docs/intro).