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))
}
}