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