Skip to content

Receiving cross chain messages

To receive ISMP messages, pallets must implement the IIsmpModule interface, this interface allows the IsmpRouter to dispatch verified cross chain messages to the pallet for execution.

Define a Module Id

Ismp uses a unique module id to identify ismp modules and route messages correctly, in contract environments this module id is the contract address. For a substrate pallet, you will need to define an 8 byte module id that is unique to the pallet

   use pallet_ismp::ModuleId;
   pub const EXAMPLE_MODULE_ID: ModuleId = ModuleId::Pallet(PalletId(*b"EXPL-MOD"));

Building the pallet

In this section we build an example pallet that allows us dispatch and receive messages from hyperbridge.

#[frame_support::pallet]
pub mod pallet {
	use super::*;
	use frame_support::{pallet_prelude::*, PalletId};
    use pallet_ismp::ModuleId;
    use ismp::host::ethereum;
 
	pub const EXAMPLE_MODULE_ID: ModuleId = ModuleId::Pallet(PalletId(*b"EXPL-MOD"));
 
	#[pallet::config]
	pub trait Config: frame_system::Config + pallet_ismp::Config {
		/// Because this pallet emits events, it depends on the runtime's definition of an event.
		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
 
		/// [`IsmpDispatcher`] implementation
		type IsmpDispatcher: IsmpDispatcher<Account = Self::AccountId, Balance = Self::Balance> + Default;
	}
 
	#[pallet::pallet]
	pub struct Pallet<T>(_);
 
	#[pallet::event]
	#[pallet::generate_deposit(pub(super) fn deposit_event)]
	pub enum Event<T: Config> {
        MessageReceived,
        TimeoutProcessed
	}
 
	// Errors encountered
	#[pallet::error]
	pub enum Error<T> {
        MessageDispatchFailed
    }
 
	// Hack for implementing the [`Default`] bound needed for
	// [`IsmpDispatcher`](ismp::dispatcher::IsmpDispatcher) and
	// [`IsmpModule`](ismp::module::IsmpModule)
	impl<T> Default for Pallet<T> {
		fn default() -> Self {
			Self(PhantomData)
		}
	}
 
    /// Extrisnic params for evm dispatch
	#[derive(
		Clone, codec::Encode, codec::Decode, scale_info::TypeInfo, PartialEq, Eq, RuntimeDebug,
	)]
	pub struct Params<Balance> {
		/// Destination contract
		pub module: sp_core::H160,
 
		/// Destination EVM host
		pub destination: u32,
 
		/// Timeout timestamp on destination chain in seconds
		pub timeout: u64
 
        /// A relayer fee for message delivery
        pub fee: Balance
	}
 
    #[pallet::call]
	impl<T: Config> Pallet<T> {
		/// Dispatch request to a connected EVM chain.
		#[pallet::weight(Weight::from_parts(1_000_000, 0))]
		#[pallet::call_index(1)]
		pub fn dispatch_to_evm(origin: OriginFor<T>, params: Params<T::Balance>) -> DispatchResult {
			let origin = ensure_signed(origin)?;
			let post = DispatchPost {
				dest: StateMachine::Evm(params.destination),
				from: EXAMPLE_MODULE_ID.to_bytes(),
				to: params.module.0.to_vec(),
				timeout: params.timeout,
				body: b"Hello from polkadot".to_vec(),
			};
 
			let dispatcher = T::IsmpDispatcher::default();
 
			// dispatch the request
            // This call will attempt to collect the protocol fee and relayer fee from the user's account
			dispatcher
				.dispatch_request(
					DispatchRequest::Post(post.clone()),
					FeeMetadata { payer: origin.clone(), fee: params.fee },
				)
				.map_err(|_| Error::<T>::MessageDispatchFailed)?;
 
			Ok(())
		}
	}
}
 
impl<T> IsmpModule for Pallet<T> {
	fn on_accept(&self, request: Post) -> Result<(), ismp::Error> {
        // Here you would perform validations on the post request data
        // Ensure it can be executed successfully before making any state changes
        // You can also dispatch a post response after execution
        Self::deposit_event(Event::<T>::MessageReceived);
		Ok(())
	}
 
	fn on_response(&self, _response: Response) -> Result<(), ismp::Error> {
        // Here you would perform validations on the post request data
        // Ensure it can be executed successfully before making any state changes
        Self::deposit_event(Event::<T>::MessageReceived);
		Ok(())
	}
 
	fn on_timeout(&self, _request: Timeout) -> Result<(), ismp::Error> {
        // Here you would revert all the state changes that were made when the
        // request was initially dispatched
        Self::deposit_event(Event::<T>::TimeoutProcessed);
		Ok(())
	}
}

Runtime Integration

Now the pallet needs to be added to the runtime and also the IsmpRouter.

impl pallet_hyperbridge::Config for Runtime {
    type RuntimeEvent = RuntimeEvent;
    type IsmpHost = Ismp;
}
 
impl pallet_example::Config for Runtime {
    type RuntimeEvent = RuntimeEvent;
    // you can either use pallet_ismp or 
	// pallet_hyperbridge depending on your
	// fee payment strategy
    type IsmpDispatcher = pallet_hyperbridge::Pallet<Runtime>;
}
 
// ... omitted impl blocks
 
construct_runtime! {
    // ...
    Ismp: pallet_ismp,
    Hyperbridge: pallet_hyperbridge,
    Example: pallet_example
}
 
#[derive(Default)]
struct ModuleRouter;
 
impl IsmpRouter for ModuleRouter {
    fn module_for_id(&self, id: Vec<u8>) -> Result<Box<dyn IsmpModule>, Error> {
        return match id.as_slice() {
            PALLET_HYPERBRIDGE_ID => Ok(Box::new(pallet_hyperbridge::Pallet::<Runtime>::default())),
            id if id == EXAMPLE_MODULE_ID.to_bytes().as_slice() => Ok(Box::new(pallet_ismp::pallet_example::Pallet::<Runtime>::default()))
            // ... other modules
            _   => Err(Error::ModuleNotFound(id)),
        };
    }
}

Security Considerations

Sample Implementation