// 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))
}
#[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())
}
// 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)
}
// 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"))
}
#[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))
}
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)
}
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)
}
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)
}
<remote-chain-binary> q interchain-accounts host packet-events <channel-id> <seq-id>
gaiad q interchain-accounts host packet-events channel-42 15