@hyperlane-xyz/sdk
Version:
The official SDK for the Hyperlane Network
137 lines • 5.58 kB
JavaScript
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