diff --git a/bridges/bin/millau/runtime/Cargo.toml b/bridges/bin/millau/runtime/Cargo.toml
index 80858b2e31eb5d899f6150e0f0435fcf408e799d..33bdcca35cf761732320259c03a74c4aabdc1d9a 100644
--- a/bridges/bin/millau/runtime/Cargo.toml
+++ b/bridges/bin/millau/runtime/Cargo.toml
@@ -20,6 +20,7 @@ bp-header-chain = { path = "../../../primitives/header-chain", default-features
 bp-messages = { path = "../../../primitives/messages", default-features = false }
 bp-millau = { path = "../../../primitives/chain-millau", default-features = false }
 bp-polkadot-core = { path = "../../../primitives/polkadot-core", default-features = false }
+bp-relayers = { path = "../../../primitives/relayers", default-features = false }
 bp-rialto = { path = "../../../primitives/chain-rialto", default-features = false }
 bp-rialto-parachain = { path = "../../../primitives/chain-rialto-parachain", default-features = false }
 bp-runtime = { path = "../../../primitives/runtime", default-features = false }
@@ -28,6 +29,7 @@ bridge-runtime-common = { path = "../../runtime-common", default-features = fals
 pallet-bridge-grandpa = { path = "../../../modules/grandpa", default-features = false }
 pallet-bridge-messages = { path = "../../../modules/messages", default-features = false }
 pallet-bridge-parachains = { path = "../../../modules/parachains", default-features = false }
+pallet-bridge-relayers = { path = "../../../modules/relayers", default-features = false }
 pallet-shift-session-manager = { path = "../../../modules/shift-session-manager", default-features = false }
 
 # Substrate Dependencies
@@ -89,6 +91,7 @@ std = [
 	"bp-messages/std",
 	"bp-millau/std",
 	"bp-polkadot-core/std",
+	"bp-relayers/std",
 	"bp-rialto/std",
 	"bp-rialto-parachain/std",
 	"bp-runtime/std",
@@ -107,6 +110,7 @@ std = [
 	"pallet-bridge-grandpa/std",
 	"pallet-bridge-messages/std",
 	"pallet-bridge-parachains/std",
+	"pallet-bridge-relayers/std",
 	"pallet-grandpa/std",
 	"pallet-mmr/std",
 	"pallet-randomness-collective-flip/std",
@@ -145,6 +149,7 @@ runtime-benchmarks = [
 	"libsecp256k1",
 	"pallet-bridge-messages/runtime-benchmarks",
 	"pallet-bridge-parachains/runtime-benchmarks",
+	"pallet-bridge-relayers/runtime-benchmarks",
 	"pallet-xcm/runtime-benchmarks",
 	"sp-runtime/runtime-benchmarks",
 	"xcm-builder/runtime-benchmarks",
diff --git a/bridges/bin/millau/runtime/src/lib.rs b/bridges/bin/millau/runtime/src/lib.rs
index 4ee80e9e124d311bb9b598966bcf458d62c3595e..36ce83049d1c5ba52d1cafa791d7ad5736181d49 100644
--- a/bridges/bin/millau/runtime/src/lib.rs
+++ b/bridges/bin/millau/runtime/src/lib.rs
@@ -388,6 +388,13 @@ parameter_types! {
 	pub const MaxRequests: u32 = 50;
 }
 
+impl pallet_bridge_relayers::Config for Runtime {
+	type Event = Event;
+	type Reward = Balance;
+	type PaymentProcedure = bp_relayers::MintReward<pallet_balances::Pallet<Runtime>, AccountId>;
+	type WeightInfo = ();
+}
+
 #[cfg(feature = "runtime-benchmarks")]
 parameter_types! {
 	/// Number of headers to keep in benchmarks.
@@ -463,7 +470,12 @@ impl pallet_bridge_messages::Config<WithRialtoMessagesInstance> for Runtime {
 
 	type TargetHeaderChain = crate::rialto_messages::Rialto;
 	type LaneMessageVerifier = crate::rialto_messages::ToRialtoMessageVerifier;
-	type MessageDeliveryAndDispatchPayment = ();
+	type MessageDeliveryAndDispatchPayment =
+		pallet_bridge_relayers::MessageDeliveryAndDispatchPaymentAdapter<
+			Runtime,
+			WithRialtoMessagesInstance,
+			GetDeliveryConfirmationTransactionFee,
+		>;
 	type OnMessageAccepted = ();
 	type OnDeliveryConfirmed = ();
 
@@ -494,7 +506,12 @@ impl pallet_bridge_messages::Config<WithRialtoParachainMessagesInstance> for Run
 
 	type TargetHeaderChain = crate::rialto_parachain_messages::RialtoParachain;
 	type LaneMessageVerifier = crate::rialto_parachain_messages::ToRialtoParachainMessageVerifier;
-	type MessageDeliveryAndDispatchPayment = ();
+	type MessageDeliveryAndDispatchPayment =
+		pallet_bridge_relayers::MessageDeliveryAndDispatchPaymentAdapter<
+			Runtime,
+			WithRialtoParachainMessagesInstance,
+			GetDeliveryConfirmationTransactionFee,
+		>;
 	type OnMessageAccepted = ();
 	type OnDeliveryConfirmed = ();
 
@@ -558,6 +575,7 @@ construct_runtime!(
 		MmrLeaf: pallet_beefy_mmr::{Pallet, Storage},
 
 		// Rialto bridge modules.
+		BridgeRelayers: pallet_bridge_relayers::{Pallet, Call, Storage, Event<T>},
 		BridgeRialtoGrandpa: pallet_bridge_grandpa::{Pallet, Call, Storage},
 		BridgeRialtoMessages: pallet_bridge_messages::{Pallet, Call, Storage, Event<T>, Config<T>},
 
@@ -937,6 +955,7 @@ impl_runtime_apis! {
 			list_benchmark!(list, extra, pallet_bridge_messages, MessagesBench::<Runtime, WithRialtoMessagesInstance>);
 			list_benchmark!(list, extra, pallet_bridge_grandpa, BridgeRialtoGrandpa);
 			list_benchmark!(list, extra, pallet_bridge_parachains, ParachainsBench::<Runtime, WithRialtoMessagesInstance>);
+			list_benchmark!(list, extra, pallet_bridge_relayers, BridgeRelayers);
 
 			let storage_info = AllPalletsWithSystem::storage_info();
 
@@ -1058,6 +1077,7 @@ impl_runtime_apis! {
 				pallet_bridge_parachains,
 				ParachainsBench::<Runtime, WithRialtoParachainsInstance>
 			);
+			add_benchmark!(params, batches, pallet_bridge_relayers, BridgeRelayers);
 
 			Ok(batches)
 		}
diff --git a/bridges/bin/rialto-parachain/runtime/Cargo.toml b/bridges/bin/rialto-parachain/runtime/Cargo.toml
index ac1c1cf3d568d29af3972f3525fc4779ea601e62..6c3d838196a42cb228e2b954b1703f2f83758175 100644
--- a/bridges/bin/rialto-parachain/runtime/Cargo.toml
+++ b/bridges/bin/rialto-parachain/runtime/Cargo.toml
@@ -19,11 +19,13 @@ serde = { version = '1.0', optional = true, features = ['derive'] }
 
 bp-messages = { path = "../../../primitives/messages", default-features = false }
 bp-millau = { path = "../../../primitives/chain-millau", default-features = false }
+bp-relayers = { path = "../../../primitives/relayers", default-features = false }
 bp-runtime = { path = "../../../primitives/runtime", default-features = false }
 bp-rialto-parachain = { path = "../../../primitives/chain-rialto-parachain", default-features = false }
 bridge-runtime-common = { path = "../../runtime-common", default-features = false }
 pallet-bridge-grandpa = { path = "../../../modules/grandpa", default-features = false }
 pallet-bridge-messages = { path = "../../../modules/messages", default-features = false }
+pallet-bridge-relayers = { path = "../../../modules/relayers", default-features = false }
 
 # Substrate Dependencies
 ## Substrate Primitive Dependencies
@@ -89,6 +91,7 @@ runtime-benchmarks = [
 std = [
 	"bp-messages/std",
 	"bp-millau/std",
+	"bp-relayers/std",
 	"bp-runtime/std",
 	"bp-rialto-parachain/std",
 	"bridge-runtime-common/std",
@@ -113,6 +116,7 @@ std = [
 	"pallet-balances/std",
 	"pallet-bridge-grandpa/std",
 	"pallet-bridge-messages/std",
+	"pallet-bridge-relayers/std",
 	"pallet-randomness-collective-flip/std",
 	"pallet-timestamp/std",
 	"pallet-sudo/std",
diff --git a/bridges/bin/rialto-parachain/runtime/src/lib.rs b/bridges/bin/rialto-parachain/runtime/src/lib.rs
index f2463b5e06f517ccf6e8f5a539c211e9405c82ec..c52504c1950e50e468fce26fa5651636a817639d 100644
--- a/bridges/bin/rialto-parachain/runtime/src/lib.rs
+++ b/bridges/bin/rialto-parachain/runtime/src/lib.rs
@@ -474,6 +474,13 @@ impl pallet_aura::Config for Runtime {
 	type MaxAuthorities = MaxAuthorities;
 }
 
+impl pallet_bridge_relayers::Config for Runtime {
+	type Event = Event;
+	type Reward = Balance;
+	type PaymentProcedure = bp_relayers::MintReward<pallet_balances::Pallet<Runtime>, AccountId>;
+	type WeightInfo = ();
+}
+
 parameter_types! {
 	/// This is a pretty unscientific cap.
 	///
@@ -530,7 +537,12 @@ impl pallet_bridge_messages::Config<WithMillauMessagesInstance> for Runtime {
 
 	type TargetHeaderChain = crate::millau_messages::Millau;
 	type LaneMessageVerifier = crate::millau_messages::ToMillauMessageVerifier;
-	type MessageDeliveryAndDispatchPayment = ();
+	type MessageDeliveryAndDispatchPayment =
+		pallet_bridge_relayers::MessageDeliveryAndDispatchPaymentAdapter<
+			Runtime,
+			WithMillauMessagesInstance,
+			GetDeliveryConfirmationTransactionFee,
+		>;
 	type OnMessageAccepted = ();
 	type OnDeliveryConfirmed = ();
 
@@ -567,6 +579,7 @@ construct_runtime!(
 		DmpQueue: cumulus_pallet_dmp_queue::{Pallet, Call, Storage, Event<T>} = 53,
 
 		// Millau bridge modules.
+		BridgeRelayers: pallet_bridge_relayers::{Pallet, Call, Storage, Event<T>},
 		BridgeMillauGrandpa: pallet_bridge_grandpa::{Pallet, Call, Storage},
 		BridgeMillauMessages: pallet_bridge_messages::{Pallet, Call, Storage, Event<T>, Config<T>},
 	}
diff --git a/bridges/bin/rialto/runtime/Cargo.toml b/bridges/bin/rialto/runtime/Cargo.toml
index ff3eb703ff35876a22992f8b5b962a322860c657..a29560a65d9a0dc8baca434c849a948bb49cf0d2 100644
--- a/bridges/bin/rialto/runtime/Cargo.toml
+++ b/bridges/bin/rialto/runtime/Cargo.toml
@@ -19,11 +19,13 @@ serde = { version = "1.0", optional = true, features = ["derive"] }
 bp-header-chain = { path = "../../../primitives/header-chain", default-features = false }
 bp-messages = { path = "../../../primitives/messages", default-features = false }
 bp-millau = { path = "../../../primitives/chain-millau", default-features = false }
+bp-relayers = { path = "../../../primitives/relayers", default-features = false }
 bp-rialto = { path = "../../../primitives/chain-rialto", default-features = false }
 bp-runtime = { path = "../../../primitives/runtime", default-features = false }
 bridge-runtime-common = { path = "../../runtime-common", default-features = false }
 pallet-bridge-grandpa = { path = "../../../modules/grandpa", default-features = false }
 pallet-bridge-messages = { path = "../../../modules/messages", default-features = false }
+pallet-bridge-relayers = { path = "../../../modules/relayers", default-features = false }
 pallet-shift-session-manager = { path = "../../../modules/shift-session-manager", default-features = false }
 
 # Substrate Dependencies
@@ -89,6 +91,7 @@ std = [
 	"bp-header-chain/std",
 	"bp-messages/std",
 	"bp-millau/std",
+	"bp-relayers/std",
 	"bp-rialto/std",
 	"bp-runtime/std",
 	"bridge-runtime-common/std",
@@ -106,6 +109,7 @@ std = [
 	"pallet-beefy-mmr/std",
 	"pallet-bridge-grandpa/std",
 	"pallet-bridge-messages/std",
+	"pallet-bridge-relayers/std",
 	"pallet-grandpa/std",
 	"pallet-mmr/std",
 	"pallet-xcm/std",
diff --git a/bridges/bin/rialto/runtime/src/lib.rs b/bridges/bin/rialto/runtime/src/lib.rs
index e06fdffe889099fd89a902b76a372f12fda92bf9..f0bc0982e69cb16af2d60de4cfe97b801753e8d3 100644
--- a/bridges/bin/rialto/runtime/src/lib.rs
+++ b/bridges/bin/rialto/runtime/src/lib.rs
@@ -389,6 +389,13 @@ impl pallet_authority_discovery::Config for Runtime {
 	type MaxAuthorities = MaxAuthorities;
 }
 
+impl pallet_bridge_relayers::Config for Runtime {
+	type Event = Event;
+	type Reward = Balance;
+	type PaymentProcedure = bp_relayers::MintReward<pallet_balances::Pallet<Runtime>, AccountId>;
+	type WeightInfo = ();
+}
+
 parameter_types! {
 	/// This is a pretty unscientific cap.
 	///
@@ -447,7 +454,12 @@ impl pallet_bridge_messages::Config<WithMillauMessagesInstance> for Runtime {
 
 	type TargetHeaderChain = crate::millau_messages::Millau;
 	type LaneMessageVerifier = crate::millau_messages::ToMillauMessageVerifier;
-	type MessageDeliveryAndDispatchPayment = ();
+	type MessageDeliveryAndDispatchPayment =
+		pallet_bridge_relayers::MessageDeliveryAndDispatchPaymentAdapter<
+			Runtime,
+			WithMillauMessagesInstance,
+			GetDeliveryConfirmationTransactionFee,
+		>;
 	type OnMessageAccepted = ();
 	type OnDeliveryConfirmed = ();
 
@@ -484,6 +496,7 @@ construct_runtime!(
 		MmrLeaf: pallet_beefy_mmr::{Pallet, Storage},
 
 		// Millau bridge modules.
+		BridgeRelayers: pallet_bridge_relayers::{Pallet, Call, Storage, Event<T>},
 		BridgeMillauGrandpa: pallet_bridge_grandpa::{Pallet, Call, Storage},
 		BridgeMillauMessages: pallet_bridge_messages::{Pallet, Call, Storage, Event<T>, Config<T>},
 
diff --git a/bridges/modules/grandpa/src/weights.rs b/bridges/modules/grandpa/src/weights.rs
index d17fee6ceda86bb21509b543f32571f1380bf4be..f23a2ac190397427ea390113e1f396029b877b71 100644
--- a/bridges/modules/grandpa/src/weights.rs
+++ b/bridges/modules/grandpa/src/weights.rs
@@ -40,6 +40,7 @@
 #![allow(clippy::all)]
 #![allow(unused_parens)]
 #![allow(unused_imports)]
+#![allow(missing_docs)]
 
 use frame_support::{
 	traits::Get,
diff --git a/bridges/modules/messages/src/weights.rs b/bridges/modules/messages/src/weights.rs
index 6db9ad5c44279f8ece61a40b9c9eee801ed61f11..3f9ea8bbb16d53d5af76f6948e019981cc9babbe 100644
--- a/bridges/modules/messages/src/weights.rs
+++ b/bridges/modules/messages/src/weights.rs
@@ -17,7 +17,7 @@
 //! Autogenerated weights for `pallet_bridge_messages`
 //!
 //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
-//! DATE: 2022-07-07, STEPS: 50, REPEAT: 20
+//! DATE: 2022-07-20, STEPS: 50, REPEAT: 20
 //! LOW RANGE: [], HIGH RANGE: []
 //! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled
 //! CHAIN: Some("dev"), DB CACHE: 1024
@@ -40,6 +40,7 @@
 #![allow(clippy::all)]
 #![allow(unused_parens)]
 #![allow(unused_imports)]
+#![allow(missing_docs)]
 
 use frame_support::{
 	traits::Get,
@@ -69,22 +70,22 @@ pub trait WeightInfo {
 pub struct MillauWeight<T>(PhantomData<T>);
 impl<T: frame_system::Config> WeightInfo for MillauWeight<T> {
 	fn send_minimal_message_worst_case() -> Weight {
-		(37_948_000 as Weight)
+		(38_822_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(5 as Weight))
 			.saturating_add(T::DbWeight::get().writes(10 as Weight))
 	}
 	fn send_1_kb_message_worst_case() -> Weight {
-		(39_158_000 as Weight)
+		(39_799_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(5 as Weight))
 			.saturating_add(T::DbWeight::get().writes(10 as Weight))
 	}
 	fn send_16_kb_message_worst_case() -> Weight {
-		(48_698_000 as Weight)
+		(47_772_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(5 as Weight))
 			.saturating_add(T::DbWeight::get().writes(10 as Weight))
 	}
 	fn maximal_increase_message_fee() -> Weight {
-		(2_919_864_000 as Weight)
+		(3_081_804_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(3 as Weight))
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
 	}
@@ -95,71 +96,71 @@ impl<T: frame_system::Config> WeightInfo for MillauWeight<T> {
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
 	}
 	fn receive_single_message_proof() -> Weight {
-		(25_992_000 as Weight)
+		(26_523_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(4 as Weight))
 			.saturating_add(T::DbWeight::get().writes(2 as Weight))
 	}
 	fn receive_two_messages_proof() -> Weight {
-		(37_016_000 as Weight)
+		(39_278_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(4 as Weight))
 			.saturating_add(T::DbWeight::get().writes(2 as Weight))
 	}
 	fn receive_single_message_proof_with_outbound_lane_state() -> Weight {
-		(31_589_000 as Weight)
+		(32_416_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(4 as Weight))
 			.saturating_add(T::DbWeight::get().writes(2 as Weight))
 	}
 	fn receive_single_message_proof_1_kb() -> Weight {
-		(25_962_000 as Weight)
+		(27_078_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(3 as Weight))
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
 	}
 	fn receive_single_message_proof_16_kb() -> Weight {
-		(74_385_000 as Weight)
+		(78_235_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(3 as Weight))
 			.saturating_add(T::DbWeight::get().writes(1 as Weight))
 	}
 	fn receive_single_prepaid_message_proof() -> Weight {
-		(26_159_000 as Weight)
+		(27_635_000 as Weight)
 			.saturating_add(T::DbWeight::get().reads(4 as Weight))
 			.saturating_add(T::DbWeight::get().writes(2 as Weight))
 	}
 	fn receive_delivery_proof_for_single_message() -> Weight {
-		(27_590_000 as Weight)
-			.saturating_add(T::DbWeight::get().reads(3 as Weight))
-			.saturating_add(T::DbWeight::get().writes(1 as Weight))
+		(34_576_000 as Weight)
+			.saturating_add(T::DbWeight::get().reads(5 as Weight))
+			.saturating_add(T::DbWeight::get().writes(2 as Weight))
 	}
 	fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight {
-		(27_354_000 as Weight)
-			.saturating_add(T::DbWeight::get().reads(3 as Weight))
-			.saturating_add(T::DbWeight::get().writes(1 as Weight))
+		(37_318_000 as Weight)
+			.saturating_add(T::DbWeight::get().reads(6 as Weight))
+			.saturating_add(T::DbWeight::get().writes(2 as Weight))
 	}
 	fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight {
-		(27_652_000 as Weight)
-			.saturating_add(T::DbWeight::get().reads(3 as Weight))
-			.saturating_add(T::DbWeight::get().writes(1 as Weight))
+		(41_245_000 as Weight)
+			.saturating_add(T::DbWeight::get().reads(7 as Weight))
+			.saturating_add(T::DbWeight::get().writes(3 as Weight))
 	}
 }
 
 // For backwards compatibility and tests
 impl WeightInfo for () {
 	fn send_minimal_message_worst_case() -> Weight {
-		(37_948_000 as Weight)
+		(38_822_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(5 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(10 as Weight))
 	}
 	fn send_1_kb_message_worst_case() -> Weight {
-		(39_158_000 as Weight)
+		(39_799_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(5 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(10 as Weight))
 	}
 	fn send_16_kb_message_worst_case() -> Weight {
-		(48_698_000 as Weight)
+		(47_772_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(5 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(10 as Weight))
 	}
 	fn maximal_increase_message_fee() -> Weight {
-		(2_919_864_000 as Weight)
+		(3_081_804_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(3 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
 	}
@@ -170,48 +171,48 @@ impl WeightInfo for () {
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
 	}
 	fn receive_single_message_proof() -> Weight {
-		(25_992_000 as Weight)
+		(26_523_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(4 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(2 as Weight))
 	}
 	fn receive_two_messages_proof() -> Weight {
-		(37_016_000 as Weight)
+		(39_278_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(4 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(2 as Weight))
 	}
 	fn receive_single_message_proof_with_outbound_lane_state() -> Weight {
-		(31_589_000 as Weight)
+		(32_416_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(4 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(2 as Weight))
 	}
 	fn receive_single_message_proof_1_kb() -> Weight {
-		(25_962_000 as Weight)
+		(27_078_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(3 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
 	}
 	fn receive_single_message_proof_16_kb() -> Weight {
-		(74_385_000 as Weight)
+		(78_235_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(3 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
 	}
 	fn receive_single_prepaid_message_proof() -> Weight {
-		(26_159_000 as Weight)
+		(27_635_000 as Weight)
 			.saturating_add(RocksDbWeight::get().reads(4 as Weight))
 			.saturating_add(RocksDbWeight::get().writes(2 as Weight))
 	}
 	fn receive_delivery_proof_for_single_message() -> Weight {
-		(27_590_000 as Weight)
-			.saturating_add(RocksDbWeight::get().reads(3 as Weight))
-			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
+		(34_576_000 as Weight)
+			.saturating_add(RocksDbWeight::get().reads(5 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(2 as Weight))
 	}
 	fn receive_delivery_proof_for_two_messages_by_single_relayer() -> Weight {
-		(27_354_000 as Weight)
-			.saturating_add(RocksDbWeight::get().reads(3 as Weight))
-			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
+		(37_318_000 as Weight)
+			.saturating_add(RocksDbWeight::get().reads(6 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(2 as Weight))
 	}
 	fn receive_delivery_proof_for_two_messages_by_two_relayers() -> Weight {
-		(27_652_000 as Weight)
-			.saturating_add(RocksDbWeight::get().reads(3 as Weight))
-			.saturating_add(RocksDbWeight::get().writes(1 as Weight))
+		(41_245_000 as Weight)
+			.saturating_add(RocksDbWeight::get().reads(7 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(3 as Weight))
 	}
 }
diff --git a/bridges/modules/parachains/src/weights.rs b/bridges/modules/parachains/src/weights.rs
index bf7384b814689b308fbdf8eb37071b8653c6e1a7..b96e24752d5128ed49764958e78ac9c71ca94d9f 100644
--- a/bridges/modules/parachains/src/weights.rs
+++ b/bridges/modules/parachains/src/weights.rs
@@ -40,6 +40,7 @@
 #![allow(clippy::all)]
 #![allow(unused_parens)]
 #![allow(unused_imports)]
+#![allow(missing_docs)]
 
 use frame_support::{
 	traits::Get,
diff --git a/bridges/modules/relayers/Cargo.toml b/bridges/modules/relayers/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..ef689f55918b27c571979b044b2e35035dcacf81
--- /dev/null
+++ b/bridges/modules/relayers/Cargo.toml
@@ -0,0 +1,55 @@
+[package]
+name = "pallet-bridge-relayers"
+description = "Module used to store relayer rewards and coordinate relayers set."
+version = "0.1.0"
+authors = ["Parity Technologies <admin@parity.io>"]
+edition = "2021"
+license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
+
+[dependencies]
+codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false }
+log = { version = "0.4.14", default-features = false }
+num-traits = { version = "0.2", default-features = false }
+scale-info = { version = "2.1.1", default-features = false, features = ["derive"] }
+serde = { version = "1.0.101", optional = true, features = ["derive"] }
+
+# Bridge dependencies
+
+bp-messages = { path = "../../primitives/messages", default-features = false }
+bp-relayers = { path = "../../primitives/relayers", default-features = false }
+pallet-bridge-messages = { path = "../messages", default-features = false }
+
+# Substrate Dependencies
+
+frame-benchmarking = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false, optional = true }
+frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
+frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
+sp-arithmetic = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
+sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
+
+[dev-dependencies]
+bp-runtime = { path = "../../primitives/runtime" }
+pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "master" }
+sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
+sp-io = { git = "https://github.com/paritytech/substrate", branch = "master" }
+sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" }
+
+[features]
+default = ["std"]
+std = [
+	"bp-messages/std",
+	"bp-relayers/std",
+	"codec/std",
+	"frame-support/std",
+	"frame-system/std",
+	"log/std",
+	"num-traits/std",
+	"pallet-bridge-messages/std",
+	"scale-info/std",
+	"serde",
+	"sp-arithmetic/std",
+	"sp-std/std",
+]
+runtime-benchmarks = [
+	"frame-benchmarking/runtime-benchmarks",
+]
diff --git a/bridges/modules/relayers/src/benchmarking.rs b/bridges/modules/relayers/src/benchmarking.rs
new file mode 100644
index 0000000000000000000000000000000000000000..706454e0497dd908332c2878c0224fa1ec14b137
--- /dev/null
+++ b/bridges/modules/relayers/src/benchmarking.rs
@@ -0,0 +1,40 @@
+// Copyright 2019-2021 Parity Technologies (UK) Ltd.
+// This file is part of Parity Bridges Common.
+
+// Parity Bridges Common is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity Bridges Common is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
+
+//! Benchmarks for the relayers Pallet.
+
+#![cfg(feature = "runtime-benchmarks")]
+
+use crate::*;
+
+use frame_benchmarking::{benchmarks, whitelisted_caller};
+use frame_system::RawOrigin;
+
+/// Reward amount that is (hopefully) is larger than existential deposit across all chains.
+const REWARD_AMOUNT: u32 = u32::MAX;
+
+benchmarks! {
+	// Benchmark `claim_rewards` call.
+	claim_rewards {
+		let relayer: T::AccountId = whitelisted_caller();
+		RelayerRewards::<T>::insert(&relayer, T::Reward::from(REWARD_AMOUNT));
+	}: _(RawOrigin::Signed(relayer))
+	verify {
+		// we can't check anything here, because `PaymentProcedure` is responsible for
+		// payment logic, so we assume that if call has succeeded, the procedure has
+		// also completed successfully
+	}
+}
diff --git a/bridges/modules/relayers/src/lib.rs b/bridges/modules/relayers/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..596dd89f31339f3596bd0a2c0463b066e4802fec
--- /dev/null
+++ b/bridges/modules/relayers/src/lib.rs
@@ -0,0 +1,170 @@
+// Copyright 2019-2021 Parity Technologies (UK) Ltd.
+// This file is part of Parity Bridges Common.
+
+// Parity Bridges Common is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity Bridges Common is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
+
+//! Runtime module that is used to store relayer rewards and (in the future) to
+//! coordinate relations between relayers.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+#![warn(missing_docs)]
+
+use bp_relayers::PaymentProcedure;
+use sp_arithmetic::traits::AtLeast32BitUnsigned;
+use sp_std::marker::PhantomData;
+use weights::WeightInfo;
+
+pub use pallet::*;
+pub use payment_adapter::MessageDeliveryAndDispatchPaymentAdapter;
+
+mod benchmarking;
+mod mock;
+mod payment_adapter;
+
+pub mod weights;
+
+#[frame_support::pallet]
+pub mod pallet {
+	use super::*;
+	use frame_support::pallet_prelude::*;
+	use frame_system::pallet_prelude::*;
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config {
+		/// The overarching event type.
+		type Event: From<Event<Self>> + IsType<<Self as frame_system::Config>::Event>;
+		/// Type of relayer reward.
+		type Reward: AtLeast32BitUnsigned + Copy + Parameter + MaxEncodedLen;
+		/// Pay rewards adapter.
+		type PaymentProcedure: PaymentProcedure<Self::AccountId, Self::Reward>;
+		/// Pallet call weights.
+		type WeightInfo: WeightInfo;
+	}
+
+	#[pallet::pallet]
+	#[pallet::generate_store(pub(super) trait Store)]
+	pub struct Pallet<T>(PhantomData<T>);
+
+	#[pallet::call]
+	impl<T: Config> Pallet<T> {
+		/// Claim accumulated rewards.
+		#[pallet::weight(T::WeightInfo::claim_rewards())]
+		pub fn claim_rewards(origin: OriginFor<T>) -> DispatchResult {
+			let relayer = ensure_signed(origin)?;
+
+			RelayerRewards::<T>::try_mutate_exists(&relayer, |maybe_reward| -> DispatchResult {
+				let reward = maybe_reward.take().ok_or(Error::<T>::NoRewardForRelayer)?;
+				T::PaymentProcedure::pay_reward(&relayer, reward).map_err(|e| {
+					log::trace!(
+						target: "runtime::bridge-relayers",
+						"Failed to pay rewards to {:?}: {:?}",
+						relayer,
+						e,
+					);
+					Error::<T>::FailedToPayReward
+				})?;
+
+				Self::deposit_event(Event::<T>::RewardPaid { relayer: relayer.clone(), reward });
+				Ok(())
+			})
+		}
+	}
+
+	#[pallet::event]
+	#[pallet::generate_deposit(pub(super) fn deposit_event)]
+	pub enum Event<T: Config> {
+		/// Reward has been paid to the relayer.
+		RewardPaid {
+			/// Relayer account that has been rewarded.
+			relayer: T::AccountId,
+			/// Reward amount.
+			reward: T::Reward,
+		},
+	}
+
+	#[pallet::error]
+	pub enum Error<T> {
+		/// No reward can be claimed by given relayer.
+		NoRewardForRelayer,
+		/// Reward payment procedure has failed.
+		FailedToPayReward,
+	}
+
+	/// Map of the relayer => accumulated reward.
+	#[pallet::storage]
+	pub type RelayerRewards<T: Config> =
+		StorageMap<_, Blake2_128Concat, T::AccountId, T::Reward, OptionQuery>;
+}
+
+#[cfg(test)]
+mod tests {
+	use super::*;
+	use mock::*;
+
+	use frame_support::{assert_noop, assert_ok, traits::fungible::Inspect};
+	use sp_runtime::DispatchError;
+
+	#[test]
+	fn root_cant_claim_anything() {
+		run_test(|| {
+			assert_noop!(
+				Pallet::<TestRuntime>::claim_rewards(Origin::root()),
+				DispatchError::BadOrigin,
+			);
+		});
+	}
+
+	#[test]
+	fn relayer_cant_claim_if_no_reward_exists() {
+		run_test(|| {
+			assert_noop!(
+				Pallet::<TestRuntime>::claim_rewards(Origin::signed(REGULAR_RELAYER)),
+				Error::<TestRuntime>::NoRewardForRelayer,
+			);
+		});
+	}
+
+	#[test]
+	fn relayer_cant_claim_if_payment_procedure_fails() {
+		run_test(|| {
+			RelayerRewards::<TestRuntime>::insert(FAILING_RELAYER, 100);
+			assert_noop!(
+				Pallet::<TestRuntime>::claim_rewards(Origin::signed(FAILING_RELAYER)),
+				Error::<TestRuntime>::FailedToPayReward,
+			);
+		});
+	}
+
+	#[test]
+	fn relayer_can_claim_reward() {
+		run_test(|| {
+			RelayerRewards::<TestRuntime>::insert(REGULAR_RELAYER, 100);
+			assert_ok!(Pallet::<TestRuntime>::claim_rewards(Origin::signed(REGULAR_RELAYER)));
+			assert_eq!(RelayerRewards::<TestRuntime>::get(REGULAR_RELAYER), None);
+		});
+	}
+
+	#[test]
+	fn mint_reward_payment_procedure_actually_mints_tokens() {
+		type Balances = pallet_balances::Pallet<TestRuntime>;
+
+		run_test(|| {
+			assert_eq!(Balances::balance(&1), 0);
+			assert_eq!(Balances::total_issuance(), 0);
+			bp_relayers::MintReward::<Balances, AccountId>::pay_reward(&1, 100).unwrap();
+			assert_eq!(Balances::balance(&1), 100);
+			assert_eq!(Balances::total_issuance(), 100);
+		});
+	}
+}
diff --git a/bridges/modules/relayers/src/mock.rs b/bridges/modules/relayers/src/mock.rs
new file mode 100644
index 0000000000000000000000000000000000000000..2ceabc6621fb3dd8e66a146f8eb5094a61c4bc55
--- /dev/null
+++ b/bridges/modules/relayers/src/mock.rs
@@ -0,0 +1,156 @@
+// Copyright 2019-2021 Parity Technologies (UK) Ltd.
+// This file is part of Parity Bridges Common.
+
+// Parity Bridges Common is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity Bridges Common is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
+
+#![cfg(test)]
+
+use crate as pallet_bridge_relayers;
+
+use bp_messages::{source_chain::ForbidOutboundMessages, target_chain::ForbidInboundMessages};
+use bp_relayers::PaymentProcedure;
+use frame_support::{parameter_types, weights::RuntimeDbWeight};
+use sp_core::H256;
+use sp_runtime::{
+	testing::Header as SubstrateHeader,
+	traits::{BlakeTwo256, IdentityLookup},
+};
+
+pub type AccountId = u64;
+pub type Balance = u64;
+
+type Block = frame_system::mocking::MockBlock<TestRuntime>;
+type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<TestRuntime>;
+
+frame_support::construct_runtime! {
+	pub enum TestRuntime where
+		Block = Block,
+		NodeBlock = Block,
+		UncheckedExtrinsic = UncheckedExtrinsic,
+	{
+		System: frame_system::{Pallet, Call, Config, Storage, Event<T>},
+		Balances: pallet_balances::{Pallet, Event<T>},
+		Messages: pallet_bridge_messages::{Pallet, Event<T>},
+		Relayers: pallet_bridge_relayers::{Pallet, Call, Event<T>},
+	}
+}
+
+parameter_types! {
+	pub const DbWeight: RuntimeDbWeight = RuntimeDbWeight { read: 1, write: 2 };
+}
+
+impl frame_system::Config for TestRuntime {
+	type Origin = Origin;
+	type Index = u64;
+	type Call = Call;
+	type BlockNumber = u64;
+	type Hash = H256;
+	type Hashing = BlakeTwo256;
+	type AccountId = AccountId;
+	type Lookup = IdentityLookup<Self::AccountId>;
+	type Header = SubstrateHeader;
+	type Event = Event;
+	type BlockHashCount = frame_support::traits::ConstU64<250>;
+	type Version = ();
+	type PalletInfo = PalletInfo;
+	type AccountData = pallet_balances::AccountData<Balance>;
+	type OnNewAccount = ();
+	type OnKilledAccount = ();
+	type BaseCallFilter = frame_support::traits::Everything;
+	type SystemWeightInfo = ();
+	type BlockWeights = ();
+	type BlockLength = ();
+	type DbWeight = DbWeight;
+	type SS58Prefix = ();
+	type OnSetCode = ();
+	type MaxConsumers = frame_support::traits::ConstU32<16>;
+}
+
+impl pallet_balances::Config for TestRuntime {
+	type MaxLocks = ();
+	type Balance = Balance;
+	type DustRemoval = ();
+	type Event = Event;
+	type ExistentialDeposit = frame_support::traits::ConstU64<1>;
+	type AccountStore = frame_system::Pallet<TestRuntime>;
+	type WeightInfo = ();
+	type MaxReserves = ();
+	type ReserveIdentifier = ();
+}
+
+parameter_types! {
+	pub const TestBridgedChainId: bp_runtime::ChainId = *b"test";
+}
+
+// we're not testing messages pallet here, so values in this config might be crazy
+impl pallet_bridge_messages::Config for TestRuntime {
+	type Event = Event;
+	type WeightInfo = ();
+	type Parameter = ();
+	type MaxMessagesToPruneAtOnce = frame_support::traits::ConstU64<0>;
+	type MaxUnrewardedRelayerEntriesAtInboundLane = frame_support::traits::ConstU64<8>;
+	type MaxUnconfirmedMessagesAtInboundLane = frame_support::traits::ConstU64<8>;
+
+	type MaximalOutboundPayloadSize = frame_support::traits::ConstU32<1024>;
+	type OutboundPayload = ();
+	type OutboundMessageFee = Balance;
+
+	type InboundPayload = ();
+	type InboundMessageFee = Balance;
+	type InboundRelayer = AccountId;
+
+	type TargetHeaderChain = ForbidOutboundMessages;
+	type LaneMessageVerifier = ForbidOutboundMessages;
+	type MessageDeliveryAndDispatchPayment = ();
+	type OnMessageAccepted = ();
+	type OnDeliveryConfirmed = ();
+
+	type SourceHeaderChain = ForbidInboundMessages;
+	type MessageDispatch = ForbidInboundMessages;
+	type BridgedChainId = TestBridgedChainId;
+}
+
+impl pallet_bridge_relayers::Config for TestRuntime {
+	type Event = Event;
+	type Reward = Balance;
+	type PaymentProcedure = TestPaymentProcedure;
+	type WeightInfo = ();
+}
+
+/// Regular relayer that may receive rewards.
+pub const REGULAR_RELAYER: AccountId = 1;
+
+/// Relayer that can't receive rewards.
+pub const FAILING_RELAYER: AccountId = 2;
+
+/// Payment procedure that rejects payments to the `FAILING_RELAYER`.
+pub struct TestPaymentProcedure;
+
+impl PaymentProcedure<AccountId, Balance> for TestPaymentProcedure {
+	type Error = ();
+
+	fn pay_reward(relayer: &AccountId, _reward: Balance) -> Result<(), Self::Error> {
+		match *relayer {
+			FAILING_RELAYER => Err(()),
+			_ => Ok(()),
+		}
+	}
+}
+
+/// Run pallet test.
+pub fn run_test<T>(test: impl FnOnce() -> T) -> T {
+	let t = frame_system::GenesisConfig::default().build_storage::<TestRuntime>().unwrap();
+	let mut ext = sp_io::TestExternalities::new(t);
+	ext.execute_with(test)
+}
diff --git a/bridges/modules/relayers/src/payment_adapter.rs b/bridges/modules/relayers/src/payment_adapter.rs
new file mode 100644
index 0000000000000000000000000000000000000000..6aa1248959dca5eab76a414c51a39f83dc9f24ae
--- /dev/null
+++ b/bridges/modules/relayers/src/payment_adapter.rs
@@ -0,0 +1,180 @@
+// Copyright 2019-2021 Parity Technologies (UK) Ltd.
+// This file is part of Parity Bridges Common.
+
+// Parity Bridges Common is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity Bridges Common is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
+
+//! Code that allows relayers pallet to be used as a delivery+dispatch payment mechanism
+//! for the messages pallet.
+
+use crate::{Config, RelayerRewards};
+
+use bp_messages::source_chain::{MessageDeliveryAndDispatchPayment, RelayersRewards};
+use frame_support::traits::Get;
+use sp_arithmetic::traits::{Bounded, Saturating, Zero};
+use sp_std::{collections::vec_deque::VecDeque, marker::PhantomData, ops::RangeInclusive};
+
+/// Adapter that allows relayers pallet to be used as a delivery+dispatch payment mechanism
+/// for the messages pallet.
+pub struct MessageDeliveryAndDispatchPaymentAdapter<T, MessagesInstance, GetConfirmationFee>(
+	PhantomData<(T, MessagesInstance, GetConfirmationFee)>,
+);
+
+impl<T, MessagesInstance, GetConfirmationFee>
+	MessageDeliveryAndDispatchPayment<T::Origin, T::AccountId, T::Reward>
+	for MessageDeliveryAndDispatchPaymentAdapter<T, MessagesInstance, GetConfirmationFee>
+where
+	T: Config + pallet_bridge_messages::Config<MessagesInstance, OutboundMessageFee = T::Reward>,
+	MessagesInstance: 'static,
+	GetConfirmationFee: Get<T::Reward>,
+{
+	type Error = &'static str;
+
+	fn pay_delivery_and_dispatch_fee(
+		_submitter: &T::Origin,
+		_fee: &T::Reward,
+	) -> Result<(), Self::Error> {
+		// nothing shall happen here, because XCM deals with fee payment (planned to be burnt?
+		// or transferred to the treasury?)
+		Ok(())
+	}
+
+	fn pay_relayers_rewards(
+		lane_id: bp_messages::LaneId,
+		messages_relayers: VecDeque<bp_messages::UnrewardedRelayer<T::AccountId>>,
+		confirmation_relayer: &T::AccountId,
+		received_range: &RangeInclusive<bp_messages::MessageNonce>,
+	) {
+		let relayers_rewards = pallet_bridge_messages::calc_relayers_rewards::<T, MessagesInstance>(
+			lane_id,
+			messages_relayers,
+			received_range,
+		);
+		if !relayers_rewards.is_empty() {
+			schedule_relayers_rewards::<T>(
+				confirmation_relayer,
+				relayers_rewards,
+				GetConfirmationFee::get(),
+			);
+		}
+	}
+}
+
+// Update rewards to given relayers, optionally rewarding confirmation relayer.
+fn schedule_relayers_rewards<T: Config>(
+	confirmation_relayer: &T::AccountId,
+	relayers_rewards: RelayersRewards<T::AccountId, T::Reward>,
+	confirmation_fee: T::Reward,
+) {
+	// reward every relayer except `confirmation_relayer`
+	let mut confirmation_relayer_reward = T::Reward::zero();
+	for (relayer, reward) in relayers_rewards {
+		let mut relayer_reward = reward.reward;
+
+		if relayer != *confirmation_relayer {
+			// If delivery confirmation is submitted by other relayer, let's deduct confirmation fee
+			// from relayer reward.
+			//
+			// If confirmation fee has been increased (or if it was the only component of message
+			// fee), then messages relayer may receive zero reward.
+			let mut confirmation_reward = T::Reward::try_from(reward.messages)
+				.unwrap_or_else(|_| Bounded::max_value())
+				.saturating_mul(confirmation_fee);
+			if confirmation_reward > relayer_reward {
+				confirmation_reward = relayer_reward;
+			}
+			relayer_reward = relayer_reward.saturating_sub(confirmation_reward);
+			confirmation_relayer_reward =
+				confirmation_relayer_reward.saturating_add(confirmation_reward);
+		} else {
+			// If delivery confirmation is submitted by this relayer, let's add confirmation fee
+			// from other relayers to this relayer reward.
+			confirmation_relayer_reward = confirmation_relayer_reward.saturating_add(reward.reward);
+			continue
+		}
+
+		schedule_relayer_reward::<T>(&relayer, relayer_reward);
+	}
+
+	// finally - pay reward to confirmation relayer
+	schedule_relayer_reward::<T>(confirmation_relayer, confirmation_relayer_reward);
+}
+
+/// Remember that the reward shall be paid to the relayer.
+fn schedule_relayer_reward<T: Config>(relayer: &T::AccountId, reward: T::Reward) {
+	if reward.is_zero() {
+		return
+	}
+
+	RelayerRewards::<T>::mutate(relayer, |old_reward: &mut Option<T::Reward>| {
+		let new_reward = old_reward.unwrap_or_else(Zero::zero).saturating_add(reward);
+		log::trace!(
+			target: "T::bridge-relayers",
+			"Relayer {:?} can now claim reward: {:?}",
+			relayer,
+			new_reward,
+		);
+		*old_reward = Some(new_reward);
+	});
+}
+
+#[cfg(test)]
+mod tests {
+	use super::*;
+	use crate::mock::*;
+
+	const RELAYER_1: AccountId = 1;
+	const RELAYER_2: AccountId = 2;
+	const RELAYER_3: AccountId = 3;
+
+	fn relayers_rewards() -> RelayersRewards<AccountId, Balance> {
+		vec![
+			(RELAYER_1, bp_messages::source_chain::RelayerRewards { reward: 100, messages: 2 }),
+			(RELAYER_2, bp_messages::source_chain::RelayerRewards { reward: 100, messages: 3 }),
+		]
+		.into_iter()
+		.collect()
+	}
+
+	#[test]
+	fn confirmation_relayer_is_rewarded_if_it_has_also_delivered_messages() {
+		run_test(|| {
+			schedule_relayers_rewards::<TestRuntime>(&RELAYER_2, relayers_rewards(), 10);
+
+			assert_eq!(RelayerRewards::<TestRuntime>::get(&RELAYER_1), Some(80));
+			assert_eq!(RelayerRewards::<TestRuntime>::get(&RELAYER_2), Some(120));
+		});
+	}
+
+	#[test]
+	fn confirmation_relayer_is_rewarded_if_it_has_not_delivered_any_delivered_messages() {
+		run_test(|| {
+			schedule_relayers_rewards::<TestRuntime>(&RELAYER_3, relayers_rewards(), 10);
+
+			assert_eq!(RelayerRewards::<TestRuntime>::get(&RELAYER_1), Some(80));
+			assert_eq!(RelayerRewards::<TestRuntime>::get(&RELAYER_2), Some(70));
+			assert_eq!(RelayerRewards::<TestRuntime>::get(&RELAYER_3), Some(50));
+		});
+	}
+
+	#[test]
+	fn only_confirmation_relayer_is_rewarded_if_confirmation_fee_has_significantly_increased() {
+		run_test(|| {
+			schedule_relayers_rewards::<TestRuntime>(&RELAYER_3, relayers_rewards(), 1000);
+
+			assert_eq!(RelayerRewards::<TestRuntime>::get(&RELAYER_1), None);
+			assert_eq!(RelayerRewards::<TestRuntime>::get(&RELAYER_2), None);
+			assert_eq!(RelayerRewards::<TestRuntime>::get(&RELAYER_3), Some(200));
+		});
+	}
+}
diff --git a/bridges/modules/relayers/src/weights.rs b/bridges/modules/relayers/src/weights.rs
new file mode 100644
index 0000000000000000000000000000000000000000..fd7efe940d45537e0ba5f31ebce169124d2de476
--- /dev/null
+++ b/bridges/modules/relayers/src/weights.rs
@@ -0,0 +1,73 @@
+// Copyright 2019-2021 Parity Technologies (UK) Ltd.
+// This file is part of Parity Bridges Common.
+
+// Parity Bridges Common is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity Bridges Common is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
+
+//! Autogenerated weights for `pallet_bridge_relayers`
+//!
+//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
+//! DATE: 2022-07-20, STEPS: 50, REPEAT: 20
+//! LOW RANGE: [], HIGH RANGE: []
+//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled
+//! CHAIN: Some("dev"), DB CACHE: 1024
+
+// Executed Command:
+// target/release/millau-bridge-node
+// benchmark
+// pallet
+// --chain=dev
+// --steps=50
+// --repeat=20
+// --pallet=pallet_bridge_relayers
+// --extrinsic=*
+// --execution=wasm
+// --wasm-execution=Compiled
+// --heap-pages=4096
+// --output=./modules/relayers/src/weights.rs
+// --template=./.maintain/millau-weight-template.hbs
+
+#![allow(clippy::all)]
+#![allow(unused_parens)]
+#![allow(unused_imports)]
+#![allow(missing_docs)]
+
+use frame_support::{
+	traits::Get,
+	weights::{constants::RocksDbWeight, Weight},
+};
+use sp_std::marker::PhantomData;
+
+/// Weight functions needed for `pallet_bridge_relayers`.
+pub trait WeightInfo {
+	fn claim_rewards() -> Weight;
+}
+
+/// Weights for `pallet_bridge_relayers` using the Millau node and recommended hardware.
+pub struct MillauWeight<T>(PhantomData<T>);
+impl<T: frame_system::Config> WeightInfo for MillauWeight<T> {
+	fn claim_rewards() -> Weight {
+		(38_435_000 as Weight)
+			.saturating_add(T::DbWeight::get().reads(2 as Weight))
+			.saturating_add(T::DbWeight::get().writes(2 as Weight))
+	}
+}
+
+// For backwards compatibility and tests
+impl WeightInfo for () {
+	fn claim_rewards() -> Weight {
+		(38_435_000 as Weight)
+			.saturating_add(RocksDbWeight::get().reads(2 as Weight))
+			.saturating_add(RocksDbWeight::get().writes(2 as Weight))
+	}
+}
diff --git a/bridges/primitives/relayers/Cargo.toml b/bridges/primitives/relayers/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..0d6d020a0997bd315b373c5fa2d39439e89382bb
--- /dev/null
+++ b/bridges/primitives/relayers/Cargo.toml
@@ -0,0 +1,38 @@
+[package]
+name = "bp-relayers"
+description = "Primitives of relayers module."
+version = "0.1.0"
+authors = ["Parity Technologies <admin@parity.io>"]
+edition = "2021"
+license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
+
+[dependencies]
+
+# Bridge dependencies
+
+#bp-runtime = { path = "../runtime", default-features = false }
+
+# Substrate Dependencies
+
+frame-support = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
+#frame-system = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
+#sp-core = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
+sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
+sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
+
+[dev-dependencies]
+hex = "0.4"
+hex-literal = "0.3"
+
+[features]
+default = ["std"]
+std = [
+#	"bp-runtime/std",
+	"frame-support/std",
+#	"frame-system/std",
+#	"scale-info/std",
+#	"serde",
+#	"sp-core/std",
+	"sp-runtime/std",
+    "sp-std/std",
+]
diff --git a/bridges/primitives/relayers/src/lib.rs b/bridges/primitives/relayers/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..d1c82d612e2e93b545cc2ec9bf8eef8b006243a3
--- /dev/null
+++ b/bridges/primitives/relayers/src/lib.rs
@@ -0,0 +1,45 @@
+// Copyright 2019-2021 Parity Technologies (UK) Ltd.
+// This file is part of Parity Bridges Common.
+
+// Parity Bridges Common is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+
+// Parity Bridges Common is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
+
+//! Primitives of messages module.
+
+#![warn(missing_docs)]
+#![cfg_attr(not(feature = "std"), no_std)]
+
+use sp_std::{fmt::Debug, marker::PhantomData};
+
+/// Reward payment procedure.
+pub trait PaymentProcedure<Relayer, Reward> {
+	/// Error that may be returned by the procedure.
+	type Error: Debug;
+
+	/// Pay reward to the relayer.
+	fn pay_reward(relayer: &Relayer, reward: Reward) -> Result<(), Self::Error>;
+}
+
+/// Reward payment procedure that is simply minting given amount of tokens.
+pub struct MintReward<T, Relayer>(PhantomData<(T, Relayer)>);
+
+impl<T, Relayer> PaymentProcedure<Relayer, T::Balance> for MintReward<T, Relayer>
+where
+	T: frame_support::traits::fungible::Mutate<Relayer>,
+{
+	type Error = sp_runtime::DispatchError;
+
+	fn pay_reward(relayer: &Relayer, reward: T::Balance) -> Result<(), Self::Error> {
+		T::mint_into(relayer, reward)
+	}
+}