This doc walks you through the key functionality of the IBC Mail application. The basic functionality provided by this contract is the ability to send a message to another account on the same or a different chain. We’ll follow the message from creation to dispatch and delivery.
Sending a message is done by executing the mail client.
#![allow(unused)]
fn main () {
#[cosmwasm_schema::cw_serde]
#[derive(cw_orch::ExecuteFns)]
#[cw_orch(impl_into(ExecuteMsg))]
pub enum ClientExecuteMsg {
ReceiveMessage(IbcMailMessage),
SendMessage {
message: Message,
route: Option <Route>,
},
}
}
#![allow(unused)]
fn main () {
pub fn execute_handler (
deps: DepsMut,
env: Env,
info: MessageInfo,
app: App,
msg: ClientExecuteMsg,
) -> ClientResult {
match msg {
ClientExecuteMsg::SendMessage { message, route } => {
send_msg(deps, env, info, message, route, app)
}
ClientExecuteMsg::ReceiveMessage(message) => receive_msg(deps, info, message, app),
}
}
}
We then construct a message and send it to the server.
#![allow(unused)]
fn main () {
fn send_msg (
deps: DepsMut,
env: Env,
_info: MessageInfo,
msg: Message,
route: Option <Route>,
app: ClientApp,
) -> ClientResult {
let to_hash = format! ("{:?}{:?}{:?}" , env.block.time, msg.subject, msg.recipient);
let hash = <sha2::Sha256 as sha2::Digest>::digest(to_hash);
let base_64_hash = BASE64_STANDARD.encode(hash);
let to_send = IbcMailMessage {
id: base_64_hash,
sender: Sender::account(
app.account_id(deps.as_ref()).unwrap(),
Some (TruncatedChainId::new(&env)),
),
message: Message {
recipient: msg.recipient,
subject: msg.subject,
body: msg.body,
},
timestamp: env.block.time,
version: app.version().to_string(),
};
SENT.save(deps.storage, to_send.id.clone(), &to_send)?;
let server: MailServer<_> = app.mail_server(deps.as_ref());
let route_msg: CosmosMsg = server.process_msg(to_send, route)?;
Ok (app.response("send" ).add_message(route_msg))
}
}
Server receives the message and routes it.
#![allow(unused)]
fn main () {
pub fn execute_handler (
deps: DepsMut,
env: Env,
info: MessageInfo,
app: Adapter,
msg: ServerExecuteMsg,
) -> ServerResult {
match msg {
ServerExecuteMsg::ProcessMessage { msg, route } => {
process_message(deps, env, info, msg, route, app)
}
}
}
}
If the recipient is local the server sends the message to the mail client on the recipient Account.
#![allow(unused)]
fn main () {
let recipient_acc: AccountBase = app.account_registry(deps)?.account_base(&account_id)?;
app.target_account = Some (recipient_acc);
let mail_client: MailClient<_> = app.mail_client(deps);
let msg: CosmosMsg = mail_client.receive_msg(msg, header)?;
}
If the recipient is a remote account the server routes the message to a server on other chain based on the configured message route.
#![allow(unused)]
fn main () {
let ibc_client_msg = ibc_client::ExecuteMsg::ModuleIbcAction {
host_chain: dest_chain.clone(),
target_module: current_module_info,
msg: to_json_binary(&ServerIbcMessage::RouteMessage { msg, header })?,
callback: None ,
};
let ibc_client_addr: Addr = app
.module_registry(deps.as_ref())?
.query_module(ModuleInfo::from_id_latest(IBC_CLIENT)?)?
.reference
.unwrap_native()?;
let msg: CosmosMsg = wasm_execute(ibc_client_addr, &ibc_client_msg, vec! [])?.into();
}
If the message is routed to a remote server it will be propagated to the remote server through the ibc-client.
The message will then be executed by the ibc-host on the remote chain. The IBC host will call the module IBC endpoint on the remote server.
#![allow(unused)]
fn main () {
pub fn module_ibc_handler (
deps: DepsMut,
_env: Env,
mut app: ServerAdapter,
module_info: ModuleIbcInfo,
msg: Binary,
) -> ServerResult {
if module_info.module.id().ne(IBCMAIL_SERVER_ID) {
return Err (ServerError::UnauthorizedIbcModule(module_info.clone()));
};
let server_msg: ServerIbcMessage = from_json(msg)?;
match server_msg {
ServerIbcMessage::RouteMessage { msg, mut header } => {
header.current_hop += 1 ;
let msg = route_msg(deps, msg, header, &mut app)?;
Ok (app.response("module_ibc" ).add_message(msg))
}
_ => Err (ServerError::UnauthorizedIbcMessage {}),
}
}
}
Here the message is either dispatched further over IBC or it is locally executed on a mail client.
#![allow(unused)]
fn main () {
fn receive_msg (deps: DepsMut, info: MessageInfo, msg: IbcMailMessage, app: App) -> ClientResult {
let sender_module = app
.module_registry(deps.as_ref())?
.module_info(info.sender)
.map_err(|_| ClientError::NotMailServer {})?;
ensure_eq!(
sender_module.info.id(),
IBCMAIL_SERVER_ID,
ClientError::NotMailServer {}
);
ensure_correct_recipient(deps.as_ref(), &msg.message.recipient, &app)?;
RECEIVED.save(deps.storage, msg.id.clone(), &msg)?;
Ok (app
.response("received" )
.add_attribute("message_id" , &msg.id))
}
}