deploy-chain
Deploy a new blockchain to the Hyperlane network by specifying chain details like name, ID, RPC URL, and token information for cross-chain connectivity.
Instructions
Deploys a new chain to the Hyperlane network.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| chainName | Yes | Name of the chain to deploy | |
| chainId | Yes | Chain ID of the chain to deploy | |
| rpcUrl | Yes | RPC URL for the chain | |
| tokenSymbol | Yes | Native token symbol | |
| tokenName | Yes | Native token name | |
| isTestnet | No | Whether this is a testnet chain |
Implementation Reference
- src/index.ts:585-703 (handler)Main handler for 'deploy-chain' tool: loads existing config or creates new chain config, deploys core contracts using runCoreDeploy, generates metadata and agent configs.async ({ chainName, chainId, rpcUrl, tokenSymbol, tokenName, isTestnet }) => { const existingConfig = await loadChainDeployConfig(chainName); if (existingConfig) { server.server.sendLoggingMessage({ level: 'info', data: `Chain deployment config already exists for ${chainName}. Using existing config.`, }); return { content: [ { type: 'text', text: `Chain config already exists. Skipping config creation.\n${JSON.stringify( existingConfig, null, 2 )}`, }, ], }; } server.server.sendLoggingMessage({ level: 'info', data: `Deploying chain ${chainName} with ID ${chainId}...`, }); // Step 1: Create Chain Config + Save const chainConfig = { chainName, chainId, rpcUrl, tokenSymbol, tokenName, isTestnet, }; await createChainConfig({ config: chainConfig, registry, }); server.server.sendLoggingMessage({ level: 'info', data: `Chain config created successfully: ${JSON.stringify( chainConfig, null, 2 )}`, }); // Step 2: Deploy Core Contracts const deployConfig = { config: chainConfig, registry }; // server.server.sendLoggingMessage({ // level: 'info', // data: `this is the deploy config: ${JSON.stringify(deployConfig, null, 2)}`, // }); const deployedAddress = await runCoreDeploy(deployConfig); server.server.sendLoggingMessage({ level: 'info', data: `Core contracts deployed successfully for ${chainName}. Deployed address: ${JSON.stringify( deployedAddress, null, 2 )}`, }); // Step 3: Create Agent Configs const metadata = { [chainName]: { name: chainName, displayName: chainName, chainId, domainId: chainId, protocol: ProtocolType.Ethereum, rpcUrls: [{ http: rpcUrl }], isTestnet, }, } as ChainMap<ChainMetadata>; server.server.sendLoggingMessage({ level: 'info', data: `Create metadata for ${chainName}: ${JSON.stringify( metadata, null, 2 )}`, }); const multiProvider = new MultiProvider(metadata, { signers: { [signer.address]: signer, }, }); const outPath = path.join(mcpDir, 'agents'); await createAgentConfigs(registry, multiProvider, outPath, chainName); server.server.sendLoggingMessage({ level: 'info', data: `✅ Chain deployment and agent config creation complete for ${chainName}`, }); return { content: [ { type: 'text', text: `✅ Successfully deployed ${chainName} and generated agent config.\n\nSaved config: ${JSON.stringify( chainConfig, null, 2 )}`, }, ], }; }
- src/index.ts:575-584 (schema)Zod schema defining input parameters for the 'deploy-chain' tool.chainName: z.string().describe('Name of the chain to deploy'), chainId: z.number().describe('Chain ID of the chain to deploy'), rpcUrl: z.string().url().describe('RPC URL for the chain'), tokenSymbol: z.string().describe('Native token symbol'), tokenName: z.string().describe('Native token name'), isTestnet: z .boolean() .default(false) .describe('Whether this is a testnet chain'), },
- src/index.ts:572-704 (registration)Registration of the 'deploy-chain' tool on the MCP server with name, description, input schema, and handler function.'deploy-chain', 'Deploys a new chain to the Hyperlane network.', { chainName: z.string().describe('Name of the chain to deploy'), chainId: z.number().describe('Chain ID of the chain to deploy'), rpcUrl: z.string().url().describe('RPC URL for the chain'), tokenSymbol: z.string().describe('Native token symbol'), tokenName: z.string().describe('Native token name'), isTestnet: z .boolean() .default(false) .describe('Whether this is a testnet chain'), }, async ({ chainName, chainId, rpcUrl, tokenSymbol, tokenName, isTestnet }) => { const existingConfig = await loadChainDeployConfig(chainName); if (existingConfig) { server.server.sendLoggingMessage({ level: 'info', data: `Chain deployment config already exists for ${chainName}. Using existing config.`, }); return { content: [ { type: 'text', text: `Chain config already exists. Skipping config creation.\n${JSON.stringify( existingConfig, null, 2 )}`, }, ], }; } server.server.sendLoggingMessage({ level: 'info', data: `Deploying chain ${chainName} with ID ${chainId}...`, }); // Step 1: Create Chain Config + Save const chainConfig = { chainName, chainId, rpcUrl, tokenSymbol, tokenName, isTestnet, }; await createChainConfig({ config: chainConfig, registry, }); server.server.sendLoggingMessage({ level: 'info', data: `Chain config created successfully: ${JSON.stringify( chainConfig, null, 2 )}`, }); // Step 2: Deploy Core Contracts const deployConfig = { config: chainConfig, registry }; // server.server.sendLoggingMessage({ // level: 'info', // data: `this is the deploy config: ${JSON.stringify(deployConfig, null, 2)}`, // }); const deployedAddress = await runCoreDeploy(deployConfig); server.server.sendLoggingMessage({ level: 'info', data: `Core contracts deployed successfully for ${chainName}. Deployed address: ${JSON.stringify( deployedAddress, null, 2 )}`, }); // Step 3: Create Agent Configs const metadata = { [chainName]: { name: chainName, displayName: chainName, chainId, domainId: chainId, protocol: ProtocolType.Ethereum, rpcUrls: [{ http: rpcUrl }], isTestnet, }, } as ChainMap<ChainMetadata>; server.server.sendLoggingMessage({ level: 'info', data: `Create metadata for ${chainName}: ${JSON.stringify( metadata, null, 2 )}`, }); const multiProvider = new MultiProvider(metadata, { signers: { [signer.address]: signer, }, }); const outPath = path.join(mcpDir, 'agents'); await createAgentConfigs(registry, multiProvider, outPath, chainName); server.server.sendLoggingMessage({ level: 'info', data: `✅ Chain deployment and agent config creation complete for ${chainName}`, }); return { content: [ { type: 'text', text: `✅ Successfully deployed ${chainName} and generated agent config.\n\nSaved config: ${JSON.stringify( chainConfig, null, 2 )}`, }, ], }; } );
- src/hyperlaneDeployer.ts:212-315 (helper)Core helper function that performs the actual deployment of Hyperlane core contracts to the specified chain, including setup, pre-checks, module creation, deployment, and registry update.export async function runCoreDeploy( config: CoreDeployConfig ): Promise<Record<string, string>> { if (!process.env.PRIVATE_KEY) { throw new Error('PRIVATE_KEY environment variable is required'); } const signer = privateKeyToSigner(process.env.PRIVATE_KEY); const chain = config.config.chainName; const metadata: ChainMetadata = { name: chain, displayName: chain, chainId: config.config.chainId, domainId: Number(config.config.chainId), //@ts-ignore protocol: ProtocolType.Ethereum, rpcUrls: [ { http: config.config.rpcUrl, }, ], isTestnet: config.config.isTestnet, }; const multiProvider = new MultiProvider( { [chain]: metadata, }, { signers: { [chain]: signer, }, } ); const userAddress = signer.address; logger.info(`Preparing to deploy core contracts to ${chain}`); const initialBalances = await prepareDeploy({ userAddress, chains: [chain], multiProvider, }); logger.info(`Initial balances: ${initialBalances}`); await runDeployPlanStep(config.registry, chain, multiProvider); logger.info(`Predepoly checks complete`); const coreConfig = await InitializeDeployment(); writeYamlOrJson( path.join( process.env.CACHE_DIR || process.env.HOME!, '.hyperlane-mcp', 'chains', `${chain}-core-config.yaml` ), coreConfig, 'yaml' ); logger.info(`Core config: ${JSON.stringify(coreConfig, null, 2)}`); logger.info(`Creating core module...`); let evmCoreModule: EvmCoreModule; try { evmCoreModule = await EvmCoreModule.create({ chain, config: coreConfig, multiProvider, // contractVerifier, }); // logger.info( // `Core module created: ${JSON.stringify(evmCoreModule, null, 2)}` // ); } catch (e) { logger.error(`Error in creating core module: ${e}`); logger.error( `Error in creating core module: ${JSON.stringify(e, null, 2)}` ); throw new Error(`Error in creating core module: ${e}`); } logger.info(`Core module created: ${JSON.stringify(evmCoreModule, null, 2)}`); logger.info(`Deploying core contracts to ${chain}`); await completeDeploy(multiProvider, initialBalances, userAddress, [chain]); const deployedAddresses = evmCoreModule.serialize(); logger.info( `Deployed addresses: ${JSON.stringify(deployedAddresses, null, 2)}` ); config.registry.updateChain({ chainName: chain, addresses: deployedAddresses, }); return deployedAddresses; // Return the deployed addresses as the function output }
- src/hyperlaneDeployer.ts:131-178 (helper)Helper function to create and register chain metadata in the registry.export async function createChainConfig( options: ChainConfigOptions ): Promise<void> { const provider = new ethers.providers.JsonRpcProvider(options.config.rpcUrl); const metadata: ChainMetadata = { name: options.config.chainName, displayName: options.config.chainName, chainId: options.config.chainId, domainId: Number(options.config.chainId), //@ts-ignore protocol: ProtocolType.Ethereum, rpcUrls: [ { http: options.config.rpcUrl, }, ], isTestnet: options.config.isTestnet, }; await addNativeTokenConfig(metadata, { tokenSymbol: options.config.tokenSymbol, tokenName: options.config.tokenName, }); logger.info(`Chain metadata: ${metadata}`); const parseResult = ChainMetadataSchema.safeParse(metadata); logger.info(`Chain metadata: ${parseResult}`); if (parseResult.success) { const metadataYaml = yamlStringify(metadata, { indent: 2, sortMapEntries: true, }); await options.registry.addChain({ chainName: metadata.name, metadata }); logger.info(`Chain metadata created: ${metadataYaml}`); } else { console.error(parseResult.error); // FIX: Properly format the error message using template literals throw new Error( `Error in creating chain metadata: ${JSON.stringify(metadata)}: ${ parseResult.error }` ); } }