Custom Module Endpoints
Default modules have a fixed set of endpoints that are defined by the base message type shown below.
#![allow(unused)] fn main() { /// Wrapper around all possible messages that can be sent to the module. #[cosmwasm_schema::cw_serde] pub enum ExecuteMsg<BaseMsg, CustomExecMsg> { /// A configuration message, defined by the base. Base(BaseMsg), /// An app request defined by a base consumer. Module(CustomExecMsg), /// IbcReceive to process IBC callbacks /// In order to trust this, the apps and adapters verify this comes from the ibc-client contract. IbcCallback(IbcResponseMsg), /// ModuleIbc endpoint to receive messages from modules on other chains /// In order to trust this, the apps and adapters verify this comes from the ibc-host contract. /// They should also trust the sending chain ModuleIbc(ModuleIbcMsg), } }
However, you might need to specify an endpoint on the top-level of your module, requiring changes to the base message type. As we’ll discuss, this can be done by defining a custom module endpoint.
Defining a Custom Module Endpoint
To define a custom module endpoint, you need to create a new message type that extends the base message type. This new message type should contain the messages supported by the base massages. In other words, it should be a superset of the base message type.
For example, if you require your contract to accept a Receive(Cw20ReceiveMsg)
endpoint to handle Cw20
deposits, then you can define a new message type as shown below.
#![allow(unused)] fn main() { pub enum CustomExecuteMsg { // Base message for your module type Base(abstract_app::std::app::BaseExecuteMsg), // Execute message for your module Module(AppExecuteMsg), /// Custom msg type Receive(cw20::Cw20ReceiveMsg), } }
Now the App
object still expects the regular ExecuteMsg
type, so you need to implement the CustomExecuteHandler
trait for your custom message type to attempt to parse it into the base type.
When this try_into_base
function returns an error, the custom_execute
function will be called, allowing you to handle the custom message type.
#![allow(unused)] fn main() { impl CustomExecuteHandler<MyApp> for CustomExecuteMsg { type ExecuteMsg = crate::msg::ExecuteMsg; fn try_into_base(self) -> Result<Self::ExecuteMsg, Self> { match self { CustomExecuteMsg::Base(msg) => Ok(crate::msg::ExecuteMsg::from(msg)), CustomExecuteMsg::Module(msg) => Ok(crate::msg::ExecuteMsg::from(msg)), _ => Err(self), } } fn custom_execute( self, deps: DepsMut, env: Env, info: MessageInfo, module: MyApp, ) -> Result<Response, AppError> { match self { CustomExecuteMsg::Receive(cw20_msg) => { // Function that handles the custom message super::receive_handler(deps, env, info, module, cw20_msg) } _ => unreachable!(), } } } }
We realize this introduces a lot of boilerplate code. Alternatively the
serde(untagged)
attribute could have been used but this increases wasm size and hurts performance significantly.
As a final change we need to update the entrypoint
macro to include the custom message type.
#![allow(unused)] fn main() { #[cfg(feature = "export")] abstract_app::export_endpoints!(APP, MyApp, CustomExecuteMsg); abstract_app::cw_orch_interface!( APP, MyApp, MyAppInterface, CustomExecuteMsg ); }
You can find a full example of a custom module endpoint in the payment module codebase.
Cw-orch Function Support
To enable your endpoints to be called on the contract’s interface you need to implement the From
trait for the CustomExecuteMsg
type. Where AppExecuteMsg
is the module’s inner execute message as shown above.
#![allow(unused)] fn main() { // Enable cw_orch api impl From<AppExecuteMsg> for CustomExecuteMsg { fn from(value: AppExecuteMsg) -> Self { Self::Module(value) } } }