- Docs
- Developers
- CW-ICA module
Introduction
The CW ICA (Interchain Account) module enables smart contracts on Archway to register ICA accounts and submit transactions to be executed on counterparty chains that are connected to Archway via IBC and are ICA-enabled. By enabling smart contracts to manage accounts and execute transactions across multiple chains, Archway opens up new possibilities for decentralized applications.
Key Features
- Creation of Interchain Accounts: Smart contracts can now create and manage accounts on other blockchains.
- Execution of Transactions: Allows smart contracts to execute transactions on behalf of the interchain accounts they manage.
Registering an interchain account
To register an interchain account, the contract sends a MsgRegisterInterchainAccount message to create an account on a counterparty chain.
Let's say we want to create an ICA on Osmosis. We will need to provide the contract address and also the IBC connection ID to the Osmosis chain and create a Stargate proto message with the correct type and encode the contract address and IBC connection ID and then submit the message and this will then get committed into Archway and then submitted via IBC to the respective chain. Once the ICA is successfully created a success message will be returned but then a callback will also be executed that you will need to handle.
Lets’s say we have the following State variable to hold all the information required:
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, JsonSchema)]pub struct State { pub owner: Addr, pub connection_id: String, pub ica_address: String, pub voted: bool, pub errors: String, pub timeout: bool,}
The following could then be used to register an account on a counterparty chain.
use crate::msg::{SudoMsg};use crate::state::{MsgRegisterInterchainAccount};pub fn register(deps: Deps, env: Env) -> Result<Response, ContractError> { let from_address = env.contract.address.to_string(); let state = STATE.load(deps.storage)?; let connection_id = state.connection_id; let regsiter_msg = MsgRegisterInterchainAccount { contract_address: from_address.clone(), connection_id: connection_id.clone(), }; let register_stargate_msg = CosmosMsg::Stargate { type_url: "/archway.cwica.v1.MsgRegisterInterchainAccount".to_string(), value: Binary::from(prost::Message::encode_to_vec(®siter_msg)), }; Ok(Response::new() .add_attribute("action", "register") .add_attribute("account_owner", from_address) .add_attribute("connection_id", connection_id) .add_message(register_stargate_msg))}
ICA callback
The callback is executed by the Archway protocol and therefore resides in a SudoMsg. There are two callback messages that can be received, either an ICA message or an Error message. Within the ICA message you will receive the account information via account_registered as shown in the code below. You can then store that ICA address for future use.
The Error message would mean that something went wrong and based on the error your can do any necessary clean up or just share the message with your user for example.
#[cfg_attr(not(feature = "library"), entry_point)]pub fn sudo(deps: DepsMut, env: Env, msg: SudoMsg) -> StdResult<Response> { match msg { SudoMsg::Ica { account_registered, tx_executed } => sudo::ica(deps, env, account_registered, tx_executed), SudoMsg::Error { module_name, error_code, contract_address: _, input_payload, error_message } => sudo::error(deps, env, module_name, error_code, input_payload, error_message), }}pub mod sudo { use crate::msg::{ICAResponse, AccountRegistered}; use super::*; pub fn ica( deps: DepsMut, _env: Env, account_registered_option: Option<AccountRegistered>, response_option: Option<ICAResponse>, ) -> StdResult<Response> { let _ = STATE.update(deps.storage, |mut state| -> Result<_, ContractError> { if account_registered_option.is_some() { let account_registered = account_registered_option.unwrap(); state.ica_address = account_registered.counterparty_address.clone(); } if response_option.is_some() { state.voted = true; } Ok(state) }); Ok(Response::new()) } pub fn error(deps: DepsMut, _env: Env, module_name: String, error_code: u32, _payload: String, error_message: String) -> StdResult<Response> { let _ = STATE.update(deps.storage, |mut state| -> Result<_, ContractError> { if module_name == "cwica" { if error_code == 1 { // packet timeout error state.timeout = true; } if error_code == 2 { // submittx execution error state.errors = error_message; } else { // unknown error } } Ok(state) }); Ok(Response::new()) }}
Execute a message on counterparty chain
So after you’ve created an ICA account on another chain you are able to execute transactions on that counterparty chain using the ICA account. For this example let’s execute a Vote transaction on a proposal.
Similar to the create ICA transaction, this transaction will be picked up by the IBC relayers and executed on the counterparty chain and a response will be returned. However, a callback will also be executed once the transaction completes execution. This callback will also come under SudoMsg::Ica but the tx_executed value will be populated this time around.
pub fn vote( deps: Deps, env: Env, proposal_id: u64, option: i32, tiny_timeout: bool, ) -> Result<Response, ContractError> { let state = STATE.load(deps.storage)?; let connection_id = state.connection_id; let from_address = env.contract.address.to_string(); let ica_address = state.ica_address; let vote_msg = MsgVote { proposal_id: proposal_id, voter: ica_address.clone(), option: option, }; let vote_msg_stargate_msg = prost_types::Any { type_url: "/cosmos.gov.v1.MsgVote".to_string(), value: vote_msg.encode_to_vec(), }; let timeout = if tiny_timeout { 1 } else { DEFAULT_TIMEOUT_SECONDS }; let sendtx_msg = MsgSendTx { contract_address: from_address.clone(), connection_id: connection_id.clone(), msgs: vec![vote_msg_stargate_msg], memo: "sent from contract".to_string(), timeout: timeout, }; let sendtx_stargate_msg = CosmosMsg::Stargate { type_url: "/archway.cwica.v1.MsgSendTx".to_string(), value: Binary::from(prost::Message::encode_to_vec(&sendtx_msg)), }; Ok(Response::new() .add_attribute("action", "send") .add_message(sendtx_stargate_msg))}
So we would create a MsgVote message and add that value to the correct type URL and then combine a MsgSendTx message in a CosmosMsg::Stargate transaction. We can see how this new ICA module abstracts away the complexities of the ICA and IBC featureset making it a lot easier for developers to build robust decentralized applications.
Info
It's important to note that if a packet timeout occurs, the ICA channel will be closed and ICA features will be unavailable. To reopen the channel, you can recreate the ICA account via the MsgRegisterAccount message with the same details as previously used.