@hyperlane-xyz/sdk
Version:
The official SDK for the Hyperlane Network
128 lines • 5.73 kB
JavaScript
import { z } from 'zod';
import { rootLogger } from '@hyperlane-xyz/utils';
import { getContractDeploymentTransaction, getLogsFromEtherscanLikeExplorerAPI, } from '../../block-explorer/etherscan.js';
import { assertIsContractAddress } from '../../contracts/contracts.js';
import { ZBytes32String, ZHash, ZUint } from '../../metadata/customZodTypes.js';
import { getContractCreationBlockFromRpc, getLogsFromRpc } from './utils.js';
export const GetLogByTopicOptionsSchema = z.object({
eventTopic: ZBytes32String,
contractAddress: ZHash,
fromBlock: ZUint.optional(),
toBlock: ZUint.optional(),
});
export const RequiredGetLogByTopicOptionsSchema = GetLogByTopicOptionsSchema.required();
export class EvmEtherscanLikeEventLogsReader {
chain;
config;
multiProvider;
constructor(chain, config, multiProvider) {
this.chain = chain;
this.config = config;
this.multiProvider = multiProvider;
}
async getContractDeploymentBlockNumber(address) {
const contractDeploymentTx = await getContractDeploymentTransaction({ apiUrl: this.config.apiUrl, apiKey: this.config.apiKey }, { contractAddress: address });
const deploymentTransactionReceipt = await this.multiProvider
.getProvider(this.chain)
.getTransactionReceipt(contractDeploymentTx.txHash);
return deploymentTransactionReceipt.blockNumber;
}
async getContractLogs(options) {
const parsedOptions = RequiredGetLogByTopicOptionsSchema.parse(options);
return getLogsFromEtherscanLikeExplorerAPI({
apiUrl: this.config.apiUrl,
apiKey: this.config.apiKey,
}, {
address: parsedOptions.contractAddress,
fromBlock: parsedOptions.fromBlock,
toBlock: parsedOptions.toBlock,
topic0: parsedOptions.eventTopic,
});
}
}
export class EvmRpcEventLogsReader {
chain;
config;
multiProvider;
constructor(chain, config, multiProvider) {
this.chain = chain;
this.config = config;
this.multiProvider = multiProvider;
}
getContractDeploymentBlockNumber(address) {
return getContractCreationBlockFromRpc(this.chain, address, this.multiProvider);
}
getContractLogs(options) {
const parsedOptions = RequiredGetLogByTopicOptionsSchema.parse(options);
return getLogsFromRpc({
chain: this.chain,
contractAddress: parsedOptions.contractAddress,
topic: parsedOptions.eventTopic,
fromBlock: parsedOptions.fromBlock,
toBlock: parsedOptions.toBlock,
multiProvider: this.multiProvider,
range: this.config.paginationBlockRange,
});
}
}
export class EvmEventLogsReader {
config;
multiProvider;
logReaderStrategy;
logger;
fallbackLogReaderStrategy;
constructor(config, multiProvider, logReaderStrategy, logger, fallbackLogReaderStrategy) {
this.config = config;
this.multiProvider = multiProvider;
this.logReaderStrategy = logReaderStrategy;
this.logger = logger;
this.fallbackLogReaderStrategy = fallbackLogReaderStrategy;
}
static fromConfig(config, multiProvider, logger = rootLogger.child({
module: EvmEventLogsReader.name,
})) {
const explorer = multiProvider.tryGetEvmExplorerMetadata(config.chain);
let logReaderStrategy;
let fallbackLogReaderSrategy;
if (explorer && !config.useRPC) {
logReaderStrategy = new EvmEtherscanLikeEventLogsReader(config.chain, explorer, multiProvider);
fallbackLogReaderSrategy = new EvmRpcEventLogsReader(config.chain, { paginationBlockRange: config.paginationBlockRange }, multiProvider);
}
else {
logReaderStrategy = new EvmRpcEventLogsReader(config.chain, { paginationBlockRange: config.paginationBlockRange }, multiProvider);
}
return new EvmEventLogsReader(config, multiProvider, logReaderStrategy, logger, fallbackLogReaderSrategy);
}
async getLogsByTopic(options) {
const provider = this.multiProvider.getProvider(this.config.chain);
await assertIsContractAddress(this.multiProvider, this.config.chain, options.contractAddress);
try {
// do NOT remove the await here it is on purpose to catch any error
// here to fallback to the rpc if any is set.
// Removing the await will cause the caller to handle the error if any
// and the fallback logic won't run
const res = await this.getLogsByTopicWithStrategy(options, provider, this.logReaderStrategy);
return res;
}
catch (err) {
if (!this.fallbackLogReaderStrategy) {
throw err;
}
this.logger.debug(`Failed to read logs on chain "${this.config.chain}": ${err}. Falling back to using the RPC`);
return this.getLogsByTopicWithStrategy(options, provider, this.fallbackLogReaderStrategy);
}
}
async getLogsByTopicWithStrategy(options, provider, logReaderStrategy) {
const parsedOptions = GetLogByTopicOptionsSchema.parse(options);
const fromBlock = parsedOptions.fromBlock ??
(await logReaderStrategy.getContractDeploymentBlockNumber(parsedOptions.contractAddress));
const toBlock = parsedOptions.toBlock ?? (await provider.getBlockNumber());
return logReaderStrategy.getContractLogs({
contractAddress: parsedOptions.contractAddress,
eventTopic: parsedOptions.eventTopic,
fromBlock,
toBlock,
});
}
}
//# sourceMappingURL=EvmEventLogsReader.js.map