UNPKG

@hyperlane-xyz/sdk

Version:

The official SDK for the Hyperlane Network

449 lines 18.7 kB
/** * The types defined here are the source of truth for chain metadata. * ANY CHANGES HERE NEED TO BE REFLECTED IN HYPERLANE-BASE CONFIG PARSING. */ import { z } from 'zod'; import { ModuleType } from '@hyperlane-xyz/sdk'; import { ProtocolType } from '@hyperlane-xyz/utils'; import { ChainMetadataSchemaObject } from './chainMetadataTypes.js'; import { ZHash, ZNzUint, ZUWei, ZUint } from './customZodTypes.js'; import { HyperlaneDeploymentArtifactsSchema, } from './deploymentArtifacts.js'; import { MatchingListSchema } from './matchingList.js'; export var RpcConsensusType; (function (RpcConsensusType) { RpcConsensusType["Single"] = "single"; RpcConsensusType["Fallback"] = "fallback"; RpcConsensusType["Quorum"] = "quorum"; })(RpcConsensusType || (RpcConsensusType = {})); export var AgentLogLevel; (function (AgentLogLevel) { AgentLogLevel["Off"] = "off"; AgentLogLevel["Error"] = "error"; AgentLogLevel["Warn"] = "warn"; AgentLogLevel["Info"] = "info"; AgentLogLevel["Debug"] = "debug"; AgentLogLevel["Trace"] = "trace"; })(AgentLogLevel || (AgentLogLevel = {})); export var AgentLogFormat; (function (AgentLogFormat) { AgentLogFormat["Json"] = "json"; AgentLogFormat["Compact"] = "compact"; AgentLogFormat["Full"] = "full"; AgentLogFormat["Pretty"] = "pretty"; })(AgentLogFormat || (AgentLogFormat = {})); export var AgentIndexMode; (function (AgentIndexMode) { AgentIndexMode["Block"] = "block"; AgentIndexMode["Sequence"] = "sequence"; })(AgentIndexMode || (AgentIndexMode = {})); export var AgentSignerKeyType; (function (AgentSignerKeyType) { AgentSignerKeyType["Aws"] = "aws"; AgentSignerKeyType["Hex"] = "hexKey"; AgentSignerKeyType["Node"] = "node"; AgentSignerKeyType["Cosmos"] = "cosmosKey"; AgentSignerKeyType["Starknet"] = "starkKey"; })(AgentSignerKeyType || (AgentSignerKeyType = {})); export var AgentSealevelPriorityFeeOracleType; (function (AgentSealevelPriorityFeeOracleType) { AgentSealevelPriorityFeeOracleType["Helius"] = "helius"; AgentSealevelPriorityFeeOracleType["Constant"] = "constant"; })(AgentSealevelPriorityFeeOracleType || (AgentSealevelPriorityFeeOracleType = {})); export var AgentSealevelHeliusFeeLevel; (function (AgentSealevelHeliusFeeLevel) { AgentSealevelHeliusFeeLevel["Recommended"] = "recommended"; AgentSealevelHeliusFeeLevel["Min"] = "min"; AgentSealevelHeliusFeeLevel["Low"] = "low"; AgentSealevelHeliusFeeLevel["Medium"] = "medium"; AgentSealevelHeliusFeeLevel["High"] = "high"; AgentSealevelHeliusFeeLevel["VeryHigh"] = "veryHigh"; AgentSealevelHeliusFeeLevel["UnsafeMax"] = "unsafeMax"; })(AgentSealevelHeliusFeeLevel || (AgentSealevelHeliusFeeLevel = {})); export var AgentSealevelTransactionSubmitterType; (function (AgentSealevelTransactionSubmitterType) { AgentSealevelTransactionSubmitterType["Rpc"] = "rpc"; AgentSealevelTransactionSubmitterType["Jito"] = "jito"; })(AgentSealevelTransactionSubmitterType || (AgentSealevelTransactionSubmitterType = {})); const AgentSignerHexKeySchema = z .object({ type: z.literal(AgentSignerKeyType.Hex).optional(), key: ZHash, }) .describe('A local hex key'); const AgentSignerAwsKeySchema = z .object({ type: z.literal(AgentSignerKeyType.Aws).optional(), id: z.string().describe('The UUID identifying the AWS KMS key'), region: z.string().describe('The AWS region'), }) .describe('An AWS signer. Note that AWS credentials must be inserted into the env separately.'); const AgentSignerCosmosKeySchema = z .object({ type: z.literal(AgentSignerKeyType.Cosmos), prefix: z.string().describe('The bech32 prefix for the cosmos address'), key: ZHash, }) .describe('Cosmos key'); const AgentSignerNodeSchema = z .object({ type: z.literal(AgentSignerKeyType.Node), }) .describe('Assume the local node will sign on RPC calls automatically'); const AgentSignerSchema = z.union([ AgentSignerHexKeySchema, AgentSignerAwsKeySchema, AgentSignerCosmosKeySchema, AgentSignerNodeSchema, ]); // Additional chain metadata for Cosmos chains required by the agents. const AgentCosmosChainMetadataSchema = z.object({ canonicalAsset: z .string() .describe('The name of the canonical asset for this chain, usually in "micro" form, e.g. untrn'), gasPrice: z.object({ denom: z .string() .describe('The coin denom, usually in "micro" form, e.g. untrn'), amount: z .string() .regex(/^(\d*[.])?\d+$/) .describe('The gas price, in denom, to pay for each unit of gas'), }), contractAddressBytes: z .number() .int() .positive() .lte(32) .describe('The number of bytes used to represent a contract address.'), }); const AgentSealevelChainMetadataSchema = z.object({ priorityFeeOracle: z .union([ z.object({ type: z.literal(AgentSealevelPriorityFeeOracleType.Helius), url: z.string(), // TODO add options feeLevel: z.nativeEnum(AgentSealevelHeliusFeeLevel), }), z.object({ type: z.literal(AgentSealevelPriorityFeeOracleType.Constant), // In microlamports fee: ZUWei, }), ]) .optional(), transactionSubmitter: z .object({ type: z.nativeEnum(AgentSealevelTransactionSubmitterType), url: z.string().optional(), }) .optional(), }); export const AgentChainMetadataSchema = ChainMetadataSchemaObject.merge(HyperlaneDeploymentArtifactsSchema) .extend({ customRpcUrls: z .string() .optional() .describe('Specify a comma separated list of custom RPC URLs to use for this chain. If not specified, the default RPC urls will be used.'), rpcConsensusType: z .nativeEnum(RpcConsensusType) .describe('The consensus type to use when multiple RPCs are configured.') .optional(), signer: AgentSignerSchema.optional().describe('The signer to use for this chain'), index: z .object({ from: ZUint.optional().describe('The starting block from which to index events.'), chunk: ZNzUint.optional().describe('The number of blocks to index at a time.'), mode: z .nativeEnum(AgentIndexMode) .optional() .describe('The indexing method to use for this chain; will attempt to choose a suitable default if not specified.'), }) .optional(), }) .merge(AgentCosmosChainMetadataSchema.partial()) .merge(AgentSealevelChainMetadataSchema.partial()) .refine((metadata) => { // Make sure that the signer is valid for the protocol const signerType = metadata.signer?.type; // If no signer is specified, no validation is needed if (signerType === undefined) { return true; } switch (metadata.protocol) { case ProtocolType.Ethereum: if (![ AgentSignerKeyType.Hex, signerType === AgentSignerKeyType.Aws, signerType === AgentSignerKeyType.Node, ].includes(signerType)) { return false; } break; case ProtocolType.Cosmos: case ProtocolType.CosmosNative: if (![AgentSignerKeyType.Cosmos].includes(signerType)) { return false; } break; case ProtocolType.Sealevel: if (![AgentSignerKeyType.Hex].includes(signerType)) { return false; } break; default: // Just accept it if we don't know the protocol } // If the protocol type is Cosmos, require everything in AgentCosmosChainMetadataSchema if (metadata.protocol === ProtocolType.Cosmos || metadata.protocol === ProtocolType.CosmosNative) { if (!AgentCosmosChainMetadataSchema.safeParse(metadata).success) { return false; } } // If the protocol type is Sealevel, require everything in AgentSealevelChainMetadataSchema if (metadata.protocol === ProtocolType.Sealevel) { if (!AgentSealevelChainMetadataSchema.safeParse(metadata).success) { return false; } } return true; }); export const AgentConfigSchema = z.object({ metricsPort: ZNzUint.lte(65535) .optional() .describe('The port to expose prometheus metrics on. Accessible via `GET /metrics`.'), chains: z .record(AgentChainMetadataSchema) .describe('Chain metadata for all chains that the agent will index.') .superRefine((data, ctx) => { for (const c in data) { if (c != data[c].name) { ctx.addIssue({ message: `Chain name ${c} does not match chain name in metadata ${data[c].name}`, code: z.ZodIssueCode.custom, }); } } }), defaultSigner: AgentSignerSchema.optional().describe('Default signer to use for any chains that have not defined their own.'), log: z .object({ format: z .nativeEnum(AgentLogFormat) .optional() .describe('The format to use for tracing logs.'), level: z .nativeEnum(AgentLogLevel) .optional() .describe("The log level to use for the agent's logs."), }) .optional(), }); const CommaSeparatedChainList = z.string().regex(/^[a-z0-9]+(,[a-z0-9]+)*$/); const CommaSeparatedDomainList = z.string().regex(/^\d+(,\d+)*$/); export var GasPaymentEnforcementPolicyType; (function (GasPaymentEnforcementPolicyType) { GasPaymentEnforcementPolicyType["None"] = "none"; GasPaymentEnforcementPolicyType["Minimum"] = "minimum"; GasPaymentEnforcementPolicyType["OnChainFeeQuoting"] = "onChainFeeQuoting"; })(GasPaymentEnforcementPolicyType || (GasPaymentEnforcementPolicyType = {})); const GasPaymentEnforcementBaseSchema = z.object({ matchingList: MatchingListSchema.optional().describe('An optional matching list, any message that matches will use this policy. By default all messages will match.'), }); const GasPaymentEnforcementSchema = z.union([ GasPaymentEnforcementBaseSchema.extend({ type: z.literal(GasPaymentEnforcementPolicyType.None).optional(), }), GasPaymentEnforcementBaseSchema.extend({ type: z.literal(GasPaymentEnforcementPolicyType.Minimum).optional(), payment: ZUWei, }), GasPaymentEnforcementBaseSchema.extend({ type: z.literal(GasPaymentEnforcementPolicyType.OnChainFeeQuoting), gasFraction: z .string() .regex(/^\d+ ?\/ ?[1-9]\d*$/) .optional(), }), ]); const MetricAppContextSchema = z.object({ name: z.string().min(1), matchingList: MatchingListSchema.describe('A matching list, any message that matches will be classified as this app context.'), }); export var IsmCachePolicy; (function (IsmCachePolicy) { IsmCachePolicy["MessageSpecific"] = "messageSpecific"; IsmCachePolicy["IsmSpecific"] = "ismSpecific"; })(IsmCachePolicy || (IsmCachePolicy = {})); export var IsmCacheSelectorType; (function (IsmCacheSelectorType) { IsmCacheSelectorType["DefaultIsm"] = "defaultIsm"; IsmCacheSelectorType["AppContext"] = "appContext"; })(IsmCacheSelectorType || (IsmCacheSelectorType = {})); const IsmCacheSelector = z.discriminatedUnion('type', [ z.object({ type: z.literal(IsmCacheSelectorType.DefaultIsm), }), z.object({ type: z.literal(IsmCacheSelectorType.AppContext), context: z.string(), }), ]); const IsmCacheConfigSchema = z.object({ selector: IsmCacheSelector.describe('The selector to use for the ISM cache policy'), moduleTypes: z .array(z.nativeEnum(ModuleType)) .describe('The ISM module types to use the cache policy for.'), chains: z .array(z.string()) .optional() .describe('The chains to use the cache policy for. If not specified, all chains will be used.'), cachePolicy: z .nativeEnum(IsmCachePolicy) .describe('The cache policy to use.'), }); export const RelayerAgentConfigSchema = AgentConfigSchema.extend({ db: z .string() .min(1) .optional() .describe('The path to the relayer database.'), relayChains: CommaSeparatedChainList.describe('Comma separated list of chains to relay messages between.'), gasPaymentEnforcement: z .union([z.array(GasPaymentEnforcementSchema), z.string().min(1)]) .optional() .describe('The gas payment enforcement configuration as JSON. Expects an ordered array of `GasPaymentEnforcementConfig`.'), whitelist: z .union([MatchingListSchema, z.string().min(1)]) .optional() .describe('If no whitelist is provided ALL messages will be considered on the whitelist.'), blacklist: z .union([MatchingListSchema, z.string().min(1)]) .optional() .describe('If no blacklist is provided ALL will be considered to not be on the blacklist.'), addressBlacklist: z .string() .optional() .describe('Comma separated list of addresses to blacklist.'), transactionGasLimit: ZUWei.optional().describe('This is optional. If not specified, any amount of gas will be valid, otherwise this is the max allowed gas in wei to relay a transaction.'), skipTransactionGasLimitFor: CommaSeparatedDomainList.optional().describe('Comma separated List of chain names to skip applying the transaction gas limit to.'), allowLocalCheckpointSyncers: z .boolean() .optional() .describe('If true, allows local storage based checkpoint syncers. Not intended for production use.'), metricAppContexts: z .union([z.array(MetricAppContextSchema), z.string().min(1)]) .optional() .describe('A list of app contexts and their matching lists to use for metrics. A message will be classified as the first matching app context.'), ismCacheConfigs: z .union([z.array(IsmCacheConfigSchema), z.string().min(1)]) .optional() .describe('The ISM cache configs to be used. If not specified, default caching will be used.'), allowContractCallCaching: z .boolean() .optional() .describe('If true, allows caching of certain contract calls that can be appropriately cached.'), txIdIndexingEnabled: z .boolean() .optional() .describe('Whether to enable TX ID based indexing for hook events given indexed messages'), igpIndexingEnabled: z .boolean() .optional() .describe('Whether to enable IGP indexing'), }); export const ScraperAgentConfigSchema = AgentConfigSchema.extend({ db: z.string().min(1).describe('Database connection string'), chainsToScrape: CommaSeparatedChainList.describe('Comma separated list of chain names to scrape'), }); export const ValidatorAgentConfigSchema = AgentConfigSchema.extend({ db: z .string() .min(1) .optional() .describe('The path to the validator database.'), originChainName: z .string() .min(1) .describe('Name of the chain to validate messages on'), validator: AgentSignerSchema.describe('The validator attestation signer'), checkpointSyncer: z.discriminatedUnion('type', [ z .object({ type: z.literal('localStorage'), path: z.string().min(1).describe('Path to the local storage location'), }) .describe('A local checkpoint syncer'), z .object({ type: z.literal('s3'), bucket: z.string().min(1), region: z.string().min(1), folder: z .string() .min(1) .optional() .describe('The folder/key-prefix to use, defaults to the root of the bucket'), }) .describe('A checkpoint syncer that uses S3'), z .object({ type: z.literal('gcs'), bucket: z.string().min(1), folder: z .string() .min(1) .optional() .describe('The folder to use, defaults to the root of the bucket'), service_account_key: z .string() .min(1) .optional() .describe('The path to GCS service account key file'), user_secrets: z .string() .min(1) .optional() .describe('The path to GCS user secret file'), }) .describe('A checkpoint syncer that uses Google Cloud Storage'), ]), interval: ZUint.optional().describe('How long to wait between checking for new checkpoints in seconds.'), }); // Note this works well for EVM chains only, and likely needs some love // before being useful for non-EVM chains. export function buildAgentConfig(chains, multiProvider, addresses, startBlocks, additionalConfig) { const chainConfigs = {}; for (const chain of [...chains].sort()) { const metadata = multiProvider.tryGetChainMetadata(chain); // Cosmos Native chains have the correct gRPC URL format in the registry. So only delete the gRPC URL for legacy Cosmos chains. if (metadata?.protocol === ProtocolType.Cosmos) { // Note: the gRPC URL format in the registry lacks a correct http:// or https:// prefix at the moment, // which is expected by the agents. For now, we intentionally skip this. delete metadata.grpcUrls; } // Delete transaction overrides for all Cosmos chains. if (metadata?.protocol === ProtocolType.Cosmos || metadata?.protocol === ProtocolType.CosmosNative) { // The agents expect gasPrice.amount and gasPrice.denom and ignore the transaction overrides. // To reduce confusion when looking at the config, we remove the transaction overrides. delete metadata.transactionOverrides; } const chainConfig = { ...metadata, ...addresses[chain], ...(additionalConfig ? additionalConfig[chain] : {}), ...(startBlocks[chain] !== undefined && { index: { from: startBlocks[chain], }, }), }; chainConfigs[chain] = chainConfig; } return { chains: chainConfigs, }; } //# sourceMappingURL=agentConfig.js.map