Skip to main content
Glama
utils.ts6.62 kB
import { BaseRegistry, ChainAddresses, IRegistry, } from '@hyperlane-xyz/registry'; import { AgentConfig, AgentConfigSchema, ChainMap, ChainMetadata, ChainName, HookConfig, HookType, HyperlaneCore, IsmConfig, IsmType, MultiProvider, } from '@hyperlane-xyz/sdk'; import { ensure0x, objMap, promiseObjAll, ProtocolType, } from '@hyperlane-xyz/utils'; import { ethers } from 'ethers'; import fs from 'fs'; import logger from './logger.js'; export function privateKeyToSigner(key: string): ethers.Wallet { if (!key) throw new Error('No private key provided'); const formattedKey = key.trim().toLowerCase(); if (ethers.utils.isHexString(ensure0x(formattedKey))) return new ethers.Wallet(ensure0x(formattedKey)); else if (formattedKey.split(' ').length >= 6) return ethers.Wallet.fromMnemonic(formattedKey); else throw new Error('Invalid private key format'); } export function callWithConfigCreationLogs<T extends IsmConfig | HookConfig>( fn: (...args: any[]) => Promise<T>, type: IsmType | HookType ) { return async (...args: any[]): Promise<T> => { console.log(`Creating ${type}...`); try { const result = await fn(...args); return result; } finally { console.log(`Created ${type}!`); } }; } export async function requestAndSaveApiKeys( chains: ChainName[], chainMetadata: ChainMap<ChainMetadata>, registry: IRegistry ): Promise<ChainMap<string>> { const apiKeys: ChainMap<string> = {}; for (const chain of chains) { if (chainMetadata[chain]?.blockExplorers?.[0]?.apiKey) { apiKeys[chain] = chainMetadata[chain]!.blockExplorers![0]!.apiKey!; continue; } chainMetadata[chain].blockExplorers![0].apiKey = apiKeys[chain]; // await registry.updateChain({ // chainName: chain, // metadata: chainMetadata[chain], // }); } return apiKeys; } export function transformChainMetadataForDisplay(chainMetadata: ChainMetadata) { return { Name: chainMetadata.name, 'Display Name': chainMetadata.displayName, 'Chain ID': chainMetadata.chainId, 'Domain ID': chainMetadata.domainId, Protocol: chainMetadata.protocol, 'JSON RPC URL': chainMetadata.rpcUrls[0].http, 'Native Token: Symbol': chainMetadata.nativeToken?.symbol, 'Native Token: Name': chainMetadata.nativeToken?.name, 'Native Token: Decimals': chainMetadata.nativeToken?.decimals, }; } export async function confirmExistingMailbox( registry: BaseRegistry, chain: ChainName ) { const addresses = await registry.getChainAddresses(chain); logger.info(`Mailbox address for ${chain} is ${addresses}`); if (addresses?.mailbox) { logger.error('Mailbox already exists at address ' + addresses.mailbox); } } export async function nativeBalancesAreSufficient( multiProvider: MultiProvider, chains: ChainName[], minGas: string ) { const sufficientBalances: boolean[] = []; for (const chain of chains) { // Only Ethereum chains are supported if (multiProvider.getProtocol(chain) !== ProtocolType.Ethereum) { // logGray(`Skipping balance check for non-EVM chain: ${chain}`); continue; } const address = multiProvider.getSigner(chain).getAddress(); const provider = multiProvider.getProvider(chain); const gasPrice = await provider.getGasPrice(); const minBalanceWei = gasPrice.mul(minGas).toString(); const minBalance = ethers.utils.formatEther(minBalanceWei.toString()); const balanceWei = await multiProvider .getProvider(chain) .getBalance(address); const balance = ethers.utils.formatEther(balanceWei.toString()); if (balanceWei.lt(minBalanceWei)) { const symbol = multiProvider.getChainMetadata(chain).nativeToken?.symbol ?? 'ETH'; sufficientBalances.push(false); } } const allSufficient = sufficientBalances.every((sufficient) => sufficient); if (allSufficient) { // logGreen('✅ Balances are sufficient'); return true; } else { return false; } } export function assertSigner(signer: ethers.Signer) { if (!signer || !ethers.Signer.isSigner(signer)) throw new Error('Signer is invalid'); } export function filterAddresses( addresses: ChainMap<ChainAddresses>, chains?: string[] ) { if (!chains) { return addresses; } const filteredAddresses: ChainMap<ChainAddresses> = {}; for (const chain in addresses) { if (chains.includes(chain)) { filteredAddresses[chain] = addresses[chain]; } } return filteredAddresses; } export async function getStartBlocks( chainAddresses: ChainMap<ChainAddresses>, core: HyperlaneCore, chainMetadata: ChainMap<ChainMetadata> ): Promise<ChainMap<number | undefined>> { return promiseObjAll( objMap(chainAddresses, async (chain, _) => { const indexFrom = chainMetadata[chain].index?.from; logger.info( `Index from for ${chain}: ${indexFrom}, chain metadata: ${JSON.stringify( chainMetadata[chain] )}` ); if (indexFrom !== undefined) { return indexFrom; } const mailbox = core.getContracts(chain).mailbox; try { const deployedBlock = await mailbox.deployedBlock(); return deployedBlock.toNumber(); } catch { console.log( `❌ Failed to get deployed block to set an index for ${chain}, this is potentially an issue with rpc provider or a misconfiguration` ); return undefined; } }) ); } export async function handleMissingInterchainGasPaymaster( chainAddresses: ChainMap<ChainAddresses> ) { for (const [chain, addressRecord] of Object.entries(chainAddresses)) { if (!addressRecord.interchainGasPaymaster) { console.warn(`Interchain gas paymaster not found for chain ${chain}`); } chainAddresses[chain].interchainGasPaymaster = ethers.constants.AddressZero; } } export function validateAgentConfig(agentConfig: AgentConfig) { const result = AgentConfigSchema.safeParse(agentConfig); if (!result.success) { const errorMessage = result.error.toString(); console.warn( `\nAgent config is invalid, this is possibly due to required contracts not being deployed. See details below:\n${errorMessage}` ); } // else { // console.log('✅ Agent config successfully created'); // } } // Utility to create directories if they don't exist export const createDirectory = (directoryPath: string): void => { if (!fs.existsSync(directoryPath)) { fs.mkdirSync(directoryPath, { recursive: true }); logger.info(`Created directory: ${directoryPath}`); } };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Suryansh-23/hyperlane-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server