跨链账户接口
开发者可以使用 跨链账户 接口从本地链创建和控制远程链上的账户。
与一般的消息传递不同,后者要求接收方实现特定接口,跨链账户(ICA)允许开发者与 任何 远程合约进行交互。
概述
跨链账户允许您通过路由器(InterchainAccountRouter
)从 链 A 进行远程调用到 链 B。其工作原理如下:
-
我们使用 CREATE2 为您计算出确定性的 OwnableMulticall 合约地址,该地址作为您跨链调用的代理。您可以在 这里 探索。
-
您可以编码您的调用,包括目标地址、调用数据和每个调用的
msg.value
,并将其批量放入数组中。 -
您将编码后的调用发送到 链 A 的路由器,该路由器会将其转发到 链 B 的路由器。
-
在解码调用后,链 B 的路由器会检查计算出的地址是否已部署。如果没有,我们将部署 OwnableMulticall 合约。
-
然后,路由器在 ICA 地址上执行多重调用,进而在 链 B 上进行所需的任意调用。
跨链账户接口为每个 (uint32 origin, address owner, address remoteRouter, address remoteISM)
元组分配一个唯一的 ICA 地址。发送者在目标链上拥有该 ICA,并可以通过 InterchainAccountRouter.callRemote()
端点指示它进行任意函数调用。
对于 Hyperlane 支持的核心链,您可以使用路由器合约所有者设置的默认值。请参阅 #overrides 部分,了解如何调用 任何 链。
接口
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity >=0.6.11;
import {CallLib} from "../contracts/libs/Call.sol";
interface IInterchainAccountRouter {
function callRemote(
uint32 _destinationDomain,
CallLib.Call[] calldata calls
) external returns (bytes32);
function getRemoteInterchainAccount(uint32 _destination, address _owner)
external
view
returns (address);
}
- 使用
InterchainAccountRouter
开箱即用 - ICA 路由器已经部署到核心链上。请参考 addresses。尝试使用callRemote
方法通过您钱包的跨链账户进行调用。
示例用法
编码
要使用 callRemote
函数,首先准备一个 Call
结构的数组。Call.data
可以使用 abi.encodeCall
函数轻松编码。
struct Call {
bytes32 to; // 支持非 EVM 目标
uint256 value;
bytes data;
}
interface IUniswapV3Pool {
function swap(
address recipient,
bool zeroForOne,
int256 amountSpecified,
uint160 sqrtPriceLimitX96,
bytes calldata data
) external returns (int256 amount0, int256 amount1);
}
IUniswapV3Pool pool = IUniswapV3Pool(...);
Call swapCall = Call({
to: TypeCasts.addressToBytes32(address(pool)),
data: abi.encodeCall(pool.swap, (...));
value: 0,
});
uint32 ethereumDomain = 1;
IInterchainAccountRouter(0xabc...).callRemote(ethereumDomain, [swapCall]);
TypeScript 用法
我们还提供了 TypeScript 工具,以便轻松部署 ICA 账户并在源链上调用 callRemote
:
const localChain = 'ethereum';
const signer = <YOUR_SIGNER>;
const localRouter: InterchainAccountRouter = InterchainAccountRouter__factory.connect(<ICA_ROUTER_ADDRESS>, signer);
const recipientAddress = <EXAMPLE_ADDRESS>; // 在此处使用您自己的地址
const recipientF = new TestRecipient__factory.connect(recipientAddress, signer); // 在此处使用您自己的合约
const fooMessage = "测试";
const data = recipient.interface.encodeFunctionData("fooBar", [1, fooMessage]);
const call = {
to: recipientAddress,
data,
value: BigNumber.from("0"),
};
const quote = await local["quoteGasPayment(uint32)"](
multiProvider.getDomainId(remoteChain)
);
const config: AccountConfig = {
origin: localChain,
owner: signer.address,
localRouter: localRouter.address,
};
await localRouter.callRemote(localChain, remoteChain, [call], config);
确定地址
在发送消息之前,了解您的 ICA 的远程地址可能会很有用。例如,您可能希望首先为该地址提供资金。可以使用 getRemoteInterchainAccount
函数根据目标链和所有者地址获取 ICA 的地址。
下面是一个合约预计算其自身跨链账户地址的示例。
address myInterchainAccount = IInterchainAccountRouter(...).getRemoteInterchainAccount(
destination,
address(this)
);
如果您使用 #overrides 来指定远程链,请在计算远程 ICA 地址时传递这些覆盖。
address myRemoteIca = IInterchainAccountRouter(...).getRemoteInterchainAccount(
address(this),
remoteRouterOverride,
remoteIsmOverride
);
覆盖
跨链账户允许开发者覆盖在 InterchainAccountRouter
中配置的默认链和安全模型。
这些功能对于:
- 在未在
InterchainAccountRouter
中配置的链上调用 ICA。 - 使用与
InterchainAccountRouter
中配置的默认值不同的 ISM。 - 调整 IGP 付款的 gas 限制或设置其他参数。
接口
callRemoteWithOverrides
函数与 callRemote
函数类似,但需要三个额外的参数。
首先,开发者可以覆盖 _router
,即远程链上 InterchainAccountRouter
的地址。这允许开发者控制未在本地 InterchainAccountRouter
上配置的远程链上的 ICA。
其次,开发者可以覆盖 _ism
,即用于保护其 ICA 的远程跨链安全模块(ISM)的地址。此 ISM 将用于验证在本地和远程 InterchainAccountRouters
之间传递的跨链消息。这允许开发者使用最适合其需求的自定义安全模型。
第三,开发者可以覆盖 _hookMetadata
,即传递给每个 ICA 调用的消息钩子的 StandardHookMetadata 元数据(例如,覆盖 IGP 付款的 gas 限制)。
/**
* @notice 将一系列远程调用调度到所有者的跨链账户在目标域上进行
* @dev 推荐使用 CallLib.build 格式化跨链调用
* @param _destination 要进行调用的链的远程域
* @param _router 远程路由器地址
* @param _ism 远程 ISM 地址
* @param _calls 要进行的调用序列
* @param _hookMetadata 要覆盖的钩子元数据
* @return Hyperlane 消息 ID
*/
function callRemoteWithOverrides(
uint32 _destination,
bytes32 _router,
bytes32 _ism,
CallLib.Call[] calldata _calls,
bytes memory _hookMetadata
) public payable returns (bytes32)
function getRemoteInterchainAccount(
address _owner,
address _router,
address _ism
) public view returns (address)