UNPKG

@hyperlane-xyz/sdk

Version:

The official SDK for the Hyperlane Network

856 lines 39.5 kB
import { BigNumber, constants as ethersConstants, } from 'ethers'; import { ERC20__factory, ERC4626__factory, HypERC20Collateral__factory, HypERC20__factory, HypERC4626Collateral__factory, HypERC4626OwnerCollateral__factory, HypERC4626__factory, HypXERC20Lockbox__factory, HypXERC20__factory, IFiatToken__factory, IPostDispatchHook__factory, ITokenBridge__factory, IXERC20VS__factory, IXERC20__factory, MovableCollateralRouter__factory, PredicateRouterWrapper__factory, StaticAggregationHook__factory, TokenRouter__factory, } from '@hyperlane-xyz/core'; import { LazyAsync, ProtocolType, ZERO_ADDRESS_HEX_32, addressToByteHexString, addressToBytes32, assert, bytes32ToAddress, convertToProtocolAddress, eqAddress, isNullish, isZeroishAddress, normalizeAddress, strip0x, } from '@hyperlane-xyz/utils'; import { BaseEvmAdapter } from '../../app/MultiProtocolApp.js'; import { UIN256_MAX_VALUE } from '../../consts/numbers.js'; import { OnchainHookType } from '../../hook/types.js'; import { isMissingSelectorCallException, isValidContractVersion, throwIfNotMissingSelector, } from '../../utils/contract.js'; import { buildBlockTagOverrides } from './utils.js'; // Ensures an address is in EVM hex format (e.g. converts Tron bs58 addresses) const toEvmAddress = (address) => convertToProtocolAddress(address, ProtocolType.Ethereum); // An estimate of the gas amount for a typical EVM token router transferRemote transaction // Computed by estimating on a few different chains, taking the max, and then adding ~50% padding export const EVM_TRANSFER_REMOTE_GAS_ESTIMATE = 450000n; const TOKEN_FEE_CONTRACT_VERSION = '10.0.0'; // Interacts with native currencies export class EvmNativeTokenAdapter extends BaseEvmAdapter { async getBalance(address) { const balance = await this.getProvider().getBalance(toEvmAddress(address)); return BigInt(balance.toString()); } async getMetadata() { const { nativeToken } = this.multiProvider.getChainMetadata(this.chainName); assert(nativeToken, `Native token data is required for ${EvmNativeTokenAdapter.name}`); return { name: nativeToken.name, symbol: nativeToken.symbol, decimals: nativeToken.decimals, }; } async getMinimumTransferAmount(_recipient) { return 0n; } async isApproveRequired(_owner, _spender, _weiAmountOrId) { return false; } async isRevokeApprovalRequired(_owner, _spender) { return false; } async populateApproveTx(_params) { throw new Error('Approve not required for native tokens'); } async populateTransferTx({ weiAmountOrId, recipient, }) { const value = BigNumber.from(weiAmountOrId.toString()); return { value, to: toEvmAddress(recipient) }; } async getTotalSupply() { // Not implemented, native tokens don't have an accessible total supply return undefined; } } // Interacts with ERC20/721 contracts export class EvmTokenAdapter extends EvmNativeTokenAdapter { chainName; multiProvider; addresses; contractFactory; contract; constructor(chainName, multiProvider, addresses, contractFactory = ERC20__factory) { super(chainName, multiProvider, addresses); this.chainName = chainName; this.multiProvider = multiProvider; this.addresses = addresses; this.contractFactory = contractFactory; this.contract = contractFactory.connect(addresses.token, this.getProvider()); } async getBalance(address) { const balance = await this.contract.balanceOf(toEvmAddress(address)); return BigInt(balance.toString()); } async getMetadata(isNft) { const [decimals, symbol, name] = await Promise.all([ isNft ? 0 : this.contract.decimals(), this.contract.symbol(), this.contract.name(), ]); return { decimals, symbol, name }; } async isApproveRequired(owner, spender, weiAmountOrId) { const allowance = await this.contract.allowance(toEvmAddress(owner), toEvmAddress(spender)); return allowance.lt(weiAmountOrId); } async isRevokeApprovalRequired(owner, spender) { const allowance = await this.contract.allowance(toEvmAddress(owner), toEvmAddress(spender)); return !allowance.isZero(); } populateApproveTx({ weiAmountOrId, recipient, }) { return this.contract.populateTransaction.approve(toEvmAddress(recipient), weiAmountOrId.toString()); } populateTransferTx({ weiAmountOrId, recipient, }) { return this.contract.populateTransaction.transfer(toEvmAddress(recipient), weiAmountOrId.toString()); } async getTotalSupply() { const totalSupply = await this.contract.totalSupply(); return totalSupply.toBigInt(); } } // Interacts with Hyp Synthetic token contracts (aka 'HypTokens') export class EvmHypSyntheticAdapter extends EvmTokenAdapter { chainName; multiProvider; addresses; contractFactory; predicateWrapperAddress; clearPredicateCache() { this.predicateWrapperAddress = undefined; } constructor(chainName, multiProvider, addresses, contractFactory = HypERC20__factory) { super(chainName, multiProvider, addresses, contractFactory); this.chainName = chainName; this.multiProvider = multiProvider; this.addresses = addresses; this.contractFactory = contractFactory; } async isApproveRequired(owner, spender, weiAmountOrId) { if (eqAddress(spender, this.addresses.token)) { // Synthetics with PredicateWrapper need approval to the wrapper instead. // Without a wrapper, transferRemote burns directly from msg.sender — no approval needed. const predicateWrapper = await this.getPredicateWrapperAddress(); if (!predicateWrapper) return false; const allowance = await this.contract.allowance(toEvmAddress(owner), predicateWrapper); return allowance.lt(weiAmountOrId); } // External spenders (e.g. QuotedCalls) get the standard ERC20 allowance check. return super.isApproveRequired(owner, spender, weiAmountOrId); } async isRevokeApprovalRequired(owner, spender) { if (eqAddress(spender, this.addresses.token)) { // When a predicate wrapper is present, allowance was granted to the wrapper. // Check whether the wrapper has a non-zero allowance that must be revoked first // (relevant for USDT-like tokens that require zeroing before re-approval). const predicateWrapper = await this.getPredicateWrapperAddress(); if (!predicateWrapper) return false; return super.isRevokeApprovalRequired(owner, predicateWrapper); } return super.isRevokeApprovalRequired(owner, spender); } async populateApproveTx(params) { if (eqAddress(params.recipient, this.addresses.token)) { // Synthetics with PredicateWrapper approve the wrapper, not the route contract const predicateWrapper = await this.getPredicateWrapperAddress(); if (predicateWrapper) { return this.contract.populateTransaction.approve(predicateWrapper, params.weiAmountOrId.toString()); } } // External spender or no predicate wrapper — standard ERC20 approve return super.populateApproveTx(params); } async getPredicateWrapperAddress() { if (this.predicateWrapperAddress !== undefined) { return this.predicateWrapperAddress; } const hookAddress = await this.contract.hook(); if (hookAddress === ethersConstants.AddressZero) { this.predicateWrapperAddress = null; return null; } const provider = this.getProvider(); const warpRouteAddress = this.addresses.token.toLowerCase(); // findPredicateWrapperInHook returns null when the hook structure contains no // matching wrapper (a confirmed structural absence). Any exception here is an // unexpected RPC/network failure — don't cache null so the next call can retry. const foundWrapper = await this.findPredicateWrapperInHook(hookAddress, warpRouteAddress, provider); this.predicateWrapperAddress = foundWrapper; return this.predicateWrapperAddress; } async findPredicateWrapperInHook(hookAddress, warpRouteAddress, provider) { const hook = IPostDispatchHook__factory.connect(hookAddress, provider); let hookType; try { hookType = await hook.hookType(); } catch (error) { // CALL_EXCEPTION means the contract at hookAddress doesn't implement hookType() // (e.g. an old or incompatible hook). Treat as "no wrapper here". // Any other error (network timeout, RPC failure) is unexpected — rethrow. if (error instanceof Error && 'code' in error && error.code === 'CALL_EXCEPTION') return null; throw error; } if (hookType === OnchainHookType.PREDICATE_ROUTER_WRAPPER) { const wrapper = PredicateRouterWrapper__factory.connect(hookAddress, provider); const warpRouteFromWrapper = await wrapper.warpRoute(); const matches = warpRouteFromWrapper.toLowerCase() === warpRouteAddress; if (matches) { return hookAddress; } } if (hookType === OnchainHookType.AGGREGATION) { const aggregationHook = StaticAggregationHook__factory.connect(hookAddress, provider); const subHooks = await aggregationHook.hooks('0x'); for (const subHook of subHooks) { const found = await this.findPredicateWrapperInHook(subHook, warpRouteAddress, provider); if (found) { return found; } } } // Known constraint: recursion only descends into AGGREGATION hooks. // FALLBACK_ROUTING, DOMAIN_ROUTING, and AMOUNT_ROUTING hooks are not traversed. // In practice this is fine because PredicateWrapperDeployer always places the // wrapper inside a StaticAggregationHook. A wrapper nested inside a routing hook // would not be detected and predicate support would silently degrade to disabled. return null; } getDomains() { return this.contract.domains(); } async getRouterAddress(domain) { const routerAddressesAsBytes32 = await this.contract.routers(domain); // Evm addresses will be padded with 12 bytes if (routerAddressesAsBytes32.startsWith('0x000000000000000000000000')) { return Buffer.from(strip0x(bytes32ToAddress(routerAddressesAsBytes32)), 'hex'); // Otherwise leave the address unchanged } else { return Buffer.from(strip0x(routerAddressesAsBytes32), 'hex'); } } async getAllRouters() { const domains = await this.getDomains(); const routers = await Promise.all(domains.map((d) => this.getRouterAddress(d))); return domains.map((d, i) => ({ domain: d, address: routers[i] })); } async getBridgedSupply(options) { const overrides = buildBlockTagOverrides(options?.blockTag); const totalSupply = await this.contract.totalSupply(overrides); return totalSupply.toBigInt(); } async getContractPackageVersion() { try { return await this.contract.PACKAGE_VERSION(); } catch (err) { if (isMissingSelectorCallException(err)) { // PACKAGE_VERSION was introduced in v5.4.0 return '5.3.9'; } this.logger.error(`Error when fetching package version ${err}`); throw err; } } async quoteTransferRemoteGas({ destination, recipient, amount, }) { const contractVersion = await this.getContractPackageVersion(); const hasQuoteTransferRemote = isValidContractVersion(contractVersion, TOKEN_FEE_CONTRACT_VERSION); // Version does not support quoteTransferRemote defaulting to quoteGasPayment if (!hasQuoteTransferRemote) { const gasPayment = await this.contract.quoteGasPayment(destination); return { igpQuote: { amount: BigInt(gasPayment.toString()) } }; } assert(!isNullish(amount), 'Amount must be defined for quoteTransferRemoteGas'); assert(recipient, 'Recipient must be defined for quoteTransferRemoteGas'); const recipBytes32 = addressToBytes32(addressToByteHexString(recipient)); const [igpQuote, ...feeQuotes] = await this.contract['quoteTransferRemote(uint32,bytes32,uint256)'](destination, recipBytes32, amount.toString()); const [, igpAmount] = igpQuote; const tokenFeeQuotes = feeQuotes.map((quote) => ({ addressOrDenom: quote[0], amount: BigInt(quote[1].toString()), })); // Because the amount is added on the fees, we need to subtract it from the actual fees const tokenFeeQuote = tokenFeeQuotes.length > 0 ? { addressOrDenom: tokenFeeQuotes[0].addressOrDenom, // the contract enforces the token address to be the same as the route amount: tokenFeeQuotes.reduce((sum, q) => sum + q.amount, 0n) - amount, } : undefined; return { igpQuote: { amount: BigInt(igpAmount.toString()), }, tokenFeeQuote, }; } /** * Check if this warp route supports Predicate attestations * @returns True if a PredicateRouterWrapper is configured on the hook */ async supportsAttestation() { const wrapperAddress = await this.getPredicateWrapperAddress(); return wrapperAddress !== null; } async populatePredicateTransferRemoteTx(params, nativeValue) { const { weiAmountOrId, destination, recipient, attestation } = params; assert(attestation, 'attestation is required'); const predicateWrapperAddress = await this.getPredicateWrapperAddress(); if (!predicateWrapperAddress) { throw new Error('Attestation provided but no PredicateRouterWrapper detected on warp route hook. ' + 'Attestations can only be used with routes that have a PredicateRouterWrapper configured.'); } let { interchainGas } = params; if (!interchainGas) { interchainGas = await this.quoteTransferRemoteGas({ destination, recipient, amount: BigInt(weiAmountOrId), }); } nativeValue += interchainGas.igpQuote.amount; if (!interchainGas.tokenFeeQuote?.addressOrDenom || isZeroishAddress(interchainGas.tokenFeeQuote?.addressOrDenom)) { nativeValue += interchainGas.tokenFeeQuote?.amount ?? 0n; } const recipBytes32 = addressToBytes32(addressToByteHexString(recipient)); const predicateWrapper = PredicateRouterWrapper__factory.connect(predicateWrapperAddress, this.getProvider()); const contractAttestation = { uuid: attestation.uuid, expiration: attestation.expiration, attester: attestation.attester, signature: attestation.signature, }; return predicateWrapper.populateTransaction.transferRemoteWithAttestation(contractAttestation, destination, recipBytes32, weiAmountOrId, { value: nativeValue.toString() }); } async populateTransferRemoteTx(params, nativeValue = 0n) { const { weiAmountOrId, destination, recipient, attestation } = params; if (attestation) { return this.populatePredicateTransferRemoteTx(params, nativeValue); } let { interchainGas } = params; if (!interchainGas) interchainGas = await this.quoteTransferRemoteGas({ destination, recipient, amount: BigInt(weiAmountOrId), }); // add igp to native value nativeValue += interchainGas.igpQuote.amount; // add token fee to native value if the denom is undefined or zero address (native token) if (!interchainGas.tokenFeeQuote?.addressOrDenom || isZeroishAddress(interchainGas.tokenFeeQuote?.addressOrDenom)) { nativeValue += interchainGas.tokenFeeQuote?.amount ?? 0n; } const recipBytes32 = addressToBytes32(addressToByteHexString(recipient)); return this.contract.populateTransaction['transferRemote(uint32,bytes32,uint256)'](destination, recipBytes32, weiAmountOrId, { value: nativeValue.toString(), }); } } class BaseEvmHypCollateralAdapter extends EvmHypSyntheticAdapter { chainName; multiProvider; addresses; collateralContract; wrappedTokenAddress = new LazyAsync(() => this.loadWrappedTokenAddress()); constructor(chainName, multiProvider, addresses) { super(chainName, multiProvider, addresses); this.chainName = chainName; this.multiProvider = multiProvider; this.addresses = addresses; this.collateralContract = TokenRouter__factory.connect(addresses.token, this.getProvider()); } async getWrappedTokenAddress() { return this.wrappedTokenAddress.get(); } async loadWrappedTokenAddress() { return this.collateralContract.token(); } async getWrappedTokenAdapter() { return new EvmTokenAdapter(this.chainName, this.multiProvider, { token: await this.getWrappedTokenAddress(), }); } async getBalance(address) { const wrappedTokenAdapter = await this.getWrappedTokenAdapter(); return wrappedTokenAdapter.getBalance(address); } async getBridgedSupply(options) { const wrappedTokenAddress = await this.getWrappedTokenAddress(); const wrappedContract = ERC20__factory.connect(wrappedTokenAddress, this.getProvider()); const overrides = buildBlockTagOverrides(options?.blockTag); const balance = await wrappedContract.balanceOf(this.addresses.token, overrides); return BigInt(balance.toString()); } getMetadata(isNft) { return this.getWrappedTokenAdapter().then((t) => t.getMetadata(isNft)); } async isApproveRequired(owner, spender, weiAmountOrId) { const wrappedTokenAdapter = await this.getWrappedTokenAdapter(); let effectiveSpender = spender; if (eqAddress(spender, this.addresses.token)) { const predicateWrapper = await this.getPredicateWrapperAddress(); if (predicateWrapper) effectiveSpender = predicateWrapper; } return wrappedTokenAdapter.isApproveRequired(owner, effectiveSpender, weiAmountOrId); } async isRevokeApprovalRequired(owner, spender) { const collateral = await this.getWrappedTokenAdapter(); let effectiveSpender = spender; if (eqAddress(spender, this.addresses.token)) { const predicateWrapper = await this.getPredicateWrapperAddress(); if (predicateWrapper) effectiveSpender = predicateWrapper; } return collateral.isRevokeApprovalRequired(owner, effectiveSpender); } async populateApproveTx(params) { const wrappedTokenAdapter = await this.getWrappedTokenAdapter(); let effectiveRecipient = params.recipient; if (eqAddress(params.recipient, this.addresses.token)) { const predicateWrapper = await this.getPredicateWrapperAddress(); if (predicateWrapper) effectiveRecipient = predicateWrapper; } return wrappedTokenAdapter.populateApproveTx({ ...params, recipient: effectiveRecipient, }); } /** * Check if this warp route supports Predicate attestations * @returns True if a PredicateRouterWrapper is configured on the hook */ async supportsAttestation() { const wrapperAddress = await this.getPredicateWrapperAddress(); return wrapperAddress !== null; } async populateTransferRemoteTx(params, nativeValue = 0n) { if (params.attestation) { return this.populatePredicateTransferRemoteTx(params, nativeValue); } return super.populateTransferRemoteTx(params, nativeValue); } populateTransferTx(params) { return this.getWrappedTokenAdapter().then((t) => t.populateTransferTx(params)); } } // Interacts with HypCollateral contracts export class EvmHypCollateralAdapter extends BaseEvmHypCollateralAdapter { chainName; multiProvider; addresses; collateralContract; constructor(chainName, multiProvider, addresses) { super(chainName, multiProvider, addresses); this.chainName = chainName; this.multiProvider = multiProvider; this.addresses = addresses; this.collateralContract = HypERC20Collateral__factory.connect(addresses.token, this.getProvider()); } async loadWrappedTokenAddress() { return this.collateralContract.wrappedToken(); } } export class EvmMovableCollateralAdapter extends EvmHypCollateralAdapter { movableCollateral() { return MovableCollateralRouter__factory.connect(this.addresses.token, this.getProvider()); } async isRebalancer(account) { const rebalancers = await this.movableCollateral().allowedRebalancers(); return rebalancers.includes(toEvmAddress(account)); } async getAllowedDestination(domain) { const allowedDestinationBytes32 = await this.movableCollateral().allowedRecipient(domain); // If allowedRecipient is not set (returns bytes32(0)), // fall back to the enrolled remote router for that domain, // matching the contract's fallback logic in MovableCollateralRouter.sol if (allowedDestinationBytes32 === ZERO_ADDRESS_HEX_32) { const routerBytes32 = await this.movableCollateral().routers(domain); return bytes32ToAddress(routerBytes32); } return bytes32ToAddress(allowedDestinationBytes32); } async isBridgeAllowed(domain, bridge) { const allowedBridges = await this.movableCollateral().allowedBridges(domain); return allowedBridges .map((bridgeAddress) => normalizeAddress(bridgeAddress)) .includes(normalizeAddress(toEvmAddress(bridge))); } async getRebalanceQuotes(bridge, domain, recipient, amount) { const bridgeContract = ITokenBridge__factory.connect(toEvmAddress(bridge), this.getProvider()); const quotes = await bridgeContract['quoteTransferRemote(uint32,bytes32,uint256)'](domain, addressToBytes32(recipient), amount); return quotes.map((quote) => ({ igpQuote: { addressOrDenom: quote.token === ethersConstants.AddressZero ? undefined : quote.token, amount: BigInt(quote.amount.toString()), }, })); } /** * @param quotes - The quotes returned by getRebalanceQuotes */ populateRebalanceTx(domain, amount, bridge, quotes) { // Obtains the trx value by adding the amount of all quotes with no addressOrDenom (native tokens) const value = quotes.reduce((value, quote) => !quote.igpQuote.addressOrDenom ? value + quote.igpQuote.amount : value, 0n); return this.movableCollateral().populateTransaction.rebalance(domain, amount, bridge, { value, }); } } export class EvmHypCollateralFiatAdapter extends EvmHypCollateralAdapter { /** * Note this may be inaccurate, as this returns the total supply * of the fiat token, which may be used by other bridges. * However this is the best we can do with a simple view call. */ async getBridgedSupply(options) { const wrappedTokenAddress = await this.getWrappedTokenAddress(); const wrappedContract = ERC20__factory.connect(wrappedTokenAddress, this.getProvider()); const overrides = buildBlockTagOverrides(options?.blockTag); const totalSupply = await wrappedContract.totalSupply(overrides); return BigInt(totalSupply.toString()); } async getMintLimit() { const wrappedToken = await this.getWrappedTokenAddress(); const fiatToken = IFiatToken__factory.connect(wrappedToken, this.getProvider()); const isMinter = await fiatToken.isMinter(this.addresses.token); if (!isMinter) { return 0n; } // if the minterAllowance call fails it probably is because the underlying // mintable contract does not define the method and instead does not restrict // minting for allowed contracts // example: https://etherscan.io/token/0x6468e79A80C0eaB0F9A2B574c8d5bC374Af59414#readContract try { const limit = await fiatToken.minterAllowance(this.addresses.token); return limit.toBigInt(); } catch (error) { throwIfNotMissingSelector(error); return UIN256_MAX_VALUE; } } } export class EvmHypRebaseCollateralAdapter extends BaseEvmHypCollateralAdapter { chainName; multiProvider; addresses; collateralContract; constructor(chainName, multiProvider, addresses) { super(chainName, multiProvider, addresses); this.chainName = chainName; this.multiProvider = multiProvider; this.addresses = addresses; this.collateralContract = HypERC4626Collateral__factory.connect(addresses.token, this.getProvider()); } async loadWrappedTokenAddress() { return this.collateralContract.wrappedToken(); } async getBridgedSupply(options) { const vault = ERC4626__factory.connect(await this.collateralContract.vault(), this.getProvider()); const overrides = buildBlockTagOverrides(options?.blockTag); const assets = await vault.maxWithdraw(this.addresses.token, overrides); return assets.toBigInt(); } } export class EvmHypOwnerCollateralAdapter extends EvmHypRebaseCollateralAdapter { collateralContract; constructor(chainName, multiProvider, addresses) { super(chainName, multiProvider, addresses); this.collateralContract = HypERC4626OwnerCollateral__factory.connect(addresses.token, this.getProvider()); } async getBridgedSupply(options) { const overrides = buildBlockTagOverrides(options?.blockTag); const assetDeposited = await this.collateralContract.assetDeposited(overrides); return assetDeposited.toBigInt(); } } export class EvmHypSyntheticRebaseAdapter extends EvmHypSyntheticAdapter { chainName; multiProvider; addresses; constructor(chainName, multiProvider, addresses) { super(chainName, multiProvider, addresses, HypERC4626__factory); this.chainName = chainName; this.multiProvider = multiProvider; this.addresses = addresses; } async getBridgedSupply(options) { const overrides = buildBlockTagOverrides(options?.blockTag); const totalShares = await this.contract.totalShares(overrides); return totalShares.toBigInt(); } } class BaseEvmHypXERC20Adapter extends EvmHypCollateralAdapter { chainName; multiProvider; addresses; hypXERC20; constructor(chainName, multiProvider, addresses) { super(chainName, multiProvider, addresses); this.chainName = chainName; this.multiProvider = multiProvider; this.addresses = addresses; this.hypXERC20 = HypXERC20__factory.connect(addresses.token, this.getProvider()); } async getXERC20() { const xerc20Addr = await this.hypXERC20.wrappedToken(); return this.connectXERC20(xerc20Addr); } async getBridgedSupply(options) { const xerc20 = await this.getXERC20(); // Both IXERC20 and IXERC20VS have totalSupply, name, etc. if they extend ERC20 const overrides = buildBlockTagOverrides(options?.blockTag); const totalSupply = await xerc20.totalSupply(overrides); return totalSupply.toBigInt(); } async getMintLimit() { const xerc20 = await this.getXERC20(); const limit = await xerc20.mintingCurrentLimitOf(this.contract.address); return limit.toBigInt(); } async getMintMaxLimit() { const xerc20 = await this.getXERC20(); const limit = await xerc20.mintingMaxLimitOf(this.contract.address); return limit.toBigInt(); } async getBurnLimit() { const xerc20 = await this.getXERC20(); const limit = await xerc20.burningCurrentLimitOf(this.contract.address); return limit.toBigInt(); } async getBurnMaxLimit() { const xerc20 = await this.getXERC20(); const limit = await xerc20.burningMaxLimitOf(this.contract.address); return limit.toBigInt(); } } class BaseEvmHypXERC20LockboxAdapter extends EvmHypCollateralAdapter { chainName; multiProvider; addresses; hypXERC20Lockbox; constructor(chainName, multiProvider, addresses) { super(chainName, multiProvider, addresses); this.chainName = chainName; this.multiProvider = multiProvider; this.addresses = addresses; this.hypXERC20Lockbox = HypXERC20Lockbox__factory.connect(addresses.token, this.getProvider()); } async loadWrappedTokenAddress() { return this.hypXERC20Lockbox.wrappedToken(); } /** * Note this may be inaccurate, as this returns the balance * of the lockbox contract, which may be used by other bridges. * However this is the best we can do with a simple view call. */ async getBridgedSupply(options) { const lockboxAddress = await this.hypXERC20Lockbox.lockbox(); const wrappedTokenAddress = await this.getWrappedTokenAddress(); const wrappedContract = ERC20__factory.connect(wrappedTokenAddress, this.getProvider()); const overrides = buildBlockTagOverrides(options?.blockTag); const balance = await wrappedContract.balanceOf(lockboxAddress, overrides); return BigInt(balance.toString()); } async getXERC20() { const xERC20Addr = await this.hypXERC20Lockbox.xERC20(); return this.connectXERC20(xERC20Addr); } async getMintLimit() { const xERC20 = await this.getXERC20(); const limit = await xERC20.mintingCurrentLimitOf(this.contract.address); return limit.toBigInt(); } async getMintMaxLimit() { const xERC20 = await this.getXERC20(); const limit = await xERC20.mintingMaxLimitOf(this.contract.address); return limit.toBigInt(); } async getBurnLimit() { const xERC20 = await this.getXERC20(); const limit = await xERC20.burningCurrentLimitOf(this.contract.address); return limit.toBigInt(); } async getBurnMaxLimit() { const xERC20 = await this.getXERC20(); const limit = await xERC20.burningMaxLimitOf(this.contract.address); return limit.toBigInt(); } } // Interacts with HypXERC20Lockbox contracts export class EvmHypXERC20LockboxAdapter extends BaseEvmHypXERC20LockboxAdapter { connectXERC20(xERC20Addr) { return IXERC20__factory.connect(xERC20Addr, this.getProvider()); } } export class EvmHypVSXERC20LockboxAdapter extends BaseEvmHypXERC20LockboxAdapter { connectXERC20(xERC20Addr) { return IXERC20VS__factory.connect(xERC20Addr, this.getProvider()); } // If you need to expose rate-limiting calls or other VS-specific logic: async getRateLimits() { const xERC20 = await this.getXERC20(); const rateLimits = await xERC20.rateLimits(this.contract.address); return { rateLimitPerSecond: BigInt(rateLimits.rateLimitPerSecond.toString()), bufferCap: BigInt(rateLimits.bufferCap.toString()), lastBufferUsedTime: Number(rateLimits.lastBufferUsedTime), bufferStored: BigInt(rateLimits.bufferStored.toString()), midPoint: BigInt(rateLimits.midPoint.toString()), }; } async populateSetBufferCapTx(newBufferCap) { const xERC20 = await this.getXERC20(); return xERC20.populateTransaction.setBufferCap(this.addresses.token, newBufferCap); } async populateSetRateLimitPerSecondTx(newRateLimitPerSecond) { const xERC20 = await this.getXERC20(); return xERC20.populateTransaction.setRateLimitPerSecond(this.addresses.token, newRateLimitPerSecond); } async populateAddBridgeTx(bufferCap, rateLimitPerSecond) { const xERC20 = await this.getXERC20(); return xERC20.populateTransaction.addBridge({ bufferCap, rateLimitPerSecond, bridge: this.addresses.token, }); } } // Interacts with HypXERC20 contracts export class EvmHypXERC20Adapter extends BaseEvmHypXERC20Adapter { connectXERC20(xerc20Addr) { return IXERC20__factory.connect(xerc20Addr, this.getProvider()); } } export class EvmHypVSXERC20Adapter extends BaseEvmHypXERC20Adapter { connectXERC20(xerc20Addr) { return IXERC20VS__factory.connect(xerc20Addr, this.getProvider()); } async getRateLimits() { const xERC20 = await this.getXERC20(); const rateLimits = await xERC20.rateLimits(this.contract.address); return { rateLimitPerSecond: BigInt(rateLimits.rateLimitPerSecond.toString()), bufferCap: BigInt(rateLimits.bufferCap.toString()), lastBufferUsedTime: Number(rateLimits.lastBufferUsedTime), bufferStored: BigInt(rateLimits.bufferStored.toString()), midPoint: BigInt(rateLimits.midPoint.toString()), }; } async populateSetBufferCapTx(newBufferCap) { const xERC20 = await this.getXERC20(); return xERC20.populateTransaction.setBufferCap(this.addresses.token, newBufferCap); } async populateSetRateLimitPerSecondTx(newRateLimitPerSecond) { const xERC20 = await this.getXERC20(); return xERC20.populateTransaction.setRateLimitPerSecond(this.addresses.token, newRateLimitPerSecond); } async populateAddBridgeTx(bufferCap, rateLimitPerSecond) { const xERC20 = await this.getXERC20(); return xERC20.populateTransaction.addBridge({ bufferCap, rateLimitPerSecond, bridge: this.addresses.token, }); } } // Interacts HypNative contracts export class EvmHypNativeAdapter extends EvmMovableCollateralAdapter { async getBalance(address) { const provider = this.getProvider(); const balance = await provider.getBalance(toEvmAddress(address)); return BigInt(balance.toString()); } async isApproveRequired() { return false; } async isRevokeApprovalRequired(_owner, _spender) { return false; } /** * @param quotes - The quotes returned by getRebalanceQuotes */ populateRebalanceTx(domain, amount, bridge, quotes) { // Obtains the trx value by adding the amount of all quotes with no addressOrDenom (native tokens) const value = quotes.reduce((value, quote) => !quote.igpQuote.addressOrDenom ? value + quote.igpQuote.amount : value, // Uses the amount to transfer as base value given that the amount is defined in native tokens for this adapter BigInt(amount)); return this.movableCollateral().populateTransaction.rebalance(domain, amount, bridge, { value, }); } async populateTransferRemoteTx({ weiAmountOrId, destination, recipient, interchainGas, attestation, }) { // For native tokens the token amount is itself msg.value, so pass it as the // initial nativeValue. populatePredicateTransferRemoteTx / super both add // IGP fees on top of this base value. const nativeValue = BigInt(weiAmountOrId); if (attestation) { return this.populatePredicateTransferRemoteTx({ weiAmountOrId, destination, recipient, interchainGas, attestation }, nativeValue); } return super.populateTransferRemoteTx({ weiAmountOrId, destination, recipient, interchainGas }, nativeValue); } async getBridgedSupply(options) { const balance = await this.getProvider().getBalance(this.addresses.token, options?.blockTag); return BigInt(balance.toString()); } } export class EvmXERC20Adapter extends EvmTokenAdapter { chainName; multiProvider; addresses; xERC20; constructor(chainName, multiProvider, addresses) { super(chainName, multiProvider, addresses); this.chainName = chainName; this.multiProvider = multiProvider; this.addresses = addresses; this.xERC20 = IXERC20__factory.connect(addresses.token, this.getProvider()); } async getLimits(bridge) { const mint = await this.xERC20.mintingMaxLimitOf(toEvmAddress(bridge)); const burn = await this.xERC20.burningMaxLimitOf(toEvmAddress(bridge)); return { mint: BigInt(mint.toString()), burn: BigInt(burn.toString()), }; } async populateSetLimitsTx(bridge, mint, burn) { return this.xERC20.populateTransaction.setLimits(toEvmAddress(bridge), mint.toString(), burn.toString()); } } export class EvmXERC20VSAdapter extends EvmTokenAdapter { chainName; multiProvider; addresses; xERC20VS; constructor(chainName, multiProvider, addresses) { super(chainName, multiProvider, addresses); this.chainName = chainName; this.multiProvider = multiProvider; this.addresses = addresses; this.xERC20VS = IXERC20VS__factory.connect(addresses.token, this.getProvider()); } async getRateLimits(bridge) { const result = await this.xERC20VS.rateLimits(toEvmAddress(bridge)); const rateLimits = { rateLimitPerSecond: BigInt(result.rateLimitPerSecond.toString()), bufferCap: BigInt(result.bufferCap.toString()), lastBufferUsedTime: Number(result.lastBufferUsedTime), bufferStored: BigInt(result.bufferStored.toString()), midPoint: BigInt(result.midPoint.toString()), }; return rateLimits; } // remove bridge async populateRemoveBridgeTx(bridge) { return this.xERC20VS.populateTransaction.removeBridge(toEvmAddress(bridge)); } async populateSetBufferCapTx(bridge, newBufferCap) { return this.xERC20VS.populateTransaction.setBufferCap(toEvmAddress(bridge), newBufferCap.toString()); } async populateSetRateLimitPerSecondTx(bridge, newRateLimitPerSecond) { return this.xERC20VS.populateTransaction.setRateLimitPerSecond(toEvmAddress(bridge), newRateLimitPerSecond.toString()); } async populateAddBridgeTx(bufferCap, rateLimitPerSecond, bridge) { return this.xERC20VS.populateTransaction.addBridge({ bufferCap: bufferCap.toString(), rateLimitPerSecond: rateLimitPerSecond.toString(), bridge: toEvmAddress(bridge), }); } } //# sourceMappingURL=EvmTokenAdapter.js.map