UNPKG

@hyperlane-xyz/sdk

Version:

The official SDK for the Hyperlane Network

174 lines 7.05 kB
import { getAbiItem, parseEventLogs, toEventSelector } from 'viem'; import { IXERC20Lockbox__factory } from '@hyperlane-xyz/core'; import { rootLogger, sleep } from '@hyperlane-xyz/utils'; import { getContractDeploymentTransaction, getLogsFromEtherscanLikeExplorerAPI, } from '../block-explorer/etherscan.js'; import { ExplorerFamily } from '../metadata/chainMetadataTypes.js'; const minimalXERC20VSABI = [ { anonymous: false, inputs: [ { indexed: true, internalType: 'address', name: 'bridge', type: 'address', }, { indexed: false, internalType: 'uint112', name: 'bufferCap', type: 'uint112', }, { indexed: false, internalType: 'uint128', name: 'rateLimitPerSecond', type: 'uint128', }, ], name: 'ConfigurationChanged', type: 'event', }, ]; const CONFIGURATION_CHANGED_EVENT_SELECTOR = toEventSelector(getAbiItem({ abi: minimalXERC20VSABI, name: 'ConfigurationChanged', })); export async function getExtraLockBoxConfigs({ xERC20Address, chain, multiProvider, logger = rootLogger, }) { const { family, apiKey } = multiProvider.getExplorerApi(chain); // Fallback to use the rpc if the user has not provided an API key and the explorer family is Etherscan // because the endpoint requires it or the explorer family is not known const isExplorerConfiguredCorrectly = family === ExplorerFamily.Etherscan ? !!apiKey : true; const canUseExplorerApi = family !== ExplorerFamily.Other && isExplorerConfiguredCorrectly; const provider = multiProvider.getProvider(chain); let logs; if (canUseExplorerApi) { logs = await getConfigurationChangedLogsFromExplorerApi({ chain, multiProvider, xERC20Address, }); } else { logger.debug(`Using rpc request to retrieve bridges on on lockbox contract ${xERC20Address} on chain ${chain}`); // Should be safe to use even with public RPCs as the total number of events in this topic should be low logs = await getConfigurationChangedLogsFromRpc({ chain, multiProvider, xERC20Address, }); } const viemLogs = logs.map((log) => ({ address: log.address, data: log.data, blockNumber: BigInt(log.blockNumber), transactionHash: log.transactionHash, logIndex: Number(log.logIndex), transactionIndex: Number(log.transactionIndex), topics: log.topics, })); return getLockboxesFromLogs(viemLogs, provider, chain, logger); } async function getConfigurationChangedLogsFromExplorerApi({ xERC20Address, chain, multiProvider, }) { const { apiUrl, apiKey } = multiProvider.getExplorerApi(chain); const contractDeploymentTx = await getContractDeploymentTransaction({ apiUrl, apiKey }, { contractAddress: xERC20Address }); const provider = multiProvider.getProvider(chain); const [currentBlockNumber, deploymentTransactionReceipt] = await Promise.all([ provider.getBlockNumber(), provider.getTransactionReceipt(contractDeploymentTx.txHash), ]); return getLogsFromEtherscanLikeExplorerAPI({ apiUrl, apiKey }, { address: xERC20Address, fromBlock: deploymentTransactionReceipt.blockNumber, toBlock: currentBlockNumber, topic0: CONFIGURATION_CHANGED_EVENT_SELECTOR, }); } async function getConfigurationChangedLogsFromRpc({ xERC20Address, chain, multiProvider, }) { const provider = multiProvider.getProvider(chain); const endBlock = await provider.getBlockNumber(); let currentStartBlock = await getContractCreationBlockFromRpc(xERC20Address, provider); const logs = []; const range = 5000; while (currentStartBlock < endBlock) { const currentLogs = await provider.getLogs({ address: xERC20Address, fromBlock: currentStartBlock, toBlock: currentStartBlock + range, topics: [CONFIGURATION_CHANGED_EVENT_SELECTOR], }); logs.push(...currentLogs); currentStartBlock += range; // Sleep to avoid being rate limited with public RPCs await sleep(350); } return logs; } // Retrieves the contract deployment number by performing a binary search // calling getCode until the creation block is found async function getContractCreationBlockFromRpc(contractAddress, provider) { const latestBlock = await provider.getBlockNumber(); const latestCode = await provider.getCode(contractAddress, latestBlock); if (latestCode === '0x') { throw new Error('Provided address is not a contract'); } let low = 0; let high = latestBlock; let creationBlock = latestBlock; while (low <= high) { const mid = Math.floor((low + high) / 2); const code = await provider.getCode(contractAddress, mid); if (code !== '0x') { creationBlock = mid; high = mid - 1; } else { low = mid + 1; } } return creationBlock; } async function getLockboxesFromLogs(logs, provider, chain, logger) { const parsedLogs = parseEventLogs({ abi: minimalXERC20VSABI, eventName: 'ConfigurationChanged', logs, }); // A bridge might appear more than once in the event logs, we are only // interested in the most recent one for each bridge so we deduplicate // entries here const dedupedBridges = parsedLogs.reduce((acc, log) => { const bridgeAddress = log.args.bridge; const isMostRecentLogForBridge = log.blockNumber > (acc[bridgeAddress]?.blockNumber ?? 0n); if (isMostRecentLogForBridge) { acc[bridgeAddress] = log; } return acc; }, {}); const lockboxPromises = Object.values(dedupedBridges) // Removing bridges where the limits are set to 0 because it is equivalent of being deactivated .filter((log) => log.args.bufferCap !== 0n && log.args.rateLimitPerSecond !== 0n) .map(async (log) => { try { const maybeXERC20Lockbox = IXERC20Lockbox__factory.connect(log.args.bridge, provider); await maybeXERC20Lockbox.callStatic.XERC20(); return log; } catch { logger.debug(`Contract at address ${log.args.bridge} on chain ${chain} is not a XERC20Lockbox contract.`); return undefined; } }); const lockboxes = await Promise.all(lockboxPromises); return lockboxes .filter((log) => log !== undefined) .map((log) => log) .map((log) => ({ lockbox: log.args.bridge, limits: { bufferCap: log.args.bufferCap.toString(), rateLimitPerSecond: log.args.rateLimitPerSecond.toString(), }, })); } //# sourceMappingURL=xerc20.js.map