@hyperlane-xyz/sdk
Version:
The official SDK for the Hyperlane Network
341 lines • 13.5 kB
JavaScript
/**
* 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 { ProtocolType, objMerge } from '@hyperlane-xyz/utils';
import { ZChainName, ZNzUint, ZUint } from './customZodTypes.js';
export var EthJsonRpcBlockParameterTag;
(function (EthJsonRpcBlockParameterTag) {
EthJsonRpcBlockParameterTag["Earliest"] = "earliest";
EthJsonRpcBlockParameterTag["Latest"] = "latest";
EthJsonRpcBlockParameterTag["Safe"] = "safe";
EthJsonRpcBlockParameterTag["Finalized"] = "finalized";
EthJsonRpcBlockParameterTag["Pending"] = "pending";
})(EthJsonRpcBlockParameterTag || (EthJsonRpcBlockParameterTag = {}));
export var ExplorerFamily;
(function (ExplorerFamily) {
ExplorerFamily["Etherscan"] = "etherscan";
ExplorerFamily["Blockscout"] = "blockscout";
ExplorerFamily["Routescan"] = "routescan";
ExplorerFamily["Voyager"] = "voyager";
ExplorerFamily["ZkSync"] = "zksync";
ExplorerFamily["Other"] = "other";
})(ExplorerFamily || (ExplorerFamily = {}));
export var ChainTechnicalStack;
(function (ChainTechnicalStack) {
ChainTechnicalStack["ArbitrumNitro"] = "arbitrumnitro";
ChainTechnicalStack["OpStack"] = "opstack";
ChainTechnicalStack["PolygonCDK"] = "polygoncdk";
ChainTechnicalStack["PolkadotSubstrate"] = "polkadotsubstrate";
ChainTechnicalStack["ZkSync"] = "zksync";
ChainTechnicalStack["Other"] = "other";
})(ChainTechnicalStack || (ChainTechnicalStack = {}));
export var ChainStatus;
(function (ChainStatus) {
ChainStatus["Live"] = "live";
ChainStatus["Disabled"] = "disabled";
})(ChainStatus || (ChainStatus = {}));
export var ChainDisabledReason;
(function (ChainDisabledReason) {
// chain is having issues with the RPC url
ChainDisabledReason["BadRpc"] = "badrpc";
// chain is not being used anymore
ChainDisabledReason["Deprecated"] = "deprecated";
// chain is not public or launched yet
ChainDisabledReason["Private"] = "private";
// chain is not available due to upgrades or maintenance
ChainDisabledReason["Unavailable"] = "unavailable";
ChainDisabledReason["Other"] = "other";
})(ChainDisabledReason || (ChainDisabledReason = {}));
export const RpcUrlSchema = z.object({
http: z
.string()
.url()
.describe('The HTTP URL of the RPC endpoint (preferably HTTPS).'),
concurrency: z
.number()
.int()
.positive()
.optional()
.describe('Maximum number of concurrent RPC requests.'),
webSocket: z
.string()
.optional()
.describe('The WSS URL if the endpoint also supports websockets.'),
pagination: z
.object({
maxBlockRange: ZNzUint.optional().describe('The maximum range between block numbers for which the RPC can query data'),
minBlockNumber: ZUint.optional().describe('The absolute minimum block number that this RPC supports.'),
maxBlockAge: ZNzUint.optional().describe('The relative different from latest block that this RPC supports.'),
})
.optional()
.describe('Limitations on the block range/age that can be queried.'),
retry: z
.object({
maxRequests: ZNzUint.describe('The maximum number of requests to attempt before failing.'),
baseRetryMs: ZNzUint.describe('The base retry delay in milliseconds.'),
})
.optional()
.describe('Default retry settings to be used by a provider such as MultiProvider.'),
public: z
.boolean()
.optional()
.describe('Flag if the RPC is publicly available.'),
});
export const BlockExplorerSchema = z.object({
name: z.string().describe('A human readable name for the explorer.'),
url: z.string().url().describe('The base URL for the explorer.'),
apiUrl: z
.string()
.url()
.describe('The base URL for requests to the explorer API.'),
apiKey: z
.string()
.optional()
.describe('An API key for the explorer (recommended for better reliability).'),
family: z
.nativeEnum(ExplorerFamily)
.optional()
.describe('The type of the block explorer. See ExplorerFamily for valid values.'),
});
export const NativeTokenSchema = z.object({
name: z.string(),
symbol: z.string(),
decimals: ZUint.lt(256),
denom: z.string().optional(),
});
export const GasPriceSchema = z.object({
denom: z.string(),
amount: z.string(),
});
export const DisabledChainSchema = z.object({
status: z
.literal(ChainStatus.Disabled)
.describe('The status that represents the chain availability. See ChainStatus for valid values.'),
reasons: z
.array(z.nativeEnum(ChainDisabledReason))
.min(1)
.describe('List of reasons explaining why the chain is disabled.'),
});
export const EnabledChainSchema = z.object({
status: z
.literal(ChainStatus.Live)
.describe('The status that represents the chain availability. See ChainStatus for valid values.'),
});
/**
* A collection of useful properties and settings for chains using Hyperlane
* Specified as a Zod schema
*/
export const ChainMetadataSchemaObject = z.object({
availability: z
.union([DisabledChainSchema, EnabledChainSchema])
.optional()
.describe('Specifies if the chain is available and the reasons why it is disabled.'),
bech32Prefix: z
.string()
.optional()
.describe('The human readable address prefix for the chains using bech32.'),
blockExplorers: z
.array(BlockExplorerSchema)
.optional()
.describe('A list of block explorers with data for this chain'),
blocks: z
.object({
confirmations: ZUint.describe('Number of blocks to wait before considering a transaction confirmed.'),
reorgPeriod: z
.union([ZUint, z.string()])
.optional()
.describe('Number of blocks before a transaction has a near-zero chance of reverting or block tag.'),
estimateBlockTime: z
.number()
.positive()
.finite()
.optional()
.describe('Rough estimate of time per block in seconds.'),
})
.optional()
.describe('Block settings for the chain/deployment.'),
bypassBatchSimulation: z
.boolean()
.optional()
.describe('Whether to bypass batch simulation for this chain.'),
chainId: z
.union([ZNzUint, z.string()])
.describe(`The chainId of the chain. Uses EIP-155 for EVM chains`),
customGrpcUrls: z
.string()
.optional()
.describe('Specify a comma separated list of custom GRPC URLs to use for this chain. If not specified, the default GRPC urls will be used.'),
deployer: z
.object({
name: z.string().describe('The name of the deployer.'),
email: z
.string()
.email()
.optional()
.describe('The email address of the deployer.'),
url: z.string().url().optional().describe('The URL of the deployer.'),
})
.optional()
.describe('Identity information of the deployer of a Hyperlane instance to this chain'),
displayName: z
.string()
.optional()
.describe('Human-readable name of the chain.'),
displayNameShort: z
.string()
.optional()
.describe('A shorter human-readable name of the chain for use in user interfaces.'),
domainId: ZNzUint.describe('The domainId of the chain, should generally default to `chainId`. Consumer of `ChainMetadata` should use this value or `name` as a unique identifier.'),
gasCurrencyCoinGeckoId: z
.string()
.optional()
.describe('The ID on CoinGecko of the token used for gas payments.'),
gnosisSafeTransactionServiceUrl: z
.string()
.optional()
.describe('The URL of the gnosis safe transaction service.'),
grpcUrls: z
.array(RpcUrlSchema)
.describe('For cosmos chains only, a list of gRPC API URLs')
.optional(),
index: z
.object({
from: z
.number()
.optional()
.describe('The block to start any indexing from.'),
})
.optional()
.describe('Indexing settings for the chain.'),
isTestnet: z
.boolean()
.optional()
.describe('Whether the chain is considered a testnet or a mainnet.'),
logoURI: z
.string()
.optional()
.describe('A URI to a logo image for this chain for use in user interfaces.'),
name: ZChainName.describe('The unique string identifier of the chain, used as the key in ChainMap dictionaries.'),
nativeToken: NativeTokenSchema.optional().describe('The metadata of the native token of the chain (e.g. ETH for Ethereum).'),
protocol: z
.nativeEnum(ProtocolType)
.describe('The type of protocol used by this chain. See ProtocolType for valid values.'),
restUrls: z
.array(RpcUrlSchema)
.describe('For cosmos chains only, a list of Rest API URLs')
.optional(),
rpcUrls: z
.array(RpcUrlSchema)
.min(1)
.describe('The list of RPC endpoints for interacting with the chain.'),
slip44: z.number().optional().describe('The SLIP-0044 coin type.'),
technicalStack: z
.nativeEnum(ChainTechnicalStack)
.optional()
.describe('The technical stack of the chain. See ChainTechnicalStack for valid values.'),
transactionOverrides: z
.record(z.any())
.optional()
.describe('Properties to include when forming transaction requests.'),
gasPrice: GasPriceSchema.optional().describe('The gas price of Cosmos chains.'),
});
// Passthrough allows for extra fields to remain in the object (such as extensions consumers may want like `mailbox`)
const ChainMetadataSchemaExtensible = ChainMetadataSchemaObject.passthrough();
// Add refinements to the object schema to conditionally validate certain fields
export const ChainMetadataSchema = ChainMetadataSchemaExtensible.refine((metadata) => {
if ([ProtocolType.Ethereum, ProtocolType.Sealevel].includes(metadata.protocol) &&
typeof metadata.chainId !== 'number')
return false;
else if ((metadata.protocol === ProtocolType.Cosmos ||
metadata.protocol === ProtocolType.CosmosNative) &&
typeof metadata.chainId !== 'string')
return false;
else
return true;
}, { message: 'Invalid Chain Id', path: ['chainId'] })
.refine((metadata) => {
if (typeof metadata.chainId === 'string' && !metadata.domainId)
return false;
else
return true;
}, { message: 'Domain Id required', path: ['domainId'] })
.refine((metadata) => {
if ((metadata.protocol === ProtocolType.Cosmos ||
metadata.protocol === ProtocolType.CosmosNative) &&
(!metadata.bech32Prefix || !metadata.slip44))
return false;
else
return true;
}, {
message: 'Bech32Prefix and Slip44 required for Cosmos chains',
path: ['bech32Prefix', 'slip44'],
})
.refine((metadata) => {
if ((metadata.protocol === ProtocolType.Cosmos ||
metadata.protocol === ProtocolType.CosmosNative) &&
(!metadata.restUrls || !metadata.grpcUrls))
return false;
else
return true;
}, {
message: 'Rest and gRPC URLs required for Cosmos chains',
path: ['restUrls', 'grpcUrls'],
})
.refine((metadata) => {
if ((metadata.protocol === ProtocolType.Cosmos ||
metadata.protocol === ProtocolType.CosmosNative) &&
metadata.nativeToken &&
!metadata.nativeToken.denom)
return false;
else
return true;
}, {
message: 'Denom values are required for Cosmos native tokens',
path: ['nativeToken', 'denom'],
})
.refine((metadata) => {
if (metadata.technicalStack === ChainTechnicalStack.ArbitrumNitro &&
metadata.index?.from === undefined) {
return false;
}
else
return true;
}, {
message: 'An index.from value is required for Arbitrum Nitro chains',
path: ['index', 'from'],
});
export function safeParseChainMetadata(c) {
return ChainMetadataSchema.safeParse(c);
}
export function isValidChainMetadata(c) {
return ChainMetadataSchema.safeParse(c).success;
}
export function getDomainId(chainMetadata) {
if (chainMetadata.domainId)
return chainMetadata.domainId;
else if (typeof chainMetadata.chainId === 'number')
return chainMetadata.chainId;
else
throw new Error('Invalid chain metadata, no valid domainId');
}
export function getChainIdNumber(chainMetadata) {
if (typeof chainMetadata.chainId === 'number')
return chainMetadata.chainId;
else
throw new Error('ChainId is not a number, chain may be of Cosmos type');
}
export function getReorgPeriod(chainMetadata) {
if (chainMetadata.blocks?.reorgPeriod !== undefined)
return chainMetadata.blocks.reorgPeriod;
else
throw new Error('Chain has no reorg period');
}
export function mergeChainMetadata(base, overrides) {
return objMerge(base, overrides || {}, 10, true);
}
export function mergeChainMetadataMap(base, overrides) {
return objMerge(base, overrides || {}, 10, true);
}
//# sourceMappingURL=chainMetadataTypes.js.map