Working Example Contract: For a complete, working implementation, see the neutron_interchain_txs example contract in the neutron-sdk repository. Many developers find it easier to learn from working code.
The Interchain Transactions module is designed exclusively for CosmWasm smart contracts. Individual users cannot directly register interchain accounts or submit transactions through CLI commands. All interactions must be programmed into smart contracts.
Registering an Interchain Account
Using CosmWasm
In your CosmWasm contract, implement the registration as follows:Copy
Ask AI
// Define the message
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
RegisterAccount {
connection_id: String,
interchain_account_id: String,
},
// Other messages...
}
// Implementation in the contract execute function
pub fn execute(
deps: DepsMut,
env: Env,
info: MessageInfo,
msg: ExecuteMsg,
) -> Result<Response, ContractError> {
match msg {
ExecuteMsg::RegisterAccount {
connection_id,
interchain_account_id,
} => register_account(deps, env, info, connection_id, interchain_account_id),
// Other message handlers...
}
}
fn register_account(
deps: DepsMut,
env: Env,
info: MessageInfo,
connection_id: String,
interchain_account_id: String,
) -> Result<Response, ContractError> {
// Create the register message
let register_msg = MsgRegisterInterchainAccount {
from_address: env.contract.address.to_string(),
connection_id,
interchain_account_id,
register_fee: vec![Coin {
denom: "untrn".to_string(),
amount: Uint128::from(1000u128),
}],
ordering: 1, // 1 corresponds to ORDERED in protobuf enum
};
// Alternatively, you can use helper functions from the neutron-sdk
// See: https://github.com/neutron-org/neutron-sdk/blob/main/packages/neutron-sdk/src/interchain_txs/helpers.rs
// For SDK usage examples, refer to /developers/sdk/
// Create CosmosMsg
let cosmos_msg = register_msg.into_cosmos_msg()?;
// Return response with the message
Ok(Response::new()
.add_message(cosmos_msg)
.add_attribute("action", "register_account")
.add_attribute("connection_id", connection_id)
.add_attribute("interchain_account_id", interchain_account_id))
}
Handling the Registration Callback
Implement the Sudo handler to process callbacks from the registration:Copy
Ask AI
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum SudoMsg {
OpenAck {
port_id: String,
channel_id: String,
counterparty_channel_id: String,
counterparty_version: String,
},
// Other sudo message variants...
}
pub fn sudo(deps: DepsMut, env: Env, msg: SudoMsg) -> Result<Response, ContractError> {
match msg {
SudoMsg::OpenAck {
port_id,
channel_id,
counterparty_channel_id,
counterparty_version,
} => handle_open_ack(
deps,
env,
port_id,
channel_id,
counterparty_channel_id,
counterparty_version,
),
// Other sudo message handlers...
}
}
fn handle_open_ack(
deps: DepsMut,
_env: Env,
port_id: String,
channel_id: String,
counterparty_channel_id: String,
_counterparty_version: String,
) -> Result<Response, ContractError> {
// Extract account ID from port_id
let account_id = extract_account_id(&port_id)?;
// Store the channel information
let channel_info = ChannelInfo {
port_id: port_id.clone(),
channel_id: channel_id.clone(),
counterparty_channel_id,
};
CHANNELS.save(deps.storage, account_id, &channel_info)?;
Ok(Response::new()
.add_attribute("action", "handle_open_ack")
.add_attribute("port_id", port_id)
.add_attribute("channel_id", channel_id))
}
// Helper function to extract account ID from port ID
fn extract_account_id(port_id: &str) -> Result<String, ContractError> {
// Port ID format: icacontroller-{contract_address}.{interchain_account_id}
let parts: Vec<&str> = port_id.split('.').collect();
if parts.len() != 2 {
return Err(ContractError::InvalidPortId {});
}
Ok(parts[1].to_string())
}
Querying an Interchain Account Address
From Smart Contracts
Query the interchain account address from within your contract:Copy
Ask AI
// Query to get the interchain account address
pub fn query_interchain_account(
deps: Deps,
owner_address: String,
interchain_account_id: String,
connection_id: String,
) -> Result<QueryInterchainAccountAddressResponse, ContractError> {
let query = QueryInterchainAccountAddressRequest {
owner_address,
interchain_account_id,
connection_id,
};
let res: QueryInterchainAccountAddressResponse =
deps.querier.query(&QueryRequest::Custom(
InterchainTxsQuery::InterchainAccountAddress(query)
))?;
Ok(res)
}
Executing Remote Transactions
Using CosmWasm
Implement the transaction submission in your contract:Copy
Ask AI
// Define the message
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum ExecuteMsg {
// Other messages...
SubmitTx {
interchain_account_id: String,
connection_id: String,
msgs: Vec<Any>,
memo: Option<String>,
timeout: u64,
},
}
// Implementation in the contract execute function
fn submit_tx(
deps: DepsMut,
env: Env,
info: MessageInfo,
interchain_account_id: String,
connection_id: String,
msgs: Vec<Any>,
memo: Option<String>,
timeout: u64,
) -> Result<Response, ContractError> {
// Check if the channel exists for this account
let _channel_info = CHANNELS.load(deps.storage, interchain_account_id.clone())?;
// Create the fee for the IBC packet
let fee = Fee {
recv_fee: vec![Coin {
denom: "untrn".to_string(),
amount: Uint128::from(2000u128),
}],
ack_fee: vec![Coin {
denom: "untrn".to_string(),
amount: Uint128::from(1000u128),
}],
timeout_fee: vec![Coin {
denom: "untrn".to_string(),
amount: Uint128::from(1000u128),
}],
};
// Create the submit transaction message
let submit_msg = MsgSubmitTx {
from_address: env.contract.address.to_string(),
interchain_account_id,
connection_id,
msgs,
memo: memo.unwrap_or_default(),
timeout,
fee,
};
// Alternatively, you can use the submit_tx helper from neutron-sdk
// See: https://github.com/neutron-org/neutron-sdk/blob/main/packages/neutron-sdk/src/interchain_txs/helpers.rs#L62
// Create CosmosMsg
let cosmos_msg = submit_msg.into_cosmos_msg()?;
// Return response with the message
Ok(Response::new()
.add_message(cosmos_msg)
.add_attribute("action", "submit_tx"))
}
Handling Transaction Callbacks
Implement the Sudo handler for transaction acknowledgements:Copy
Ask AI
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum SudoMsg {
// Other sudo message variants...
Response {
request_id: String,
data: Binary,
},
Error {
request_id: String,
error: String,
},
Timeout {
request_id: String,
},
}
pub fn sudo(deps: DepsMut, env: Env, msg: SudoMsg) -> Result<Response, ContractError> {
match msg {
// Other sudo message handlers...
SudoMsg::Response { request_id, data } => {
handle_response(deps, env, request_id, data)
},
SudoMsg::Error { request_id, error } => {
handle_error(deps, env, request_id, error)
},
SudoMsg::Timeout { request_id } => {
handle_timeout(deps, env, request_id)
},
}
}
fn handle_response(
deps: DepsMut,
_env: Env,
request_id: String,
data: Binary,
) -> Result<Response, ContractError> {
// Process successful transaction response
// The data field contains the serialized acknowledgement data from the remote chain
// Extract the transaction hash or other relevant information
let response_data: AcknowledgementData = from_binary(&data)?;
// Update contract state based on the successful transaction
// ...
Ok(Response::new()
.add_attribute("action", "handle_response")
.add_attribute("request_id", request_id))
}
fn handle_error(
deps: DepsMut,
_env: Env,
request_id: String,
error: String,
) -> Result<Response, ContractError> {
// Process transaction error
// Log the error and potentially retry or take corrective action
Ok(Response::new()
.add_attribute("action", "handle_error")
.add_attribute("request_id", request_id)
.add_attribute("error", error))
}
fn handle_timeout(
deps: DepsMut,
_env: Env,
request_id: String,
) -> Result<Response, ContractError> {
// Process transaction timeout
// Typically, you would retry the transaction or mark it as failed
Ok(Response::new()
.add_attribute("action", "handle_timeout")
.add_attribute("request_id", request_id))
}
Creating Cosmos Messages for Remote Chains
Here are examples of creating different Cosmos SDK messages for execution on remote chains:Bank Send Message
Copy
Ask AI
use cosmwasm_std::StdError;
use cosmos_sdk_proto::traits::Message;
use neutron_std::types::cosmos::base::v1beta1::Coin;
use neutron_std::types::cosmos::bank::v1beta1::MsgSend;
use neutron_std::shim::Any;
use neutron_sdk::NeutronError;
fn create_bank_send_msg(
from_address: String,
to_address: String,
amount: Vec<Coin>,
) -> Result<Any, NeutronError> {
let bank_send_msg = MsgSend {
from_address,
to_address,
amount,
};
// Encode using protobuf
let mut buf = Vec::with_capacity(bank_send_msg.encoded_len());
if let Err(e) = bank_send_msg.encode(&mut buf) {
return Err(NeutronError::Std(StdError::generic_err(format!(
"Encode error: {}",
e
))));
}
// Convert to Any
let any_msg = Any {
type_url: "/cosmos.bank.v1beta1.MsgSend".to_string(),
value: buf,
};
Ok(any_msg)
}
Staking Delegate Message
Copy
Ask AI
use cosmwasm_std::StdError;
use cosmos_sdk_proto::traits::Message;
use neutron_std::types::cosmos::base::v1beta1::Coin;
use neutron_std::types::cosmos::staking::v1beta1::MsgDelegate;
use neutron_std::shim::Any;
use neutron_sdk::NeutronError;
fn create_staking_delegate_msg(
delegator_address: String,
validator_address: String,
amount: Coin,
) -> Result<Any, NeutronError> {
let delegate_msg = MsgDelegate {
delegator_address,
validator_address,
amount: Some(amount),
};
// Encode using protobuf
let mut buf = Vec::with_capacity(delegate_msg.encoded_len());
if let Err(e) = delegate_msg.encode(&mut buf) {
return Err(NeutronError::Std(StdError::generic_err(format!(
"Encode error: {}",
e
))));
}
// Convert to Any
let any_msg = Any {
type_url: "/cosmos.staking.v1beta1.MsgDelegate".to_string(),
value: buf,
};
Ok(any_msg)
}
Governance Vote Message
Copy
Ask AI
use cosmwasm_std::StdError;
use cosmos_sdk_proto::traits::Message;
use neutron_std::types::cosmos::gov::v1beta1::{MsgVote, VoteOption};
use neutron_std::shim::Any;
use neutron_sdk::NeutronError;
fn create_governance_vote_msg(
proposal_id: u64,
voter: String,
option: VoteOption,
) -> Result<Any, NeutronError> {
let vote_msg = MsgVote {
proposal_id,
voter,
option: option as i32,
};
// Encode using protobuf
let mut buf = Vec::with_capacity(vote_msg.encoded_len());
if let Err(e) = vote_msg.encode(&mut buf) {
return Err(NeutronError::Std(StdError::generic_err(format!(
"Encode error: {}",
e
))));
}
// Convert to Any
let any_msg = Any {
type_url: "/cosmos.gov.v1beta1.MsgVote".to_string(),
value: buf,
};
Ok(any_msg)
}
Debugging Failed Remote Transactions
When a transaction fails on the remote chain, you can use the following command to get detailed error information:Copy
Ask AI
<remote-chain-binary> q interchain-accounts host packet-events <channel-id> <seq-id>
Copy
Ask AI
gaiad q interchain-accounts host packet-events channel-42 15