UNPKG

@hyperlane-xyz/sdk

Version:

The official SDK for the Hyperlane Network

137 lines 5.58 kB
import { parseEventLogs } from 'viem'; import { normalizeAddress, rootLogger } from '@hyperlane-xyz/utils'; import { MultiProtocolProvider } from '../providers/MultiProtocolProvider.js'; import { HyperlaneReader } from '../utils/HyperlaneReader.js'; import { EvmXERC20Adapter, EvmXERC20VSAdapter, } from './adapters/EvmTokenAdapter.js'; import { XERC20Type } from './types.js'; import { CONFIGURATION_CHANGED_EVENT_SELECTOR, XERC20_VS_ABI, } from './xerc20-abi.js'; import { deriveXERC20TokenType } from './xerc20.js'; /** * Reader for on-chain XERC20 state. * Reads limits and bridge configurations from XERC20 contracts. */ export class EvmXERC20Reader extends HyperlaneReader { multiProvider; logger = rootLogger.child({ module: 'EvmXERC20Reader' }); multiProtocolProvider; constructor(multiProvider, chain) { super(multiProvider, chain); this.multiProvider = multiProvider; this.multiProtocolProvider = MultiProtocolProvider.fromMultiProvider(multiProvider); } async deriveXERC20TokenType(xERC20Address) { return deriveXERC20TokenType(this.multiProvider, this.chain, xERC20Address); } /** * Read current limits for the specified bridges. */ async readLimits(xERC20Address, bridges, type) { const limitsMap = {}; const chainName = this.multiProvider.getChainName(this.chain); if (type === XERC20Type.Standard) { const adapter = new EvmXERC20Adapter(chainName, this.multiProtocolProvider, { token: xERC20Address }); for (const bridge of bridges) { const limits = await adapter.getLimits(bridge); limitsMap[bridge] = this.toStandardLimits(limits); } } else { const adapter = new EvmXERC20VSAdapter(chainName, this.multiProtocolProvider, { token: xERC20Address }); for (const bridge of bridges) { const rateLimits = await adapter.getRateLimits(bridge); limitsMap[bridge] = this.toVeloLimits(rateLimits); } } return limitsMap; } /** * Read all bridges configured on-chain for a Velodrome XERC20 by parsing ConfigurationChanged events. * Returns empty array for Standard XERC20 since it has no event-based bridge enumeration. * Note: Queries from block 0 which may be slow on chains with long histories. */ async readOnChainBridges(xERC20Address, type) { if (type === XERC20Type.Standard) { this.logger.debug('Standard XERC20 does not support on-chain bridge enumeration'); return []; } const filter = { address: xERC20Address, topics: [CONFIGURATION_CHANGED_EVENT_SELECTOR], fromBlock: 0, toBlock: 'latest', }; const rawLogs = await this.provider.getLogs(filter); const logs = rawLogs.map((log) => ({ address: log.address, blockHash: log.blockHash, blockNumber: BigInt(log.blockNumber), data: log.data, logIndex: log.logIndex, transactionHash: log.transactionHash, transactionIndex: log.transactionIndex, removed: log.removed, topics: log.topics, })); const parsedLogs = parseEventLogs({ abi: XERC20_VS_ABI, eventName: 'ConfigurationChanged', logs, }); // Track latest log per bridge (use logIndex as tiebreaker for same block) const bridgeToLatestLog = new Map(); for (const log of parsedLogs) { const bridge = normalizeAddress(log.args.bridge); const existing = bridgeToLatestLog.get(bridge); const isMoreRecent = !existing || log.blockNumber > existing.blockNumber || (log.blockNumber === existing.blockNumber && log.logIndex > existing.logIndex); if (isMoreRecent) { bridgeToLatestLog.set(bridge, log); } } // Filter to active bridges (non-zero limits) const activeBridges = []; for (const [bridge, log] of bridgeToLatestLog) { const hasNonZeroLimits = log.args.bufferCap !== 0n || log.args.rateLimitPerSecond !== 0n; if (hasNonZeroLimits) { activeBridges.push(bridge); } } return activeBridges; } toStandardLimits(limits) { return { type: XERC20Type.Standard, mint: limits.mint.toString(), burn: limits.burn.toString(), }; } toVeloLimits(rateLimits) { return { type: XERC20Type.Velo, bufferCap: rateLimits.bufferCap.toString(), rateLimitPerSecond: rateLimits.rateLimitPerSecond.toString(), }; } } export function limitsAreZero(limits) { if (limits.type === XERC20Type.Standard) { return limits.mint === '0' && limits.burn === '0'; } return limits.bufferCap === '0' && limits.rateLimitPerSecond === '0'; } export function limitsMatch(a, b) { if (a.type !== b.type) return false; if (a.type === XERC20Type.Standard && b.type === XERC20Type.Standard) { return a.mint === b.mint && a.burn === b.burn; } if (a.type === XERC20Type.Velo && b.type === XERC20Type.Velo) { return (a.bufferCap === b.bufferCap && a.rateLimitPerSecond === b.rateLimitPerSecond); } return false; } //# sourceMappingURL=EvmXERC20Reader.js.map