Walkthrough
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
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 { /// Receive a message from the server ReceiveMessage(IbcMailMessage), /// Send a message 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 { // validate basic fields of message, construct message to send to server 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) } } } }
Recipient is Local Account
If the recipient is local the server sends the message to the mail client on the recipient Account.
#![allow(unused)] fn main() { // Set target account for actions, is used by APIs to retrieve mail client address. 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)?; }
Recipient is Remote Account
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() { // Call IBC client 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(); }
Remote Server
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 { // Assert IBC sender was the server 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.
Client Receives Message
#![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)) } }