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