UNPKG

@hyperlane-xyz/sdk

Version:

The official SDK for the Hyperlane Network

114 lines 6.33 kB
import { BigNumber } from 'ethers'; import { addBufferToGasLimit, addressToBytes32, bytes32ToAddress, isZeroishAddress, } from '@hyperlane-xyz/utils'; import { appFromAddressesMapHelper } from '../../contracts/contracts.js'; import { RouterApp } from '../../router/RouterApps.js'; import { interchainAccountFactories, } from './contracts.js'; export class InterchainAccount extends RouterApp { constructor(contractsMap, multiProvider) { super(contractsMap, multiProvider); } async remoteChains(chainName) { return Object.keys(this.contractsMap).filter((chain) => chain !== chainName); } router(contracts) { return contracts.interchainAccountRouter; } static fromAddressesMap(addressesMap, multiProvider) { const helper = appFromAddressesMapHelper(addressesMap, interchainAccountFactories, multiProvider); return new InterchainAccount(helper.contractsMap, helper.multiProvider); } async getAccount(destinationChain, config, routerOverride, ismOverride) { return this.getOrDeployAccount(false, destinationChain, config, routerOverride, ismOverride); } async deployAccount(destinationChain, config, routerOverride, ismOverride) { return this.getOrDeployAccount(true, destinationChain, config, routerOverride, ismOverride); } async getOrDeployAccount(deployIfNotExists, destinationChain, config, routerOverride, ismOverride) { const originDomain = this.multiProvider.tryGetDomainId(config.origin); if (!originDomain) { throw new Error(`Origin chain (${config.origin}) metadata needed for deploying ICAs ...`); } const destinationRouter = this.router(this.contractsMap[destinationChain]); const originRouterAddress = routerOverride ?? bytes32ToAddress(await destinationRouter.routers(originDomain)); if (isZeroishAddress(originRouterAddress)) { throw new Error(`Origin router address is zero for ${config.origin} on ${destinationChain}`); } const destinationIsmAddress = ismOverride ?? bytes32ToAddress(await destinationRouter.isms(originDomain)); const destinationAccount = await destinationRouter['getLocalInterchainAccount(uint32,address,address,address)'](originDomain, config.owner, originRouterAddress, destinationIsmAddress); // If not deploying anything, return the account address. if (!deployIfNotExists) { return destinationAccount; } // If the account does not exist, deploy it. if ((await this.multiProvider .getProvider(destinationChain) .getCode(destinationAccount)) === '0x') { const txOverrides = this.multiProvider.getTransactionOverrides(destinationChain); // Estimate gas for deployment const gasEstimate = await destinationRouter.estimateGas['getDeployedInterchainAccount(uint32,address,address,address)'](originDomain, config.owner, originRouterAddress, destinationIsmAddress); // Add buffer to gas estimate const gasWithBuffer = addBufferToGasLimit(gasEstimate); // Execute deployment with buffered gas estimate await this.multiProvider.handleTx(destinationChain, destinationRouter['getDeployedInterchainAccount(uint32,address,address,address)'](originDomain, config.owner, originRouterAddress, destinationIsmAddress, { ...txOverrides, gasLimit: gasWithBuffer, })); this.logger.debug(`Interchain account deployed at ${destinationAccount}`); } else { this.logger.debug(`Interchain account recovered at ${destinationAccount}`); } return destinationAccount; } // meant for ICA governance to return the populatedTx async getCallRemote({ chain, destination, innerCalls, config, hookMetadata, }) { const localRouter = this.router(this.contractsMap[chain]); const remoteDomain = this.multiProvider.getDomainId(destination); const quote = await localRouter['quoteGasPayment(uint32)'](remoteDomain); const remoteRouter = addressToBytes32(config.routerOverride ?? this.routerAddress(destination)); const remoteIsm = addressToBytes32(config.ismOverride ?? (await this.router(this.contractsMap[destination]).isms(remoteDomain))); const callEncoded = await localRouter.populateTransaction['callRemoteWithOverrides(uint32,bytes32,bytes32,(bytes32,uint256,bytes)[],bytes)'](remoteDomain, remoteRouter, remoteIsm, innerCalls.map((call) => ({ to: addressToBytes32(call.to), value: call.value ?? BigNumber.from('0'), data: call.data, })), hookMetadata ?? '0x', { value: quote }); return callEncoded; } async getAccountConfig(chain, account) { const accountOwner = await this.router(this.contractsMap[chain]).accountOwners(account); const originChain = this.multiProvider.getChainName(accountOwner.origin); return { origin: originChain, owner: accountOwner.owner, localRouter: this.router(this.contractsMap[chain]).address, }; } // general helper for different overloaded callRemote functions // can override the gasLimit by StandardHookMetadata.overrideGasLimit for optional hookMetadata here async callRemote({ chain, destination, innerCalls, config, hookMetadata, }) { await this.multiProvider.sendTransaction(chain, this.getCallRemote({ chain, destination, innerCalls, config, hookMetadata, })); } } export function buildInterchainAccountApp(multiProvider, chain, config) { if (!config.localRouter) { throw new Error('localRouter is required for account deployment'); } const addressesMap = { [chain]: { interchainAccountRouter: config.localRouter }, }; return InterchainAccount.fromAddressesMap(addressesMap, multiProvider); } export async function deployInterchainAccount(multiProvider, chain, config) { const interchainAccountApp = buildInterchainAccountApp(multiProvider, chain, config); return interchainAccountApp.deployAccount(chain, config); } //# sourceMappingURL=InterchainAccount.js.map