feat: typescript relayer MVP (#3693)

### Description

- implement `HyperlaneRelayer` as a wrapper around `HyperlaneCore`
- add relaying to CLI status command
- add infra script relay for an environment

### Drive-by changes

<!--
Are there any minor or drive-by changes also included?
-->

### Related issues

- Touches
https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3448

### Backward compatibility

<!--
Are these changes backward compatible? Are there any infrastructure
implications, e.g. changes that would prohibit deploying older commits
using this infra tooling?

Yes/No
-->

### Testing

Manual

---------

Co-authored-by: -f <kunalarora1729@gmail.com>
Co-authored-by: Noah Bayindirli 🥂 <noah@primeprotocol.xyz>
dan/debug-cosmos-rpc
Yorke Rhodes 4 months ago committed by GitHub
parent e5807d4801
commit 388d255171
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 8
      .changeset/breezy-wasps-grin.md
  2. 1
      typescript/cli/ci-advanced-test.sh
  3. 9
      typescript/cli/src/commands/status.ts
  4. 5
      typescript/cli/src/send/message.ts
  5. 23
      typescript/cli/src/send/transfer.ts
  6. 71
      typescript/cli/src/status/message.ts
  7. 42
      typescript/infra/scripts/relay.ts
  8. 180
      typescript/sdk/src/core/HyperlaneCore.ts
  9. 6
      typescript/sdk/src/core/HyperlaneCoreDeployer.ts
  10. 225
      typescript/sdk/src/core/HyperlaneRelayer.ts
  11. 334
      typescript/sdk/src/index.ts
  12. 12
      typescript/sdk/src/metadata/ChainMetadataManager.ts
  13. 12
      typescript/sdk/src/providers/MultiProvider.ts
  14. 2
      typescript/sdk/src/providers/SmartProvider/SmartProvider.ts
  15. 3
      typescript/utils/src/async.ts

@ -0,0 +1,8 @@
---
'@hyperlane-xyz/infra': minor
'@hyperlane-xyz/utils': minor
'@hyperlane-xyz/cli': minor
'@hyperlane-xyz/sdk': minor
---
Added HyperlaneRelayer for relaying messages from the CLI

@ -252,6 +252,7 @@ run_hyperlane_send_message() {
--destination ${CHAIN2} \
--warp ${WARP_CONFIG_FILE} \
--quick \
--relay \
--key $ANVIL_KEY \
| tee /tmp/message2

@ -4,7 +4,7 @@ import { checkMessageStatus } from '../status/message.js';
import { MessageOptionsArgTypes, messageOptions } from './send.js';
export const statusCommand: CommandModuleWithContext<
MessageOptionsArgTypes & { id?: string }
MessageOptionsArgTypes & { id?: string } & { dispatchTx?: string }
> = {
command: 'status',
describe: 'Check status of a message',
@ -14,10 +14,15 @@ export const statusCommand: CommandModuleWithContext<
type: 'string',
description: 'Message ID',
},
dispatchTx: {
type: 'string',
description: 'Dispatch transaction hash',
},
},
handler: async ({ context, origin, destination, id, relay }) => {
handler: async ({ context, origin, destination, id, relay, dispatchTx }) => {
await checkMessageStatus({
context,
dispatchTx,
messageId: id,
destination,
origin,

@ -1,6 +1,6 @@
import { stringify as yamlStringify } from 'yaml';
import { ChainName, HyperlaneCore } from '@hyperlane-xyz/sdk';
import { ChainName, HyperlaneCore, HyperlaneRelayer } from '@hyperlane-xyz/sdk';
import { addressToBytes32, timeout } from '@hyperlane-xyz/utils';
import { MINIMUM_TEST_SEND_GAS } from '../consts.js';
@ -109,8 +109,9 @@ async function executeDelivery({
log(`Message:\n${indentYamlOrJson(yamlStringify(message, null, 2), 4)}`);
if (selfRelay) {
const relayer = new HyperlaneRelayer({ core });
log('Attempting self-relay of message');
await core.relayMessage(message, dispatchTx);
await relayer.relayMessage(dispatchTx);
logGreen('Message was self-relayed!');
} else {
if (skipWaitForDelivery) {

@ -1,6 +1,10 @@
import { stringify as yamlStringify } from 'yaml';
import {
ChainName,
DispatchedMessage,
HyperlaneCore,
HyperlaneRelayer,
MultiProtocolProvider,
ProviderType,
Token,
@ -13,8 +17,9 @@ import { timeout } from '@hyperlane-xyz/utils';
import { MINIMUM_TEST_SEND_GAS } from '../consts.js';
import { WriteCommandContext } from '../context/types.js';
import { runPreflightChecksForChains } from '../deploy/utils.js';
import { logBlue, logGreen, logRed } from '../logger.js';
import { log, logBlue, logGreen, logRed } from '../logger.js';
import { runSingleChainSelectionStep } from '../utils/chains.js';
import { indentYamlOrJson } from '../utils/files.js';
import { runTokenSelectionStep } from '../utils/tokens.js';
export async function sendTestTransfer({
@ -153,16 +158,22 @@ async function executeDelivery({
txReceipts.push(txReceipt);
}
}
const transferTxReceipt = txReceipts[txReceipts.length - 1];
const messageIndex: number = 0;
const message: DispatchedMessage =
HyperlaneCore.getDispatchedMessages(transferTxReceipt)[messageIndex];
const message = core.getDispatchedMessages(transferTxReceipt)[0];
logBlue(`Sent message from ${origin} to ${recipient} on ${destination}.`);
logBlue(
`Sent transfer from sender (${senderAddress}) on ${origin} to recipient (${recipient}) on ${destination}.`,
);
logBlue(`Message ID: ${message.id}`);
log(`Message:\n${indentYamlOrJson(yamlStringify(message, null, 2), 4)}`);
if (selfRelay) {
await core.relayMessage(message, transferTxReceipt);
logGreen('Message was self-relayed!');
const relayer = new HyperlaneRelayer({ core });
log('Attempting self-relay of transfer...');
await relayer.relayMessage(transferTxReceipt, messageIndex, message);
logGreen('Transfer was self-relayed!');
return;
}

@ -1,9 +1,11 @@
import type { TransactionReceipt } from '@ethersproject/providers';
import { input } from '@inquirer/prompts';
import { ChainName, HyperlaneCore } from '@hyperlane-xyz/sdk';
import { ChainName, HyperlaneCore, HyperlaneRelayer } from '@hyperlane-xyz/sdk';
import { assert } from '@hyperlane-xyz/utils';
import { CommandContext } from '../context/types.js';
import { log, logBlue, logGreen } from '../logger.js';
import { log, logBlue, logGreen, logRed } from '../logger.js';
import { runSingleChainSelectionStep } from '../utils/chains.js';
export async function checkMessageStatus({
@ -12,17 +14,19 @@ export async function checkMessageStatus({
destination,
origin,
selfRelay,
dispatchTx,
}: {
context: CommandContext;
dispatchTx?: string;
messageId?: string;
destination?: ChainName;
origin?: ChainName;
selfRelay?: boolean;
}) {
if (!destination) {
destination = await runSingleChainSelectionStep(
if (!origin) {
origin = await runSingleChainSelectionStep(
context.chainMetadata,
'Select the destination chain',
'Select the origin chain',
);
}
@ -37,27 +41,50 @@ export async function checkMessageStatus({
chainAddresses,
context.multiProvider,
);
const mailbox = core.getContracts(destination).mailbox;
let dispatchedReceipt: TransactionReceipt;
if (!dispatchTx) {
try {
dispatchedReceipt = await core.getDispatchTx(origin, messageId);
} catch (e) {
logRed(`Failed to infer dispatch transaction for message ${messageId}`);
}
dispatchTx = await input({
message: 'Provide dispatch transaction hash',
});
}
dispatchedReceipt ??= await context.multiProvider
.getProvider(origin)
.getTransactionReceipt(dispatchTx);
const messages = core.getDispatchedMessages(dispatchedReceipt);
const match = messages.find((m) => m.id === messageId);
assert(match, `Message ${messageId} not found in dispatch tx ${dispatchTx}`);
const message = match;
let deliveredTx: TransactionReceipt;
log(`Checking status of message ${messageId} on ${destination}`);
const delivered = await mailbox.delivered(messageId);
const delivered = await core.isDelivered(message);
if (delivered) {
logGreen(`Message ${messageId} was delivered`);
return;
}
logBlue(`Message ${messageId} was not yet delivered`);
if (selfRelay) {
// TODO: implement option for tx receipt input
if (!origin) {
origin = await runSingleChainSelectionStep(
context.chainMetadata,
'Select the origin chain',
);
deliveredTx = await core.getProcessedReceipt(message);
} else {
logBlue(`Message ${messageId} was not yet delivered`);
if (!selfRelay) {
return;
}
const receipt = await core.getDispatchTx(origin, messageId);
const messages = core.getDispatchedMessages(receipt);
await core.relayMessage(messages[0], receipt);
logGreen(`Message ${messageId} was self-relayed!`);
const relayer = new HyperlaneRelayer({ core });
deliveredTx = await relayer.relayMessage(dispatchedReceipt);
}
logGreen(
`Message ${messageId} delivered in ${context.multiProvider.getExplorerTxUrl(
message.parsed.destination,
{ hash: deliveredTx.transactionHash },
)}`,
);
}

@ -0,0 +1,42 @@
import { HyperlaneRelayer, RelayerCacheSchema } from '@hyperlane-xyz/sdk';
import { readFile, writeFile } from 'fs/promises';
import { getArgs } from './agent-utils.js';
import { getHyperlaneCore } from './core-utils.js';
const CACHE_PATH = process.env.RELAYER_CACHE ?? './relayer-cache.json';
async function main() {
const { environment } = await getArgs().argv;
const { core } = await getHyperlaneCore(environment);
const relayer = new HyperlaneRelayer({ core });
// target subset of chains
// const chains = ['ethereum', 'polygon', 'bsc']
const chains = undefined;
try {
const contents = await readFile(CACHE_PATH, 'utf-8');
const data = JSON.parse(contents);
const cache = RelayerCacheSchema.parse(data);
relayer.hydrate(cache);
console.log(`Relayer cache loaded from ${CACHE_PATH}`);
} catch (e) {
console.error(`Failed to load cache from ${CACHE_PATH}`);
}
relayer.start(chains);
process.once('SIGINT', async () => {
relayer.stop(chains);
const cache = JSON.stringify(relayer.cache);
await writeFile(CACHE_PATH, cache, 'utf-8');
console.log(`Relayer cache saved to ${CACHE_PATH}`);
process.exit(0);
});
}
main();

@ -2,18 +2,25 @@ import { TransactionReceipt } from '@ethersproject/providers';
import { ethers } from 'ethers';
import type { TransactionReceipt as ViemTxReceipt } from 'viem';
import { Mailbox__factory } from '@hyperlane-xyz/core';
import {
IMessageRecipient,
IMessageRecipient__factory,
MailboxClient__factory,
Mailbox__factory,
} from '@hyperlane-xyz/core';
import {
Address,
AddressBytes32,
ProtocolType,
addressToBytes32,
bytes32ToAddress,
isZeroishAddress,
messageId,
objFilter,
objMap,
parseMessage,
pollAsync,
promiseObjAll,
} from '@hyperlane-xyz/utils';
import { HyperlaneApp } from '../app/HyperlaneApp.js';
@ -22,13 +29,13 @@ import { HyperlaneAddressesMap } from '../contracts/types.js';
import { OwnableConfig } from '../deploy/types.js';
import { DerivedHookConfig, EvmHookReader } from '../hook/EvmHookReader.js';
import { DerivedIsmConfig, EvmIsmReader } from '../ism/EvmIsmReader.js';
import { BaseMetadataBuilder } from '../ism/metadata/builder.js';
import { MultiProvider } from '../providers/MultiProvider.js';
import { RouterConfig } from '../router/types.js';
import { ChainMap, ChainName } from '../types.js';
import { findMatchingLogEvents } from '../utils/logUtils.js';
import { CoreFactories, coreFactories } from './contracts.js';
import { DispatchEvent } from './events.js';
import { DispatchedMessage } from './types.js';
export class HyperlaneCore extends HyperlaneApp<CoreFactories> {
@ -83,11 +90,11 @@ export class HyperlaneCore extends HyperlaneApp<CoreFactories> {
);
};
protected getDestination(message: DispatchedMessage): ChainName {
getDestination(message: DispatchedMessage): ChainName {
return this.multiProvider.getChainName(message.parsed.destination);
}
protected getOrigin(message: DispatchedMessage): ChainName {
getOrigin(message: DispatchedMessage): ChainName {
return this.multiProvider.getChainName(message.parsed.origin);
}
@ -119,23 +126,6 @@ export class HyperlaneCore extends HyperlaneApp<CoreFactories> {
return hookReader.deriveHookConfig(address);
}
async buildMetadata(
message: DispatchedMessage,
dispatchTx: TransactionReceipt,
): Promise<string> {
const ismConfig = await this.getRecipientIsmConfig(message);
const hookConfig = await this.getHookConfig(message);
const baseMetadataBuilder = new BaseMetadataBuilder(this);
return baseMetadataBuilder.build({
ism: ismConfig,
hook: hookConfig,
message,
dispatchTx,
});
}
async sendMessage(
origin: ChainName,
destination: ChainName,
@ -172,19 +162,131 @@ export class HyperlaneCore extends HyperlaneApp<CoreFactories> {
};
}
async relayMessage(
onDispatch(
handler: (
message: DispatchedMessage,
event: DispatchEvent,
) => Promise<void>,
chains = Object.keys(this.contractsMap),
): {
removeHandler: (chains?: ChainName[]) => void;
} {
chains.map((originChain) => {
const mailbox = this.contractsMap[originChain].mailbox;
this.logger.debug(`Listening for dispatch on ${originChain}`);
mailbox.on<DispatchEvent>(
mailbox.filters.Dispatch(),
(_sender, _destination, _recipient, message, event) => {
const parsed = HyperlaneCore.parseDispatchedMessage(message);
this.logger.info(`Observed message ${parsed.id} on ${originChain}`);
return handler(parsed, event);
},
);
});
return {
removeHandler: (removeChains) =>
(removeChains ?? chains).map((originChain) => {
this.contractsMap[originChain].mailbox.removeAllListeners('Dispatch');
this.logger.debug(`Stopped listening for dispatch on ${originChain}`);
}),
};
}
getDefaults(): Promise<ChainMap<{ ism: Address; hook: Address }>> {
return promiseObjAll(
objMap(this.contractsMap, async (_, contracts) => ({
ism: await contracts.mailbox.defaultIsm(),
hook: await contracts.mailbox.defaultHook(),
})),
);
}
getIsm(
destinationChain: ChainName,
recipientAddress: Address,
): Promise<Address> {
const destinationMailbox = this.contractsMap[destinationChain];
return destinationMailbox.mailbox.recipientIsm(recipientAddress);
}
protected getRecipient(message: DispatchedMessage): IMessageRecipient {
return IMessageRecipient__factory.connect(
bytes32ToAddress(message.parsed.recipient),
this.multiProvider.getProvider(this.getDestination(message)),
);
}
async estimateHandle(message: DispatchedMessage): Promise<string> {
return (
await this.getRecipient(message).estimateGas.handle(
message.parsed.origin,
message.parsed.sender,
message.parsed.body,
{ from: this.getAddresses(this.getDestination(message)).mailbox },
)
).toString();
}
deliver(
message: DispatchedMessage,
dispatchTx: ethers.ContractReceipt,
ismMetadata: string,
): Promise<ethers.ContractReceipt> {
const metadata = await this.buildMetadata(message, dispatchTx);
const destinationChain = this.getDestination(message);
return this.multiProvider.handleTx(
destinationChain,
this.getContracts(destinationChain).mailbox.process(
ismMetadata,
message.message,
),
);
}
async getHook(
originChain: ChainName,
senderAddress: Address,
): Promise<Address> {
const provider = this.multiProvider.getProvider(originChain);
try {
const client = MailboxClient__factory.connect(senderAddress, provider);
const hook = await client.hook();
if (!isZeroishAddress(hook)) {
return hook;
}
} catch (e) {
this.logger.debug(`MailboxClient hook not found for ${senderAddress}`);
this.logger.trace({ e });
}
const originMailbox = this.contractsMap[originChain].mailbox;
return originMailbox.defaultHook();
}
isDelivered(message: DispatchedMessage): Promise<boolean> {
const destinationChain = this.getDestination(message);
return this.getContracts(destinationChain).mailbox.delivered(message.id);
}
async getSenderHookAddress(message: DispatchedMessage): Promise<Address> {
const originChain = this.getOrigin(message);
const senderAddress = bytes32ToAddress(message.parsed.sender);
return this.getHook(originChain, senderAddress);
}
async getProcessedReceipt(
message: DispatchedMessage,
): Promise<ethers.ContractReceipt> {
const destinationChain = this.getDestination(message);
const mailbox = this.contractsMap[destinationChain].mailbox;
return this.multiProvider.handleTx(
destinationChain,
mailbox.process(metadata, message.message),
const processedBlock = await mailbox.processedAt(message.id);
const events = await mailbox.queryFilter(
mailbox.filters.ProcessId(message.id),
processedBlock,
processedBlock,
);
const processedEvent = events[0];
return processedEvent.getTransactionReceipt();
}
protected waitForProcessReceipt(
@ -262,7 +364,7 @@ export class HyperlaneCore extends HyperlaneApp<CoreFactories> {
// Redundant with static method but keeping for backwards compatibility
getDispatchedMessages(
sourceTx: ethers.ContractReceipt | ViemTxReceipt,
sourceTx: TransactionReceipt | ViemTxReceipt,
): DispatchedMessage[] {
const messages = HyperlaneCore.getDispatchedMessages(sourceTx);
return messages.map(({ parsed, ...other }) => {
@ -277,19 +379,31 @@ export class HyperlaneCore extends HyperlaneApp<CoreFactories> {
async getDispatchTx(
originChain: ChainName,
messageId: string,
): Promise<ethers.ContractReceipt> {
blockNumber?: number,
): Promise<TransactionReceipt> {
const mailbox = this.contractsMap[originChain].mailbox;
const filter = mailbox.filters.DispatchId(messageId);
const matchingEvents = await mailbox.queryFilter(filter);
if (matchingEvents.length === 0) {
const { fromBlock, toBlock } = blockNumber
? { toBlock: blockNumber, fromBlock: blockNumber }
: await this.multiProvider.getLatestBlockRange(originChain);
const matching = await mailbox.queryFilter(filter, fromBlock, toBlock);
if (matching.length === 0) {
throw new Error(`No dispatch event found for message ${messageId}`);
}
const event = matchingEvents[0]; // only 1 event per message ID
const event = matching[0]; // only 1 event per message ID
return event.getTransactionReceipt();
}
static parseDispatchedMessage(message: string): DispatchedMessage {
const parsed = parseMessage(message);
const id = messageId(message);
return { id, message, parsed };
}
static getDispatchedMessages(
sourceTx: ethers.ContractReceipt | ViemTxReceipt,
sourceTx: TransactionReceipt | ViemTxReceipt,
): DispatchedMessage[] {
const mailbox = Mailbox__factory.createInterface();
const dispatchLogs = findMatchingLogEvents(

@ -202,7 +202,11 @@ export class HyperlaneCoreDeployer extends HyperlaneDeployer<
this.hookDeployer.deployedContracts[chain],
this.hookDeployer.verificationInputs[chain],
);
return hooks[config.type];
if (typeof config === 'string') {
return Object.values(hooks)[0];
} else {
return hooks[config.type];
}
}
async deployIsm(

@ -0,0 +1,225 @@
import { ethers, providers } from 'ethers';
import { Logger } from 'pino';
import { z } from 'zod';
import {
Address,
assert,
objMap,
objMerge,
pollAsync,
promiseObjAll,
} from '@hyperlane-xyz/utils';
import { DerivedHookConfig, EvmHookReader } from '../hook/EvmHookReader.js';
import { HookConfigSchema } from '../hook/schemas.js';
import { DerivedIsmConfig, EvmIsmReader } from '../ism/EvmIsmReader.js';
import { BaseMetadataBuilder } from '../ism/metadata/builder.js';
import { IsmConfigSchema } from '../ism/schemas.js';
import { MultiProvider } from '../providers/MultiProvider.js';
import { ChainName } from '../types.js';
import { HyperlaneCore } from './HyperlaneCore.js';
import { DispatchedMessage } from './types.js';
const WithAddressSchema = z.object({
address: z.string(),
});
const DerivedHookConfigWithAddressSchema =
HookConfigSchema.and(WithAddressSchema);
const DerivedIsmConfigWithAddressSchema =
IsmConfigSchema.and(WithAddressSchema);
export const RelayerCacheSchema = z.object({
hook: z.record(z.record(DerivedHookConfigWithAddressSchema)),
ism: z.record(z.record(DerivedIsmConfigWithAddressSchema)),
});
type RelayerCache = z.infer<typeof RelayerCacheSchema>;
export class HyperlaneRelayer {
protected multiProvider: MultiProvider;
protected metadataBuilder: BaseMetadataBuilder;
protected readonly core: HyperlaneCore;
protected readonly retryTimeout: number;
public cache: RelayerCache | undefined;
protected stopRelayingHandler: ((chains?: ChainName[]) => void) | undefined;
public readonly logger: Logger;
constructor({
core,
caching = true,
retryTimeout = 5 * 1000,
}: {
core: HyperlaneCore;
caching?: boolean;
retryTimeout?: number;
}) {
this.core = core;
this.retryTimeout = retryTimeout;
this.logger = core.logger.child({ module: 'Relayer' });
this.metadataBuilder = new BaseMetadataBuilder(core);
this.multiProvider = core.multiProvider;
if (caching) {
this.cache = {
hook: {},
ism: {},
};
}
}
async getHookConfig(
chain: ChainName,
hook: Address,
): Promise<DerivedHookConfig> {
let config: DerivedHookConfig | undefined;
if (this.cache?.hook[chain]?.[hook]) {
config = this.cache.hook[chain][hook] as DerivedHookConfig | undefined;
} else {
const evmHookReader = new EvmHookReader(this.multiProvider, chain);
config = await evmHookReader.deriveHookConfig(hook);
}
if (!config) {
throw new Error(`Hook config not found for ${hook}`);
}
if (this.cache) {
this.cache.hook[chain] ??= {};
this.cache.hook[chain][hook] = config;
}
return config;
}
async getIsmConfig(
chain: ChainName,
ism: Address,
): Promise<DerivedIsmConfig> {
let config: DerivedIsmConfig | undefined;
if (this.cache?.ism[chain]?.[ism]) {
config = this.cache.ism[chain][ism] as DerivedIsmConfig | undefined;
} else {
const evmIsmReader = new EvmIsmReader(this.multiProvider, chain);
config = await evmIsmReader.deriveIsmConfig(ism);
}
if (!config) {
throw new Error(`ISM config not found for ${ism}`);
}
if (this.cache) {
this.cache.ism[chain] ??= {};
this.cache.ism[chain][ism] = config;
}
return config;
}
async getSenderHookConfig(
message: DispatchedMessage,
): Promise<DerivedHookConfig> {
const originChain = this.core.getOrigin(message);
const hook = await this.core.getSenderHookAddress(message);
return this.getHookConfig(originChain, hook);
}
async getRecipientIsmConfig(
message: DispatchedMessage,
): Promise<DerivedIsmConfig> {
const destinationChain = this.core.getDestination(message);
const ism = await this.core.getRecipientIsmAddress(message);
return this.getIsmConfig(destinationChain, ism);
}
async relayMessage(
dispatchTx: providers.TransactionReceipt,
messageIndex = 0,
message = HyperlaneCore.getDispatchedMessages(dispatchTx)[messageIndex],
): Promise<ethers.ContractReceipt> {
this.logger.info(`Preparing to relay message ${message.id}`);
const isDelivered = await this.core.isDelivered(message);
if (isDelivered) {
this.logger.debug(`Message ${message.id} already delivered`);
return this.core.getProcessedReceipt(message);
}
this.logger.debug({ message }, `Simulating recipient message handling`);
await this.core.estimateHandle(message);
// parallelizable because configs are on different chains
const [ism, hook] = await Promise.all([
this.getRecipientIsmConfig(message),
this.getSenderHookConfig(message),
]);
this.logger.debug({ ism, hook }, `Retrieved ISM and hook configs`);
const blockTime = this.multiProvider.getChainMetadata(
message.parsed.destination,
).blocks?.estimateBlockTime;
const waitTime = blockTime ? blockTime * 2 : this.retryTimeout;
const metadata = await pollAsync(
() => this.metadataBuilder.build({ message, ism, hook, dispatchTx }),
waitTime,
12, // 12 attempts
);
this.logger.info({ message, metadata }, `Relaying message ${message.id}`);
return this.core.deliver(message, metadata);
}
hydrate(cache: RelayerCache): void {
assert(this.cache, 'Caching not enabled');
this.cache = objMerge(this.cache, cache);
}
// fill cache with default ISM and hook configs for quicker relaying (optional)
async hydrateDefaults(): Promise<void> {
assert(this.cache, 'Caching not enabled');
const defaults = await this.core.getDefaults();
await promiseObjAll(
objMap(defaults, async (chain, { ism, hook }) => {
this.logger.debug(
`Hydrating ${chain} cache with default ISM and hook configs`,
);
await this.getHookConfig(chain, hook);
await this.getIsmConfig(chain, ism);
}),
);
}
start(chains = this.multiProvider.getKnownChainNames()): void {
assert(!this.stopRelayingHandler, 'Relayer already started');
const { removeHandler } = this.core.onDispatch(async (message, event) => {
const destination = message.parsed.destination;
const chain = this.multiProvider.tryGetChainName(destination);
if (!chain) {
this.logger.warn(`Unknown destination ${destination}`);
return;
}
if (!chains.includes(chain)) {
this.logger.info(`Skipping message to chain ${chain}`);
return;
}
const dispatchReceipt = await event.getTransactionReceipt();
await this.relayMessage(dispatchReceipt, undefined, message);
}, chains);
this.stopRelayingHandler = removeHandler;
}
stop(chains = this.multiProvider.getKnownChainNames()): void {
assert(this.stopRelayingHandler, 'Relayer not started');
this.stopRelayingHandler(chains);
this.stopRelayingHandler = undefined;
}
}

@ -6,6 +6,8 @@ export {
BaseSealevelAdapter,
MultiProtocolApp,
} from './app/MultiProtocolApp.js';
export { S3Config, S3Receipt, S3Wrapper } from './aws/s3.js';
export { S3Validator } from './aws/validator.js';
export {
TOKEN_EXCHANGE_RATE_DECIMALS,
TOKEN_EXCHANGE_RATE_SCALE,
@ -24,14 +26,6 @@ export {
testCosmosChain,
testSealevelChain,
} from './consts/testChains.js';
export {
AddressesMap,
HyperlaneAddresses,
HyperlaneAddressesMap,
HyperlaneContracts,
HyperlaneContractsMap,
HyperlaneFactories,
} from './contracts/types.js';
export {
attachContracts,
attachContractsMap,
@ -46,28 +40,17 @@ export {
serializeContractsMap,
} from './contracts/contracts.js';
export {
CoreConfig,
CoreViolationType,
DispatchedMessage,
MailboxMultisigIsmViolation,
MailboxViolation,
MailboxViolationType,
ValidatorAnnounceViolation,
} from './core/types.js';
export { HyperlaneCore } from './core/HyperlaneCore.js';
export { HyperlaneCoreChecker } from './core/HyperlaneCoreChecker.js';
export { HyperlaneCoreDeployer } from './core/HyperlaneCoreDeployer.js';
export { MultiProtocolCore } from './core/MultiProtocolCore.js';
export { TestCoreApp } from './core/TestCoreApp.js';
export { TestCoreDeployer } from './core/TestCoreDeployer.js';
export {
TestRecipientConfig,
TestRecipientDeployer,
} from './core/TestRecipientDeployer.js';
export { ICoreAdapter } from './core/adapters/types.js';
AddressesMap,
HyperlaneAddresses,
HyperlaneAddressesMap,
HyperlaneContracts,
HyperlaneContractsMap,
HyperlaneFactories,
} from './contracts/types.js';
export { CosmWasmCoreAdapter } from './core/adapters/CosmWasmCoreAdapter.js';
export { EvmCoreAdapter } from './core/adapters/EvmCoreAdapter.js';
export { SealevelCoreAdapter } from './core/adapters/SealevelCoreAdapter.js';
export { ICoreAdapter } from './core/adapters/types.js';
export {
CoreAddresses,
CoreFactories,
@ -75,19 +58,42 @@ export {
} from './core/contracts.js';
export { HyperlaneLifecyleEvent } from './core/events.js';
export { EvmCoreReader } from './core/EvmCoreReader.js';
export { HyperlaneCore } from './core/HyperlaneCore.js';
export { HyperlaneCoreChecker } from './core/HyperlaneCoreChecker.js';
export { HyperlaneCoreDeployer } from './core/HyperlaneCoreDeployer.js';
export {
HyperlaneRelayer,
RelayerCacheSchema,
} from './core/HyperlaneRelayer.js';
export { MultiProtocolCore } from './core/MultiProtocolCore.js';
export { CoreConfigSchema } from './core/schemas.js';
export { TestCoreApp } from './core/TestCoreApp.js';
export { TestCoreDeployer } from './core/TestCoreDeployer.js';
export {
CheckerViolation,
OwnableConfig,
OwnerViolation,
ViolationType,
} from './deploy/types.js';
TestRecipientConfig,
TestRecipientDeployer,
} from './core/TestRecipientDeployer.js';
export {
CoreConfig,
CoreViolationType,
DispatchedMessage,
MailboxMultisigIsmViolation,
MailboxViolation,
MailboxViolationType,
ValidatorAnnounceViolation,
} from './core/types.js';
export { HyperlaneAppChecker } from './deploy/HyperlaneAppChecker.js';
export {
DeployerOptions,
HyperlaneDeployer,
} from './deploy/HyperlaneDeployer.js';
export { HyperlaneProxyFactoryDeployer } from './deploy/HyperlaneProxyFactoryDeployer.js';
export {
CheckerViolation,
OwnableConfig,
OwnerViolation,
ViolationType,
} from './deploy/types.js';
export { ContractVerifier } from './deploy/verify/ContractVerifier.js';
export { PostDeploymentContractVerifier } from './deploy/verify/PostDeploymentContractVerifier.js';
export {
@ -98,17 +104,6 @@ export {
VerificationInput,
} from './deploy/verify/types.js';
export * as verificationUtils from './deploy/verify/utils.js';
export {
IgpBeneficiaryViolation,
IgpConfig,
IgpGasOraclesViolation,
IgpOverheadViolation,
IgpViolation,
IgpViolationType,
} from './gas/types.js';
export { HyperlaneIgp } from './gas/HyperlaneIgp.js';
export { HyperlaneIgpChecker } from './gas/HyperlaneIgpChecker.js';
export { HyperlaneIgpDeployer } from './gas/HyperlaneIgpDeployer.js';
export { SealevelOverheadIgpAdapter } from './gas/adapters/SealevelIgpAdapter.js';
export {
SealevelInterchainGasPaymasterConfig,
@ -118,8 +113,22 @@ export {
SealevelOverheadIgpDataSchema,
} from './gas/adapters/serialization.js';
export { IgpFactories, igpFactories } from './gas/contracts.js';
export { HyperlaneIgp } from './gas/HyperlaneIgp.js';
export { HyperlaneIgpChecker } from './gas/HyperlaneIgpChecker.js';
export { HyperlaneIgpDeployer } from './gas/HyperlaneIgpDeployer.js';
export { StorageGasOracleConfig } from './gas/oracle/types.js';
export { CoinGeckoTokenPriceGetter } from './gas/token-prices.js';
export {
IgpBeneficiaryViolation,
IgpConfig,
IgpGasOraclesViolation,
IgpOverheadViolation,
IgpViolation,
IgpViolationType,
} from './gas/types.js';
export { EvmHookReader } from './hook/EvmHookReader.js';
export { HyperlaneHookDeployer } from './hook/HyperlaneHookDeployer.js';
export { HookConfigSchema } from './hook/schemas.js';
export {
AggregationHookConfig,
DomainRoutingHookConfig,
@ -132,9 +141,12 @@ export {
PausableHookConfig,
ProtocolFeeHookConfig,
} from './hook/types.js';
export { HookConfigSchema } from './hook/schemas.js';
export { HyperlaneHookDeployer } from './hook/HyperlaneHookDeployer.js';
export { EvmHookReader } from './hook/EvmHookReader.js';
export { EvmIsmReader } from './ism/EvmIsmReader.js';
export { HyperlaneIsmFactory } from './ism/HyperlaneIsmFactory.js';
export {
buildAggregationIsmConfigs,
buildMultisigIsmConfigs,
} from './ism/multisig.js';
export {
AggregationIsmConfig,
DeployedIsm,
@ -148,34 +160,7 @@ export {
RoutingIsmConfig,
TrustedRelayerIsmConfig,
} from './ism/types.js';
export { HyperlaneIsmFactory } from './ism/HyperlaneIsmFactory.js';
export {
buildAggregationIsmConfigs,
buildMultisigIsmConfigs,
} from './ism/multisig.js';
export { EvmIsmReader } from './ism/EvmIsmReader.js';
export { collectValidators, moduleCanCertainlyVerify } from './ism/utils.js';
export { ZChainName, ZHash } from './metadata/customZodTypes.js';
export {
BlockExplorer,
ChainMetadata,
ChainMetadataSchema,
ChainMetadataSchemaObject,
ChainTechnicalStack,
ExplorerFamily,
ExplorerFamilyValue,
NativeToken,
RpcUrl,
RpcUrlSchema,
getChainIdNumber,
getDomainId,
getReorgPeriod,
isValidChainMetadata,
} from './metadata/chainMetadataTypes.js';
export {
ChainMetadataManager,
ChainMetadataManagerOptions,
} from './metadata/ChainMetadataManager.js';
export {
AgentChainMetadata,
AgentChainMetadataSchema,
@ -197,6 +182,27 @@ export {
ValidatorConfig,
buildAgentConfig,
} from './metadata/agentConfig.js';
export {
ChainMetadataManager,
ChainMetadataManagerOptions,
} from './metadata/ChainMetadataManager.js';
export {
BlockExplorer,
ChainMetadata,
ChainMetadataSchema,
ChainMetadataSchemaObject,
ChainTechnicalStack,
ExplorerFamily,
ExplorerFamilyValue,
NativeToken,
RpcUrl,
RpcUrlSchema,
getChainIdNumber,
getDomainId,
getReorgPeriod,
isValidChainMetadata,
} from './metadata/chainMetadataTypes.js';
export { ZChainName, ZHash } from './metadata/customZodTypes.js';
export {
HyperlaneDeploymentArtifacts,
HyperlaneDeploymentArtifactsSchema,
@ -207,13 +213,9 @@ export {
WarpRouteConfigSchema,
} from './metadata/warpRouteConfig.js';
export {
AccountConfigSchema,
GetCallRemoteSettingsSchema,
} from './middleware/account/schemas.js';
export {
AccountConfig,
GetCallRemoteSettings,
} from './middleware/account/types.js';
InterchainAccountFactories,
interchainAccountFactories,
} from './middleware/account/contracts.js';
export { InterchainAccount } from './middleware/account/InterchainAccount.js';
export { InterchainAccountChecker } from './middleware/account/InterchainAccountChecker.js';
export {
@ -221,9 +223,14 @@ export {
InterchainAccountDeployer,
} from './middleware/account/InterchainAccountDeployer.js';
export {
InterchainAccountFactories,
interchainAccountFactories,
} from './middleware/account/contracts.js';
AccountConfigSchema,
GetCallRemoteSettingsSchema,
} from './middleware/account/schemas.js';
export {
AccountConfig,
GetCallRemoteSettings,
} from './middleware/account/types.js';
export { liquidityLayerFactories } from './middleware/liquidity-layer/contracts.js';
export { LiquidityLayerApp } from './middleware/liquidity-layer/LiquidityLayerApp.js';
export {
BridgeAdapterConfig,
@ -233,14 +240,13 @@ export {
LiquidityLayerDeployer,
PortalAdapterConfig,
} from './middleware/liquidity-layer/LiquidityLayerRouterDeployer.js';
export { liquidityLayerFactories } from './middleware/liquidity-layer/contracts.js';
export { interchainQueryFactories } from './middleware/query/contracts.js';
export { InterchainQuery } from './middleware/query/InterchainQuery.js';
export { InterchainQueryChecker } from './middleware/query/InterchainQueryChecker.js';
export {
InterchainQueryConfig,
InterchainQueryDeployer,
} from './middleware/query/InterchainQueryDeployer.js';
export { interchainQueryFactories } from './middleware/query/contracts.js';
export {
MultiProtocolProvider,
MultiProtocolProviderOptions,
@ -249,6 +255,18 @@ export {
MultiProvider,
MultiProviderOptions,
} from './providers/MultiProvider.js';
export {
ProviderBuilderFn,
ProviderBuilderMap,
TypedProviderBuilderFn,
defaultEthersV5ProviderBuilder,
defaultFuelProviderBuilder,
defaultProviderBuilder,
defaultProviderBuilderMap,
defaultSolProviderBuilder,
defaultViemProviderBuilder,
protocolToDefaultProviderBuilder,
} from './providers/providerBuilders.js';
export {
CosmJsContract,
CosmJsProvider,
@ -277,16 +295,7 @@ export {
ViemTransaction,
ViemTransactionReceipt,
} from './providers/ProviderType.js';
export {
ChainMetadataWithRpcConnectionInfo,
ProviderErrorResult,
ProviderPerformResult,
ProviderRetryOptions,
ProviderStatus,
ProviderSuccessResult,
ProviderTimeoutResult,
SmartProviderOptions,
} from './providers/SmartProvider/types.js';
export { ProviderRetryOptions } from './providers/SmartProvider/types.js';
export { HyperlaneEtherscanProvider } from './providers/SmartProvider/HyperlaneEtherscanProvider.js';
export { HyperlaneJsonRpcProvider } from './providers/SmartProvider/HyperlaneJsonRpcProvider.js';
export {
@ -296,18 +305,6 @@ export {
excludeProviderMethods,
} from './providers/SmartProvider/ProviderMethods.js';
export { HyperlaneSmartProvider } from './providers/SmartProvider/SmartProvider.js';
export {
ProviderBuilderFn,
ProviderBuilderMap,
TypedProviderBuilderFn,
defaultEthersV5ProviderBuilder,
defaultFuelProviderBuilder,
defaultProviderBuilder,
defaultProviderBuilderMap,
defaultSolProviderBuilder,
defaultViemProviderBuilder,
protocolToDefaultProviderBuilder,
} from './providers/providerBuilders.js';
export {
PopulatedTransactionSchema,
PopulatedTransactionsSchema,
@ -318,10 +315,10 @@ export {
PopulatedTransactions,
} from './providers/transactions/types.js';
export { TxSubmitterType } from './providers/transactions/submitter/TxSubmitterTypes.js';
export { SubmitterMetadataSchema } from './providers/transactions/submitter/schemas.js';
export { SubmitterMetadata } from './providers/transactions/submitter/types.js';
export { TxSubmitterInterface } from './providers/transactions/submitter/TxSubmitterInterface.js';
export { TxSubmitterType } from './providers/transactions/submitter/TxSubmitterTypes.js';
export { SubmitterMetadata } from './providers/transactions/submitter/types.js';
export {
EV5GnosisSafeTxSubmitterPropsSchema,
@ -333,24 +330,41 @@ export {
} from './providers/transactions/submitter/ethersV5/types.js';
export { SubmissionStrategySchema } from './providers/transactions/submitter/builder/schemas.js';
export { SubmissionStrategy } from './providers/transactions/submitter/builder/types.js';
export { TxSubmitterBuilder } from './providers/transactions/submitter/builder/TxSubmitterBuilder.js';
export { SubmissionStrategy } from './providers/transactions/submitter/builder/types.js';
export { EV5GnosisSafeTxSubmitter } from './providers/transactions/submitter/ethersV5/EV5GnosisSafeTxSubmitter.js';
export { EV5ImpersonatedAccountTxSubmitter } from './providers/transactions/submitter/ethersV5/EV5ImpersonatedAccountTxSubmitter.js';
export { EV5JsonRpcTxSubmitter } from './providers/transactions/submitter/ethersV5/EV5JsonRpcTxSubmitter.js';
export { EV5TxSubmitterInterface } from './providers/transactions/submitter/ethersV5/EV5TxSubmitterInterface.js';
export { TxTransformerType } from './providers/transactions/transformer/TxTransformerTypes.js';
export { TransformerMetadataSchema } from './providers/transactions/transformer/schemas.js';
export { TransformerMetadata } from './providers/transactions/transformer/types.js';
export { TxTransformerInterface } from './providers/transactions/transformer/TxTransformerInterface.js';
export { TxTransformerType } from './providers/transactions/transformer/TxTransformerTypes.js';
export { TransformerMetadata } from './providers/transactions/transformer/types.js';
export { EV5InterchainAccountTxTransformerPropsSchema } from './providers/transactions/transformer/ethersV5/schemas.js';
export { EV5InterchainAccountTxTransformerProps } from './providers/transactions/transformer/ethersV5/types.js';
export { EV5InterchainAccountTxTransformer } from './providers/transactions/transformer/ethersV5/EV5InterchainAccountTxTransformer.js';
export { EV5TxTransformerInterface } from './providers/transactions/transformer/ethersV5/EV5TxTransformerInterface.js';
export { EV5InterchainAccountTxTransformerPropsSchema } from './providers/transactions/transformer/ethersV5/schemas.js';
export { EV5InterchainAccountTxTransformerProps } from './providers/transactions/transformer/ethersV5/types.js';
export {
EvmGasRouterAdapter,
EvmRouterAdapter,
} from './router/adapters/EvmRouterAdapter.js';
export {
SealevelGasRouterAdapter,
SealevelRouterAdapter,
} from './router/adapters/SealevelRouterAdapter.js';
export { IGasRouterAdapter, IRouterAdapter } from './router/adapters/types.js';
export { GasRouterDeployer } from './router/GasRouterDeployer.js';
export { HyperlaneRouterChecker } from './router/HyperlaneRouterChecker.js';
export { HyperlaneRouterDeployer } from './router/HyperlaneRouterDeployer.js';
export {
MultiProtocolGasRouterApp,
MultiProtocolRouterApp,
} from './router/MultiProtocolRouterApps.js';
export { GasRouterApp, RouterApp } from './router/RouterApps.js';
export {
MailboxClientConfig as ConnectionClientConfig,
ClientViolation as ConnectionClientViolation,
@ -365,47 +379,11 @@ export {
RouterViolationType,
proxiedFactories,
} from './router/types.js';
export { GasRouterDeployer } from './router/GasRouterDeployer.js';
export { HyperlaneRouterChecker } from './router/HyperlaneRouterChecker.js';
export { HyperlaneRouterDeployer } from './router/HyperlaneRouterDeployer.js';
export {
MultiProtocolGasRouterApp,
MultiProtocolRouterApp,
} from './router/MultiProtocolRouterApps.js';
export { GasRouterApp, RouterApp } from './router/RouterApps.js';
export { IGasRouterAdapter, IRouterAdapter } from './router/adapters/types.js';
export {
EvmGasRouterAdapter,
EvmRouterAdapter,
} from './router/adapters/EvmRouterAdapter.js';
export {
SealevelGasRouterAdapter,
SealevelRouterAdapter,
} from './router/adapters/SealevelRouterAdapter.js';
export { IToken, TokenArgs, TokenConfigSchema } from './token/IToken.js';
export { Token } from './token/Token.js';
export { TokenAmount } from './token/TokenAmount.js';
export {
HyperlaneTokenConnection,
IbcToHyperlaneTokenConnection,
IbcTokenConnection,
TokenConnection,
TokenConnectionConfigSchema,
TokenConnectionType,
getTokenConnectionId,
parseTokenConnectionId,
} from './token/TokenConnection.js';
export {
PROTOCOL_TO_NATIVE_STANDARD,
TOKEN_COLLATERALIZED_STANDARDS,
TOKEN_COSMWASM_STANDARDS,
TOKEN_HYP_STANDARDS,
TOKEN_MULTI_CHAIN_STANDARDS,
TOKEN_NFT_STANDARDS,
TOKEN_STANDARD_TO_PROTOCOL,
TOKEN_TYPE_TO_STANDARD,
TokenStandard,
} from './token/TokenStandard.js';
CosmIbcToWarpTokenAdapter,
CosmIbcTokenAdapter,
CosmNativeTokenAdapter,
} from './token/adapters/CosmosTokenAdapter.js';
export {
CW20Metadata,
CwHypCollateralAdapter,
@ -414,11 +392,6 @@ export {
CwNativeTokenAdapter,
CwTokenAdapter,
} from './token/adapters/CosmWasmTokenAdapter.js';
export {
CosmIbcToWarpTokenAdapter,
CosmIbcTokenAdapter,
CosmNativeTokenAdapter,
} from './token/adapters/CosmosTokenAdapter.js';
export {
EvmHypCollateralAdapter,
EvmHypNativeAdapter,
@ -451,15 +424,38 @@ export { HypERC20App } from './token/app.js';
export { HypERC20Checker } from './token/checker.js';
export { TokenType } from './token/config.js';
export {
hypERC20factories,
HypERC20Factories,
HypERC721Factories,
TokenFactories,
hypERC20factories,
} from './token/contracts.js';
export { HypERC20Deployer, HypERC721Deployer } from './token/deploy.js';
export { EvmERC20WarpRouteReader } from './token/EvmERC20WarpRouteReader.js';
export { IToken, TokenArgs, TokenConfigSchema } from './token/IToken.js';
export { Token } from './token/Token.js';
export { TokenAmount } from './token/TokenAmount.js';
export {
HyperlaneTokenConnection,
IbcToHyperlaneTokenConnection,
IbcTokenConnection,
TokenConnection,
TokenConnectionConfigSchema,
TokenConnectionType,
getTokenConnectionId,
parseTokenConnectionId,
} from './token/TokenConnection.js';
export {
PROTOCOL_TO_NATIVE_STANDARD,
TOKEN_COLLATERALIZED_STANDARDS,
TOKEN_COSMWASM_STANDARDS,
TOKEN_HYP_STANDARDS,
TOKEN_MULTI_CHAIN_STANDARDS,
TOKEN_NFT_STANDARDS,
TOKEN_STANDARD_TO_PROTOCOL,
TOKEN_TYPE_TO_STANDARD,
TokenStandard,
} from './token/TokenStandard.js';
export { ChainMap, ChainName, ChainNameOrId, Connection } from './types.js';
export { MultiGeneric } from './utils/MultiGeneric.js';
export { getCosmosRegistryChain } from './utils/cosmos.js';
export { filterByChains } from './utils/filter.js';
export {
@ -470,7 +466,8 @@ export {
setFork,
stopImpersonatingAccount,
} from './utils/fork.js';
export { MultiGeneric } from './utils/MultiGeneric.js';
export { TokenRouterConfig, WarpRouteDeployConfig } from './token/types.js';
export { multisigIsmVerificationCost } from './utils/ism.js';
export {
SealevelAccountDataWrapper,
@ -478,7 +475,6 @@ export {
getSealevelAccountDataSchema,
} from './utils/sealevelSerialization.js';
export { chainMetadataToWagmiChain } from './utils/wagmi.js';
export { WarpCore, WarpCoreOptions } from './warp/WarpCore.js';
export {
FeeConstantConfig,
RouteBlacklist,
@ -487,29 +483,27 @@ export {
WarpTxCategory,
WarpTypedTransaction,
} from './warp/types.js';
export { WarpCore, WarpCoreOptions } from './warp/WarpCore.js';
export { AggregationIsmConfigSchema, IsmConfigSchema } from './ism/schemas.js';
export { MailboxClientConfigSchema as mailboxClientConfigSchema } from './router/schemas.js';
export {
WarpRouteDeployConfigSchema,
TokenRouterConfigSchema,
CollateralConfig,
NativeConfig,
TokenRouterConfigSchema,
WarpRouteDeployConfigSchema,
isCollateralConfig,
isNativeConfig,
isSyntheticConfig,
isTokenMetadata,
} from './token/schemas.js';
export { isCompliant } from './utils/schemas.js';
export { TokenRouterConfig, WarpRouteDeployConfig } from './token/types.js';
export { S3Validator } from './aws/validator.js';
export { S3Config, S3Wrapper, S3Receipt } from './aws/s3.js';
// prettier-ignore
// @ts-ignore
export { canProposeSafeTransactions, getSafe, getSafeDelegates, getSafeService } from './utils/gnosisSafe.js';
export { EvmCoreModule, DeployedCoreAdresses } from './core/EvmCoreModule.js';
export { EvmERC20WarpModule } from './token/EvmERC20WarpModule.js';
export { DeployedCoreAdresses, EvmCoreModule } from './core/EvmCoreModule.js';
export { EvmIsmModule } from './ism/EvmIsmModule.js';
export { EvmERC20WarpModule } from './token/EvmERC20WarpModule.js';
export { ProxyFactoryFactoriesAddresses } from './deploy/schemas.js';

@ -30,6 +30,7 @@ export interface ChainMetadataManagerOptions {
export class ChainMetadataManager<MetaExt = {}> {
public readonly metadata: ChainMap<ChainMetadata<MetaExt>> = {};
public readonly logger: Logger;
static readonly DEFAULT_MAX_BLOCK_RANGE = 1000;
/**
* Create a new ChainMetadataManager with the given chainMetadata,
@ -106,6 +107,17 @@ export class ChainMetadataManager<MetaExt = {}> {
return chainMetadata;
}
getMaxBlockRange(chainNameOrId: ChainNameOrId): number {
const metadata = this.getChainMetadata(chainNameOrId);
return Math.max(
...metadata.rpcUrls.map(
({ pagination }) =>
pagination?.maxBlockRange ??
ChainMetadataManager.DEFAULT_MAX_BLOCK_RANGE,
),
);
}
/**
* Returns true if the given chain name, chain id, or domain id is
* include in this manager's metadata, false otherwise

@ -277,6 +277,18 @@ export class MultiProvider<MetaExt = {}> extends ChainMetadataManager<MetaExt> {
return null;
}
/**
* Get the latest block range for a given chain's RPC provider
*/
async getLatestBlockRange(
chainNameOrId: ChainNameOrId,
rangeSize = this.getMaxBlockRange(chainNameOrId),
): Promise<{ fromBlock: number; toBlock: number }> {
const toBlock = await this.getProvider(chainNameOrId).getBlock('latest');
const fromBlock = Math.max(toBlock.number - rangeSize, 0);
return { fromBlock, toBlock: toBlock.number };
}
/**
* Get the transaction overrides for a given chain name, chain id, or domain id
* @throws if chain's metadata has not been set

@ -267,7 +267,7 @@ export class HyperlaneSmartProvider
if (result.status === ProviderStatus.Success) {
return result.value;
} else if (result.status === ProviderStatus.Timeout) {
this.logger.debug(
this.logger.trace(
`Slow response from provider #${pIndex}.${
!isLastProvider ? ' Triggering next provider.' : ''
}`,

@ -1,3 +1,5 @@
import { rootLogger } from './logging.js';
/**
* Return a promise that resolves in ms milliseconds.
* @param ms Time to wait
@ -95,6 +97,7 @@ export async function pollAsync<T>(
const ret = await runner();
return ret;
} catch (error) {
rootLogger.debug(`Error in pollAsync`, { error });
saveError = error;
attempts += 1;
await sleep(delayMs);

Loading…
Cancel
Save