UNPKG

@hyperlane-xyz/sdk

Version:

The official SDK for the Hyperlane Network

680 lines 34.2 kB
import { compareVersions } from 'compare-versions'; import { BigNumber, Contract, constants } from 'ethers'; import { EverclearTokenBridge__factory, HypERC20Collateral__factory, HypERC20__factory, HypERC4626Collateral__factory, HypERC4626OwnerCollateral__factory, HypERC4626__factory, HypXERC20Lockbox__factory, HypXERC20__factory, IFiatToken__factory, IMessageTransmitter__factory, ISafe__factory, IWETH__factory, IXERC20__factory, MovableCollateralRouter__factory, OpL1NativeTokenBridge__factory, OpL2NativeTokenBridge__factory, Ownable__factory, PackageVersioned__factory, ProxyAdmin__factory, TokenBridgeCctpBase__factory, TokenBridgeCctpV2__factory, TokenRouter__factory, } from '@hyperlane-xyz/core'; import { buildArtifact as coreBuildArtifact } from '@hyperlane-xyz/core/buildArtifact.js'; import { arrayToObject, assert, eqAddress, getLogLevel, isZeroish, isZeroishAddress, objFilter, objMap, promiseObjAll, rootLogger, } from '@hyperlane-xyz/utils'; import { ExplorerLicenseType } from '../block-explorer/etherscan.js'; import { DEFAULT_CONTRACT_READ_CONCURRENCY } from '../consts/concurrency.js'; import { isAddressActive } from '../contracts/contracts.js'; import { ContractVerifier } from '../deploy/verify/ContractVerifier.js'; import { VerifyContractTypes } from '../deploy/verify/types.js'; import { EvmTokenFeeReader } from '../fee/EvmTokenFeeReader.js'; import { EvmHookReader } from '../hook/EvmHookReader.js'; import { EvmIsmReader } from '../ism/EvmIsmReader.js'; import { EvmRouterReader } from '../router/EvmRouterReader.js'; import { isProxy, proxyAdmin, proxyImplementation } from './../deploy/proxy.js'; import { NON_ZERO_SENDER_ADDRESS, TokenType } from './config.js'; import { ContractVerificationStatus, HypTokenConfigSchema, OwnerStatus, XERC20Type, isMovableCollateralTokenConfig, } from './types.js'; import { getExtraLockBoxConfigs } from './xerc20.js'; const REBALANCING_CONTRACT_VERSION = '8.0.0'; export const TOKEN_FEE_CONTRACT_VERSION = '10.0.0'; export class EvmERC20WarpRouteReader extends EvmRouterReader { multiProvider; chain; concurrency; logger = rootLogger.child({ module: 'EvmERC20WarpRouteReader', }); // Using null instead of undefined to force // a compile error when adding a new token type deriveTokenConfigMap; evmHookReader; evmIsmReader; evmTokenFeeReader; contractVerifier; constructor(multiProvider, chain, concurrency = DEFAULT_CONTRACT_READ_CONCURRENCY, contractVerifier) { super(multiProvider, chain); this.multiProvider = multiProvider; this.chain = chain; this.concurrency = concurrency; this.evmHookReader = new EvmHookReader(multiProvider, chain, concurrency); this.evmIsmReader = new EvmIsmReader(multiProvider, chain, concurrency); this.evmTokenFeeReader = new EvmTokenFeeReader(multiProvider, chain); this.deriveTokenConfigMap = { [TokenType.XERC20]: this.deriveHypXERC20TokenConfig.bind(this), [TokenType.XERC20Lockbox]: this.deriveHypXERC20LockboxTokenConfig.bind(this), [TokenType.collateral]: this.deriveHypCollateralTokenConfig.bind(this), [TokenType.collateralFiat]: this.deriveHypCollateralFiatTokenConfig.bind(this), [TokenType.collateralVault]: this.deriveHypCollateralVaultTokenConfig.bind(this), [TokenType.collateralCctp]: this.deriveHypCollateralCctpTokenConfig.bind(this), [TokenType.collateralVaultRebase]: this.deriveHypCollateralVaultRebaseTokenConfig.bind(this), [TokenType.native]: this.deriveHypNativeTokenConfig.bind(this), [TokenType.nativeOpL2]: this.deriveOpL2TokenConfig.bind(this), [TokenType.nativeOpL1]: this.deriveOpL1TokenConfig.bind(this), [TokenType.synthetic]: this.deriveHypSyntheticTokenConfig.bind(this), [TokenType.syntheticRebase]: this.deriveHypSyntheticRebaseConfig.bind(this), [TokenType.nativeScaled]: null, [TokenType.collateralUri]: null, [TokenType.syntheticUri]: null, [TokenType.ethEverclear]: this.deriveEverclearEthTokenBridgeConfig.bind(this), [TokenType.collateralEverclear]: this.deriveEverclearCollateralTokenBridgeConfig.bind(this), }; this.contractVerifier = contractVerifier ?? new ContractVerifier(multiProvider, {}, coreBuildArtifact, ExplorerLicenseType.MIT); } /** * Derives the configuration for a Hyperlane ERC20 router contract at the given address. * * @param warpRouteAddress - The address of the Hyperlane ERC20 router contract. * @returns The configuration for the Hyperlane ERC20 router. * */ async deriveWarpRouteConfig(warpRouteAddress) { // Derive the config type const type = await this.deriveTokenType(warpRouteAddress); const tokenConfig = await this.fetchTokenConfig(type, warpRouteAddress); const routerConfig = await this.readRouterConfig(warpRouteAddress); // if the token has not been deployed as a proxy do not derive the config // inevm warp routes are an example const proxyAdmin = (await isProxy(this.provider, warpRouteAddress)) ? await this.fetchProxyAdminConfig(warpRouteAddress) : undefined; const destinationGas = await this.fetchDestinationGas(warpRouteAddress); const hasRebalancingInterface = compareVersions(tokenConfig.contractVersion, REBALANCING_CONTRACT_VERSION) >= 0; let allowedRebalancers; let allowedRebalancingBridges; if (hasRebalancingInterface && isMovableCollateralTokenConfig(tokenConfig)) { const movableToken = MovableCollateralRouter__factory.connect(warpRouteAddress, this.provider); try { allowedRebalancers = await MovableCollateralRouter__factory.connect(warpRouteAddress, this.provider).allowedRebalancers(); } catch (error) { // If this crashes it probably is because the token implementation has not been updated to be a movable collateral this.logger.error(`Failed to get configured rebalancers for token at "${warpRouteAddress}" on chain ${this.chain}`, error); } try { const knownDomains = await movableToken.domains(); const allowedBridgesByDomain = await promiseObjAll(objMap(arrayToObject(knownDomains.map((domain) => domain.toString())), (domain) => movableToken.allowedBridges(domain))); allowedRebalancingBridges = objFilter(objMap(allowedBridgesByDomain, (_domain, bridges) => bridges.map((bridge) => ({ bridge }))), // Remove domains that do not have allowed bridges (_domain, bridges) => bridges.length !== 0); } catch (error) { // If this crashes it probably is because the token implementation has not been updated to be a movable collateral this.logger.error(`Failed to get allowed rebalancer bridges for token at "${warpRouteAddress}" on chain ${this.chain}`, error); } const tokenFee = await this.fetchTokenFee(warpRouteAddress, await movableToken.domains()); return { ...routerConfig, ...tokenConfig, allowedRebalancers, allowedRebalancingBridges, proxyAdmin, destinationGas, tokenFee, }; } return { ...routerConfig, ...tokenConfig, proxyAdmin, destinationGas, }; } async fetchTokenFee(routerAddress, destinations) { const TokenRouter = TokenRouter__factory.connect(routerAddress, this.provider); const [packageVersion, tokenFee] = await Promise.all([ this.fetchPackageVersion(routerAddress), TokenRouter.feeRecipient().catch(() => constants.AddressZero), ]); const hasTokenFeeInterface = compareVersions(packageVersion, TOKEN_FEE_CONTRACT_VERSION) >= 0; if (!hasTokenFeeInterface) { this.logger.debug(`Token at address "${routerAddress}" on chain "${this.chain}" does not have a token fee interface`); return undefined; } if (isZeroishAddress(tokenFee)) { this.logger.debug(`Token at address "${routerAddress}" on chain "${this.chain}" has a no token fee`); return undefined; } return this.evmTokenFeeReader.deriveTokenFeeConfig({ address: tokenFee, routingDestinations: destinations, }); } async getContractVerificationStatus(chain, address) { const contractVerificationStatus = {}; const contractType = (await isProxy(this.provider, address)) ? VerifyContractTypes.Proxy : VerifyContractTypes.Implementation; if (this.multiProvider.isLocalRpc(chain)) { this.logger.debug('Skipping verification for local endpoints'); return { [contractType]: ContractVerificationStatus.Skipped }; } contractVerificationStatus[contractType] = await this.contractVerifier.getContractVerificationStatus(chain, address); if (contractType === VerifyContractTypes.Proxy) { contractVerificationStatus[VerifyContractTypes.Implementation] = await this.contractVerifier.getContractVerificationStatus(chain, await proxyImplementation(this.provider, address)); // Derive ProxyAdmin status contractVerificationStatus[VerifyContractTypes.ProxyAdmin] = await this.contractVerifier.getContractVerificationStatus(chain, await proxyAdmin(this.provider, address)); } return contractVerificationStatus; } async getOwnerStatus(chain, address) { let ownerStatus = {}; if (this.multiProvider.isLocalRpc(chain)) { this.logger.debug('Skipping owner verification for local endpoints'); return { [address]: OwnerStatus.Skipped, }; } const provider = this.multiProvider.getProvider(chain); const owner = await Ownable__factory.connect(address, provider).owner(); ownerStatus[owner] = (await isAddressActive(provider, owner)) ? OwnerStatus.Active : OwnerStatus.Inactive; // Heuristically check if the owner could be a safe by calling expected functions // This status will overwrite 'active' status try { const potentialGnosisSafe = ISafe__factory.connect(owner, provider); await Promise.all([ potentialGnosisSafe.getThreshold(), potentialGnosisSafe.nonce(), ]); ownerStatus[owner] = OwnerStatus.GnosisSafe; } catch { this.logger.debug(`${owner} may not be a safe`); } // Check Proxy admin and implementation recursively const contractType = (await isProxy(this.provider, address)) ? VerifyContractTypes.Proxy : VerifyContractTypes.Implementation; if (contractType === VerifyContractTypes.Proxy) { const [proxyStatus, implementationStatus] = await Promise.all([ this.getOwnerStatus(chain, await proxyAdmin(provider, address)), this.getOwnerStatus(chain, await proxyImplementation(this.provider, address)), ]); ownerStatus = { ...ownerStatus, ...proxyStatus, ...implementationStatus, }; } return ownerStatus; } async deriveWarpRouteVirtualConfig(chain, address) { const virtualConfig = { contractVerificationStatus: await this.getContractVerificationStatus(chain, address), // Used to check if the top address owner's nonce or code === 0 ownerStatus: await this.getOwnerStatus(chain, address), }; return virtualConfig; } /** * Derives the token type for a given Warp Route address using specific methods * * @param warpRouteAddress - The Warp Route address to derive the token type for. * @returns The derived token type, which can be one of: collateralVault, collateral, native, or synthetic. */ async deriveTokenType(warpRouteAddress) { const contractTypes = { [TokenType.collateralVault]: { factory: HypERC4626OwnerCollateral__factory, method: 'assetDeposited', }, [TokenType.collateralVaultRebase]: { factory: HypERC4626Collateral__factory, method: 'NULL_RECIPIENT', }, [TokenType.XERC20Lockbox]: { factory: HypXERC20Lockbox__factory, method: 'lockbox', }, [TokenType.collateralCctp]: { factory: TokenBridgeCctpBase__factory, method: 'messageTransmitter', }, [TokenType.collateral]: { factory: HypERC20Collateral__factory, method: 'wrappedToken', }, [TokenType.syntheticRebase]: { factory: HypERC4626__factory, method: 'collateralDomain', }, }; // Temporarily turn off SmartProvider logging // Provider errors are expected because deriving will call methods that may not exist in the Bytecode this.setSmartProviderLogLevel('silent'); try { // First, try checking token specific methods for (const [tokenType, { factory, method }] of Object.entries(contractTypes)) { try { const warpRoute = factory.connect(warpRouteAddress, this.provider); await warpRoute[method](); if (tokenType === TokenType.collateral) { const wrappedToken = await warpRoute.wrappedToken(); try { const xerc20 = IXERC20__factory.connect(wrappedToken, this.provider); await xerc20['mintingCurrentLimitOf(address)'](warpRouteAddress); return TokenType.XERC20; } catch (error) { this.logger.debug(`Warp route token at address "${warpRouteAddress}" on chain "${this.chain}" is not a ${TokenType.XERC20}`, error); } try { const fiatToken = IFiatToken__factory.connect(wrappedToken, this.provider); // Simulate minting tokens from the warp route contract await fiatToken.callStatic.mint(NON_ZERO_SENDER_ADDRESS, 1, { from: warpRouteAddress, }); return TokenType.collateralFiat; } catch (error) { this.logger.debug(`Warp route token at address "${warpRouteAddress}" on chain "${this.chain}" is not a ${TokenType.collateralFiat}`, error); } try { const maybeEverclearTokenBridge = EverclearTokenBridge__factory.connect(warpRouteAddress, this.provider); await maybeEverclearTokenBridge.callStatic.everclearAdapter(); let everclearTokenType = TokenType.collateralEverclear; try { // if simulating an ETH transfer works this should be the WETH contract await this.provider.estimateGas({ from: NON_ZERO_SENDER_ADDRESS, to: wrappedToken, data: IWETH__factory.createInterface().encodeFunctionData('deposit'), value: 0, }); everclearTokenType = TokenType.ethEverclear; } catch (error) { this.logger.debug(`Warp route token at address "${warpRouteAddress}" on chain "${this.chain}" is not a ${TokenType.collateralEverclear}`, error); } return everclearTokenType; } catch (error) { this.logger.debug(`Warp route token at address "${warpRouteAddress}" on chain "${this.chain}" is not a ${TokenType.collateralEverclear}`, error); } } return tokenType; } catch { continue; } } const packageVersion = await this.fetchPackageVersion(warpRouteAddress); const hasTokenFeeInterface = compareVersions(packageVersion, TOKEN_FEE_CONTRACT_VERSION) >= 0; const isNativeToken = await this.isNativeWarpToken(warpRouteAddress, hasTokenFeeInterface); if (isNativeToken) { return TokenType.native; } const isSyntheticToken = await this.isSyntheticWarpToken(warpRouteAddress, hasTokenFeeInterface); if (isSyntheticToken) { return TokenType.synthetic; } throw new Error(`Error deriving token type for token at address "${warpRouteAddress}" on chain "${this.chain}"`); } finally { this.setSmartProviderLogLevel(getLogLevel()); } } async isNativeWarpToken(warpRouteAddress, hasTokenFeeInterface) { try { if (hasTokenFeeInterface) { const tokenRouter = TokenRouter__factory.connect(warpRouteAddress, this.provider); const tokenAddress = await tokenRouter.token(); // Native token returns address(0) return isZeroishAddress(tokenAddress); } else { // Check native using estimateGas to send 0 wei. Success implies that the Warp Route has a receive() function await this.multiProvider.estimateGas(this.chain, { to: warpRouteAddress, value: BigNumber.from(0), }, NON_ZERO_SENDER_ADDRESS); return true; } } catch (e) { this.logger.debug(`Warp route token at address "${warpRouteAddress}" on chain "${this.chain}" is not a ${TokenType.native}`, e); return false; } } async isSyntheticWarpToken(warpRouteAddress, hasTokenFeeInterface) { try { if (hasTokenFeeInterface) { const tokenRouter = TokenRouter__factory.connect(warpRouteAddress, this.provider); const tokenAddress = await tokenRouter.token(); // HypERC20.token() returns address(this) return eqAddress(tokenAddress, warpRouteAddress); } else { const tokenRouter = HypERC20__factory.connect(warpRouteAddress, this.provider); await tokenRouter.decimals(); return true; } } catch (error) { this.logger.debug(`Warp route token at address "${warpRouteAddress}" on chain "${this.chain}" is not a ${TokenType.synthetic}`, error); return false; } } async fetchXERC20Config(xERC20Address, warpRouteAddress) { // fetch the limits if possible const rateLimitsABI = [ 'function rateLimitPerSecond(address) external view returns (uint128)', 'function bufferCap(address) external view returns (uint112)', ]; const xERC20 = new Contract(xERC20Address, rateLimitsABI, this.provider); try { const extraBridgesLimits = await getExtraLockBoxConfigs({ chain: this.chain, multiProvider: this.multiProvider, xERC20Address, logger: this.logger, }); // TODO: fix this such that it fetches from WL's values too return { xERC20: { warpRouteLimits: { type: XERC20Type.Velo, rateLimitPerSecond: (await xERC20.rateLimitPerSecond(warpRouteAddress)).toString(), bufferCap: (await xERC20.bufferCap(warpRouteAddress)).toString(), }, extraBridges: extraBridgesLimits.length > 0 ? extraBridgesLimits : undefined, }, }; } catch (error) { this.logger.error(`Error fetching xERC20 limits for token at ${xERC20Address} on chain ${this.chain}`, error); return {}; } } /** * Fetches the metadata for a token address. * * @param warpRouteAddress - The address of the token. * @returns A partial ERC20 metadata object containing the token name, symbol, total supply, and decimals. * Throws if unsupported token type */ async fetchTokenConfig(type, warpRouteAddress) { const deriveFunction = this.deriveTokenConfigMap[type]; if (!deriveFunction) { throw new Error(`Provided unsupported token type "${type}" when fetching token metadata on chain "${this.chain}" at address "${warpRouteAddress}"`); } const config = await deriveFunction(warpRouteAddress); config.contractVersion = await this.fetchPackageVersion(warpRouteAddress); return HypTokenConfigSchema.parse(config); } async deriveHypXERC20TokenConfig(hypTokenAddress) { const hypXERC20TokenInstance = HypXERC20__factory.connect(hypTokenAddress, this.provider); const collateralTokenAddress = await hypXERC20TokenInstance.wrappedToken(); const [erc20TokenMetadata, xERC20Metadata] = await Promise.all([ this.fetchERC20Metadata(collateralTokenAddress), this.fetchXERC20Config(collateralTokenAddress, hypTokenAddress), ]); return { ...erc20TokenMetadata, type: TokenType.XERC20, token: collateralTokenAddress, xERC20: xERC20Metadata.xERC20, }; } async deriveHypXERC20LockboxTokenConfig(hypTokenAddress) { const hypXERC20TokenLockboxTokenInstance = HypXERC20Lockbox__factory.connect(hypTokenAddress, this.provider); const xerc20TokenAddress = await hypXERC20TokenLockboxTokenInstance.xERC20(); const [erc20TokenMetadata, xERC20Metadata, lockbox] = await Promise.all([ this.fetchERC20Metadata(xerc20TokenAddress), this.fetchXERC20Config(xerc20TokenAddress, hypTokenAddress), hypXERC20TokenLockboxTokenInstance.lockbox(), ]); return { ...erc20TokenMetadata, type: TokenType.XERC20Lockbox, token: lockbox, xERC20: xERC20Metadata.xERC20, }; } async deriveHypCollateralCctpTokenConfig(hypToken) { const collateralConfig = await this.deriveHypCollateralTokenConfig(hypToken); const tokenBridge = TokenBridgeCctpBase__factory.connect(hypToken, this.provider); const [messageTransmitter, tokenMessenger, urls] = await Promise.all([ tokenBridge.messageTransmitter(), tokenBridge.tokenMessenger(), tokenBridge.urls(), ]); const onchainCctpVersion = await IMessageTransmitter__factory.connect(messageTransmitter, this.provider).version(); if (onchainCctpVersion === 0) { return { ...collateralConfig, type: TokenType.collateralCctp, cctpVersion: 'V1', messageTransmitter, tokenMessenger, urls, }; } else if (onchainCctpVersion === 1) { const tokenBridgeV2 = TokenBridgeCctpV2__factory.connect(hypToken, this.provider); const [minFinalityThreshold, maxFeeBps] = await Promise.all([ tokenBridgeV2.minFinalityThreshold(), tokenBridgeV2.maxFeeBps(), ]); return { ...collateralConfig, type: TokenType.collateralCctp, cctpVersion: 'V2', messageTransmitter, tokenMessenger, urls, minFinalityThreshold, maxFeeBps: maxFeeBps.toNumber(), }; } else { throw new Error(`Unsupported CCTP version ${onchainCctpVersion}`); } } async deriveHypCollateralTokenConfig(hypToken) { const hypCollateralTokenInstance = HypERC20Collateral__factory.connect(hypToken, this.provider); const collateralTokenAddress = await hypCollateralTokenInstance.wrappedToken(); const erc20TokenMetadata = await this.fetchERC20Metadata(collateralTokenAddress); return { ...erc20TokenMetadata, type: TokenType.collateral, token: collateralTokenAddress, }; } async deriveHypCollateralFiatTokenConfig(hypToken) { const erc20TokenMetadata = await this.deriveHypCollateralTokenConfig(hypToken); return { ...erc20TokenMetadata, type: TokenType.collateralFiat, }; } async deriveHypCollateralVaultTokenConfig(hypToken) { const erc20TokenMetadata = await this.deriveHypCollateralTokenConfig(hypToken); return { ...erc20TokenMetadata, token: await HypERC4626OwnerCollateral__factory.connect(hypToken, this.provider).vault(), type: TokenType.collateralVault, }; } async deriveHypCollateralVaultRebaseTokenConfig(hypToken) { const erc20TokenMetadata = await this.deriveHypCollateralTokenConfig(hypToken); return { ...erc20TokenMetadata, token: await HypERC4626Collateral__factory.connect(hypToken, this.provider).vault(), type: TokenType.collateralVaultRebase, }; } async deriveHypSyntheticTokenConfig(hypTokenAddress) { const erc20TokenMetadata = await this.fetchERC20Metadata(hypTokenAddress); return { ...erc20TokenMetadata, type: TokenType.synthetic, }; } async deriveHypNativeTokenConfig(_address) { const chainMetadata = this.multiProvider.getChainMetadata(this.chain); if (!chainMetadata.nativeToken) { throw new Error(`Warp route config specifies native token but chain metadata for chain "${this.chain}" does not provide native token details`); } const { name, symbol, decimals } = chainMetadata.nativeToken; return { type: TokenType.native, name, symbol, decimals, isNft: false, }; } async deriveOpL2TokenConfig(_address) { const config = await this.deriveHypNativeTokenConfig(_address); const contract = OpL2NativeTokenBridge__factory.connect(_address, this.multiProvider.getProvider(this.chain)); const l2Bridge = await contract.l2Bridge(); return { ...config, type: TokenType.nativeOpL2, l2Bridge, }; } async deriveOpL1TokenConfig(_address) { const config = await this.deriveHypNativeTokenConfig(_address); const contract = OpL1NativeTokenBridge__factory.connect(_address, this.multiProvider.getProvider(this.chain)); const urls = await contract.urls(); const portal = await contract.opPortal(); return { ...config, type: TokenType.nativeOpL1, urls, portal, // assume version 1 for now version: 1, }; } async deriveHypSyntheticRebaseConfig(hypTokenAddress) { const hypERC4626 = HypERC4626__factory.connect(hypTokenAddress, this.provider); const [erc20TokenMetadata, collateralDomainId] = await Promise.all([ this.fetchERC20Metadata(hypTokenAddress), hypERC4626.collateralDomain(), ]); const collateralChainName = this.multiProvider.getChainName(collateralDomainId); return { ...erc20TokenMetadata, type: TokenType.syntheticRebase, collateralChainName, }; } async deriveEverclearBaseBridgeConfig(everclearTokenbridgeInstance) { const [everclearBridgeAddress, domains] = await Promise.all([ everclearTokenbridgeInstance.everclearAdapter(), everclearTokenbridgeInstance.domains(), ]); const outputAssets = await promiseObjAll(objMap(arrayToObject(domains.map(String)), async (domainId, _) => everclearTokenbridgeInstance.outputAssets(domainId))); // Remove unset domains from the output const filteredOutputAssets = objFilter(outputAssets, (_domainId, assetAddress) => !isZeroish(assetAddress)); const feeParamsByDomain = await promiseObjAll(objMap(arrayToObject(domains.map(String)), async (domainId, _) => { const [fee, deadline, signature] = await everclearTokenbridgeInstance.feeParams(domainId); return { deadline: deadline.toNumber(), fee: fee.toNumber(), signature, }; })); // Remove unset fee params from the output const filteredFeeParamsByDomain = objFilter(feeParamsByDomain, (_domainId, feeConfig) => { // if all the fields have their default value then the fee config for the // current domain is unset return !(feeConfig.deadline === 0 && feeConfig.fee === 0 && feeConfig.signature === '0x'); }); return { everclearBridgeAddress, outputAssets: filteredOutputAssets, everclearFeeParams: filteredFeeParamsByDomain, }; } async deriveEverclearEthTokenBridgeConfig(hypTokenAddress) { const everclearTokenbridgeInstance = EverclearTokenBridge__factory.connect(hypTokenAddress, this.provider); const wethAddress = await everclearTokenbridgeInstance.wrappedToken(); const { everclearBridgeAddress, everclearFeeParams, outputAssets } = await this.deriveEverclearBaseBridgeConfig(everclearTokenbridgeInstance); return { type: TokenType.ethEverclear, wethAddress, everclearBridgeAddress, everclearFeeParams, outputAssets, }; } async deriveEverclearCollateralTokenBridgeConfig(hypTokenAddress) { const everclearTokenbridgeInstance = EverclearTokenBridge__factory.connect(hypTokenAddress, this.provider); const collateralTokenAddress = await everclearTokenbridgeInstance.wrappedToken(); const [erc20TokenMetadata, { everclearBridgeAddress, everclearFeeParams, outputAssets },] = await Promise.all([ this.fetchERC20Metadata(collateralTokenAddress), this.deriveEverclearBaseBridgeConfig(everclearTokenbridgeInstance), ]); return { type: TokenType.collateralEverclear, ...erc20TokenMetadata, token: collateralTokenAddress, everclearBridgeAddress, everclearFeeParams, outputAssets, }; } async fetchERC20Metadata(tokenAddress) { const erc20 = HypERC20__factory.connect(tokenAddress, this.provider); const [name, symbol, decimals] = await Promise.all([ erc20.name(), erc20.symbol(), erc20.decimals(), ]); return { name, symbol, decimals, isNft: false }; } async fetchPackageVersion(address) { const contractWithVersion = PackageVersioned__factory.connect(address, this.provider); try { return await contractWithVersion.PACKAGE_VERSION(); } catch (err) { if (err.cause?.code && err.cause?.code === 'CALL_EXCEPTION') { // PACKAGE_VERSION was introduced in @hyperlane-xyz/core@5.4.0 // See https://github.com/hyperlane-xyz/hyperlane-monorepo/releases/tag/%40hyperlane-xyz%2Fcore%405.4.0 // The real version of a contract without this function is below 5.4.0 return '5.3.9'; } else { this.logger.error(`Error when fetching package version ${err}`); return '0.0.0'; } } } async fetchProxyAdminConfig(tokenAddress) { const proxyAdminAddress = await proxyAdmin(this.provider, tokenAddress); assert(!isZeroishAddress(proxyAdminAddress), `ProxyAdmin config for warp token at address "${tokenAddress}" can't be derived because it is not a proxy.`); const proxyAdminInstance = ProxyAdmin__factory.connect(proxyAdminAddress, this.provider); return { address: proxyAdminAddress, owner: await proxyAdminInstance.owner(), }; } async fetchDestinationGas(warpRouteAddress) { const warpRoute = TokenRouter__factory.connect(warpRouteAddress, this.provider); /** * @remark * Router.domains() is used to enumerate the destination gas because GasRouter.destinationGas is not EnumerableMapExtended type * This means that if a domain is removed, then we cannot read the destinationGas for it. This may impact updates. */ const domains = await warpRoute.domains(); return Object.fromEntries(await Promise.all(domains.map(async (domain) => { return [domain, (await warpRoute.destinationGas(domain)).toString()]; }))); } } //# sourceMappingURL=EvmERC20WarpRouteReader.js.map