Interchain Abstract Accounts
Introduction
Abstract Accounts have the capability to control Accounts on any chain supporting Abstract over the Inter-Blockchain Communication (IBC) protocol through Interchain Abstract Accounts (ICAAs). ICAAs allow for any Abstract Account to control Accounts, contracts, and liquidity on any chain, offering a chain-abstracted experience users and developers in the Interchain ecosystem.
Designed with versatility in mind, ICAAs are able to execute messages locally and remotely, supporting both single- and multi-hop executions.
flowchart subgraph Account IBC direction LR subgraph Chain B A4[Account] end subgraph Chain A direction TB User2[fa:fa-user User] -- owns --> A3["Account"] A4[Account] end A3 -.IBC.-> A4 end
If you’d rather watch a video, an overview of Interchain Abstract Accounts can be found here.
Integrating with Interchain Abstract Accounts
Enabling IBC on the Account
Users must install the ibc-client
contract on their account to enable IBC. To do so they can call the ExecuteMsg::InstallModules
endpoint with the abstract:ibc-client
module ID.
#![allow(unused)] fn main() { pub enum ExecuteMsg{ InstallModules { // Module information (module_id and version) modules: Vec<ModuleInstallConfig>, }, //... } }
(Optional) Create an account on the remote chain
After the initialization step, the account is ready to send messages across IBC. However, if you wish, you can customize the remote account metadata before sending any messages. The following message is executed on the account
contract:
#![allow(unused)] fn main() { pub enum AccountExecuteMsg { ExecuteOnModule { module_id: "abstract:ibc-client", exec_msg: IbcClientExecuteMsg { Register { host_chain: "destination-chain", // Customizable parameters base_asset: None, namespace: None, install_modules: vec![], }, ..., } } ..., } }
Remember that this step is optional as accounts are created automatically when sending the first message across IBC.
Account ID structure
The remote Interchain Abstract Account will have the same account sequence but will have a different trace. Let’s take an example. A account on Neutron
with account sequence 42
wants to create accounts on Osmosis
and Stargaze
.
- Their account ID on
Neutron
islocal-42
. - Their account ID on
Osmosis
isneutron-42
. - Their account ID on
Stargaze
isneutron-42
as well!
Remote accounts can create other remote accounts, and their traces will be chained. For instance, the neutron-42
account on Osmosis
can create an account on Stargaze
which will have the ID osmosis>neutron-42
.
This gives the ability to trace ICAAs back to their origin chain.
Note that in code, inside the AccountTrace
struct, the trace is defined backwards, For account osmosis>neutron-42
, the trace will be:
AccountTrace::Remote(vec!["neutron", "osmosis"])
Sending messages on remote accounts
With or without a pre-existing remote Account, Abstract Accounts are able to send messages on remote Accounts. The account_msgs
will be executed in order on the remote account.
#![allow(unused)] fn main() { pub enum AccountExecuteMsg { ExecuteOnModule { module_id: "abstract:ibc-client", exec_msg: IbcClientExecuteMsg { RemoteAction{ host_chain: "destination-chain", action: HostAction{ Dispatch{ account_msgs: Vec<AccountExecuteMsg { ... }> }, ..., } }, ..., } } ..., } }
Note that the two instances of the AccountExecuteMsg
enum are the exact same type. This allows you to send multi-hop IBC messages. However, multi-hop transactions (of these kind) are not really something you would use often, unless you’re using another chain as a routing chain.
Specification of Interchain Abstract Accounts
The following specification specifies packet data structure, state machine handling logic, and encoding details for the transfer of messages and creation of Abstract accounts over an IBC channel between a client and a host on separate chains. The state machine logic presented allows for safe multi-chain account creation and execution.
Motivation
Users of a set of chains connected over the IBC protocol might wish to interact with smart-contracts and dapps present on another chain than their origin, while not having to onboard the remote chain, create a new wallet or transfer the necessary funds to this other chain. This application-layer standard describes a protocol for interacting with a remote chain and creating Abstract Account on chains connected with IBC which preserves asset ownership, limits the impact of Byzantine faults, and requires no additional permissioning.
Definitions
The Abstract IBC Account interface is described in the following guide and the specifications are roughly presented here
Desired Properties
- Preservation of account and funds ownership
- All interactions that can be done by a local account should be possible for a remote account as well.
Technical Specification
General mechanism
Abstract IBC capabilities are allowed by the ibc-client
<->ibc-host
pair. The ibc-client
is responsible for authenticating the sender and sending packets across IBC to the ibc-host
. The ibc-host
is responsible for receiving packets and routing the packet to the corresponding contract on the remote chain. Under the hood,the client
-host
connection is handled by a Polytone channel. This allows Abstract to be interoperable with other protocols, more resilient to IBC constraints. However, Abstract is not bound to Polytone and any other IBC relaying protocol could be used for relaying packets. Here is a simple schematic that explains the different components of the Abstract IBC infrastructure.
flowchart LR subgraph Osmosis[Osmosis Chain] Osmosis-Abstract-Account -.-> Osmosis-Client Osmosis-Client --> Osmosis-Note subgraph Osmosis-Polytone[Polytone] Osmosis-Note end end subgraph Neutron[Neutron Chain] subgraph Neutron-Polytone[Polytone] Neutron-Voice -.-> Neutron-Proxy end Neutron-Proxy --> Neutron-Host Neutron-Host -.-> Neutron-Abstract-Account end Osmosis-Note ==IBC==> Neutron-Voice
You see that an Abstract Interchain connection is uni-directional. You need 2 connections to be able to interact bi-directionnally with Abstract. Up until today however, only a local account can act on a distant account and not the other way around. Here is an examples using AccountId
between neutron
and osmosis
:
local-42
onneutron
CAN controlneutron-42
onosmosis
via IBCneutron-42
onosmosis
CAN’T controllocal-42
onneutron
Account creation
Interchain Abstract Accounts are traditional Abstract Accounts controlled by the ibc-host. The ibc-host is the admin of the account and routes any packet sent by a remote account on the corresponding local account. When creating an abstract account, it is simply registered by the ibc-host
using the account-factory
just like any other account.
When an action is triggered by a remote account, the ibc-host
does the following verifications:
- If an local account already exists on-chain for the remote account, it just dispatches the message to the account.
- If no account exists, it creates one with default metadata and THEN dispatches the messages to this new account.
The Account creation process is therefore not mandatory when interacting with Interchain Abstract Accounts. This is why when you create an Abstract Account, you automatically have an account on every connected chains!
Data Structures
Interchain Abstract Account communication is done via a single message structure:
#![allow(unused)] fn main() { pub enum IbcHostExecuteMsg{ /// Allows for remote execution from the Polytone implementation #[cw_orch(fn_name("ibc_execute"))] Execute { account_id: AccountId, /// The address of the calling account id. This is used purely for the send-all-back method. /// We include it in all messages none-the-less to simplify the users life account_address: String, action: HostAction, }, ..., } }
account-id
is theid
of the local account calling the action.account_address
is the address of the local account calling the action.action
is the action that should be executed by theibc-host
on the remote account:
#![allow(unused)] fn main() { #[cosmwasm_schema::cw_serde] #[non_exhaustive] pub enum InternalAction { /// Registers a new account from a remote chain Register { name: Option<String>, description: Option<String>, link: Option<String>, namespace: Option<String>, install_modules: Vec<ModuleInstallConfig>, }, } #[cosmwasm_schema::cw_serde] #[non_exhaustive] pub enum HelperAction { SendAllBack, } /// Callable actions on a remote host #[cosmwasm_schema::cw_serde] pub enum HostAction { /// Dispatch messages to a remote Account. /// Will create a new Account if required. Dispatch { account_msgs: Vec<account::ExecuteMsg>, }, /// Can't be called by an account directly. These are permissioned messages that only the IBC Client is allowed to call by itself. Internal(InternalAction), /// Some helpers that allow calling dispatch messages faster (for actions that are called regularly) Helpers(HelperAction), } }
Acknowledgement and Callback
IBC works with 4 steps:
- Sending a packet (Source chain)
- Receiving a packet (Destination chain)
- Sending an acknowledgement (Destination chain)
- Receiving an acknowledgement (Source chain)
We have already covered the 2 first steps with the sections above. We cover the 2 lasts steps in this section.
Step 3 (sending an ack), is handled by Polytone. They catch any error that could happen during contract execution and send back an acknowledgement reflecting the state of the contract execution on the remote chain. This is handled through the Callback struct.
For Step 4, Polytone allows for sending a message to the initial sender of the IBC interaction after the packet was successfully received in the remote chain. Abstract DOESN’T use this feature for user actions, so callbacks are not possible when using Interchain Abstract Accounts. If you are a module
developer, check out the Module Ibc section that allows for callbacks.
Abstract only uses Polytone Callbacks when:
- Registering a new Abstract IBC counterpart, to store the remote Polytone
proxy
caller. - Creating remote accounts to be able to store the remote Abstract
Account
address locally.
Cross chain trace
Because accounts created across chains using the IAA protocol are controlled by an account located on a remote chain, the account that is calling the action needs to be related to the account on the remote chain.
This is done through the AccountId struct. The IBC-host module leverages the AccountId::trace
field of this struct. An account is wether AccountTrace::Local
or AccountTrace::Remote
. When a PacketMsg is sent across an IBC channel, the account id is transformed on the receiving chain in the following manner:
- If it was
AccountTrace::Local
before transfer, it turns into anAccountTrace::Remote
account with one chain in the associated vector being the chain calling thePacketMsg
(PacketMsg::client_chain
) - If it was
AccountTrace::Remote
before transfer, it stays remote and theclient_chain
field is pushed to the associated vector.
This allows full traceability of the account creations and calls.