diff --git a/bridges/bin/millau/runtime/src/lib.rs b/bridges/bin/millau/runtime/src/lib.rs
index 9bbdf3a93724999c8ea92f98d75b13ae98de53e1..1eb77ed75ea63aba70d39907cf0084a5a198f3a3 100644
--- a/bridges/bin/millau/runtime/src/lib.rs
+++ b/bridges/bin/millau/runtime/src/lib.rs
@@ -282,7 +282,7 @@ parameter_types! {
 impl pallet_transaction_payment::Config for Runtime {
 	type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter<Balances, ()>;
 	type TransactionByteFee = TransactionByteFee;
-	type WeightToFee = IdentityFee<Balance>;
+	type WeightToFee = bp_millau::WeightToFee;
 	type FeeMultiplierUpdate = pallet_transaction_payment::TargetedFeeAdjustment<
 		Runtime,
 		TargetBlockFullness,
diff --git a/bridges/bin/rialto/runtime/src/lib.rs b/bridges/bin/rialto/runtime/src/lib.rs
index 2899d18d140275064bff862a38bfdf05500faa3d..c13c0d78a1b40fde4fcb32ab7ecab1b0a2b181eb 100644
--- a/bridges/bin/rialto/runtime/src/lib.rs
+++ b/bridges/bin/rialto/runtime/src/lib.rs
@@ -414,7 +414,7 @@ parameter_types! {
 impl pallet_transaction_payment::Config for Runtime {
 	type OnChargeTransaction = pallet_transaction_payment::CurrencyAdapter<Balances, ()>;
 	type TransactionByteFee = TransactionByteFee;
-	type WeightToFee = IdentityFee<Balance>;
+	type WeightToFee = bp_rialto::WeightToFee;
 	type FeeMultiplierUpdate = pallet_transaction_payment::TargetedFeeAdjustment<
 		Runtime,
 		TargetBlockFullness,
diff --git a/bridges/primitives/chain-kusama/Cargo.toml b/bridges/primitives/chain-kusama/Cargo.toml
index 70ff3b844df07a295c098541f933c82d226cf542..33102ea4f88715079daa47191b3673d19d2bbb44 100644
--- a/bridges/primitives/chain-kusama/Cargo.toml
+++ b/bridges/primitives/chain-kusama/Cargo.toml
@@ -7,13 +7,17 @@ edition = "2018"
 license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
 
 [dependencies]
+smallvec = "1.6"
 
 # Bridge Dependencies
+
 bp-messages = { path = "../messages", default-features = false }
 bp-polkadot-core = { path = "../polkadot-core", default-features = false }
 bp-runtime = { path = "../runtime", default-features = false }
 
 # Substrate Based Dependencies
+
+frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" , default-features = false }
 sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" , default-features = false }
 sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
 
@@ -23,6 +27,7 @@ std = [
 	"bp-messages/std",
 	"bp-polkadot-core/std",
 	"bp-runtime/std",
+	"frame-support/std",
 	"sp-api/std",
 	"sp-std/std",
 ]
diff --git a/bridges/primitives/chain-kusama/src/lib.rs b/bridges/primitives/chain-kusama/src/lib.rs
index e5ab47259e54c2fe31d6d0441fabf49460037f1a..59bb486c90064a1f2d271fbd7aaf2f4074255629 100644
--- a/bridges/primitives/chain-kusama/src/lib.rs
+++ b/bridges/primitives/chain-kusama/src/lib.rs
@@ -21,6 +21,7 @@
 #![allow(clippy::unnecessary_mut_passed)]
 
 use bp_messages::{LaneId, MessageDetails, MessageNonce, UnrewardedRelayersState};
+use frame_support::weights::{WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial};
 use sp_std::prelude::*;
 
 pub use bp_polkadot_core::*;
@@ -28,6 +29,24 @@ pub use bp_polkadot_core::*;
 /// Kusama Chain
 pub type Kusama = PolkadotLike;
 
+// NOTE: This needs to be kept up to date with the Kusama runtime found in the Polkadot repo.
+pub struct WeightToFee;
+impl WeightToFeePolynomial for WeightToFee {
+	type Balance = Balance;
+	fn polynomial() -> WeightToFeeCoefficients<Self::Balance> {
+		const CENTS: Balance = 1_000_000_000_000 / 30_000;
+		// in Kusama, extrinsic base weight (smallest non-zero weight) is mapped to 1/10 CENT:
+		let p = CENTS;
+		let q = 10 * Balance::from(ExtrinsicBaseWeight::get());
+		smallvec::smallvec![WeightToFeeCoefficient {
+			degree: 1,
+			negative: false,
+			coeff_frac: Perbill::from_rational(p % q, q),
+			coeff_integer: p / q,
+		}]
+	}
+}
+
 // We use this to get the account on Kusama (target) which is derived from Polkadot's (source)
 // account.
 pub fn derive_account_from_polkadot_id(id: bp_runtime::SourceAccount<AccountId>) -> AccountId {
diff --git a/bridges/primitives/chain-millau/src/lib.rs b/bridges/primitives/chain-millau/src/lib.rs
index 34d59ce2ef9a0ca331917baca68994fce401d7b5..cb732bccfa055a92a6ed67c8f253ecb67ed89afa 100644
--- a/bridges/primitives/chain-millau/src/lib.rs
+++ b/bridges/primitives/chain-millau/src/lib.rs
@@ -25,7 +25,7 @@ mod millau_hash;
 use bp_messages::{LaneId, MessageDetails, MessageNonce, UnrewardedRelayersState};
 use bp_runtime::Chain;
 use frame_support::{
-	weights::{constants::WEIGHT_PER_SECOND, DispatchClass, Weight},
+	weights::{constants::WEIGHT_PER_SECOND, DispatchClass, IdentityFee, Weight},
 	Parameter, RuntimeDebug,
 };
 use frame_system::limits;
@@ -149,6 +149,9 @@ pub type AccountSigner = MultiSigner;
 /// Balance of an account.
 pub type Balance = u64;
 
+/// Weight-to-Fee type used by Millau.
+pub type WeightToFee = IdentityFee<Balance>;
+
 /// Millau chain.
 #[derive(RuntimeDebug)]
 pub struct Millau;
diff --git a/bridges/primitives/chain-polkadot/Cargo.toml b/bridges/primitives/chain-polkadot/Cargo.toml
index 22ded41b9145ca690f423939d7bdc611ecc48c55..4d3be2ae477d77d47bcb019245d05e8c3729da18 100644
--- a/bridges/primitives/chain-polkadot/Cargo.toml
+++ b/bridges/primitives/chain-polkadot/Cargo.toml
@@ -7,14 +7,17 @@ edition = "2018"
 license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
 
 [dependencies]
+smallvec = "1.6"
 
 # Bridge Dependencies
+
 bp-messages = { path = "../messages", default-features = false }
 bp-polkadot-core = { path = "../polkadot-core", default-features = false }
 bp-runtime = { path = "../runtime", default-features = false }
 
 # Substrate Based Dependencies
 
+frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" , default-features = false }
 sp-api = { git = "https://github.com/paritytech/substrate", branch = "master" , default-features = false }
 sp-std = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
 
@@ -24,6 +27,7 @@ std = [
 	"bp-messages/std",
 	"bp-polkadot-core/std",
 	"bp-runtime/std",
+	"frame-support/std",
 	"sp-api/std",
 	"sp-std/std",
 ]
diff --git a/bridges/primitives/chain-polkadot/src/lib.rs b/bridges/primitives/chain-polkadot/src/lib.rs
index b0ba77c66ffc34cc7dbb9fd5534832e74cce5c23..17c80e82b22c216a367f17aaf20759c23cec792e 100644
--- a/bridges/primitives/chain-polkadot/src/lib.rs
+++ b/bridges/primitives/chain-polkadot/src/lib.rs
@@ -21,6 +21,7 @@
 #![allow(clippy::unnecessary_mut_passed)]
 
 use bp_messages::{LaneId, MessageDetails, MessageNonce, UnrewardedRelayersState};
+use frame_support::weights::{WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial};
 use sp_std::prelude::*;
 
 pub use bp_polkadot_core::*;
@@ -28,6 +29,24 @@ pub use bp_polkadot_core::*;
 /// Polkadot Chain
 pub type Polkadot = PolkadotLike;
 
+// NOTE: This needs to be kept up to date with the Polkadot runtime found in the Polkadot repo.
+pub struct WeightToFee;
+impl WeightToFeePolynomial for WeightToFee {
+	type Balance = Balance;
+	fn polynomial() -> WeightToFeeCoefficients<Self::Balance> {
+		const CENTS: Balance = 10_000_000_000 / 100;
+		// in Polkadot, extrinsic base weight (smallest non-zero weight) is mapped to 1/10 CENT:
+		let p = CENTS;
+		let q = 10 * Balance::from(ExtrinsicBaseWeight::get());
+		smallvec::smallvec![WeightToFeeCoefficient {
+			degree: 1,
+			negative: false,
+			coeff_frac: Perbill::from_rational(p % q, q),
+			coeff_integer: p / q,
+		}]
+	}
+}
+
 // We use this to get the account on Polkadot (target) which is derived from Kusama's (source)
 // account.
 pub fn derive_account_from_kusama_id(id: bp_runtime::SourceAccount<AccountId>) -> AccountId {
diff --git a/bridges/primitives/chain-rialto/src/lib.rs b/bridges/primitives/chain-rialto/src/lib.rs
index 57fe5d4bfa54264b61107b018a2a298ec4d767aa..3c57b701b71668ae758f2474ba24b49caea03f3f 100644
--- a/bridges/primitives/chain-rialto/src/lib.rs
+++ b/bridges/primitives/chain-rialto/src/lib.rs
@@ -23,7 +23,7 @@
 use bp_messages::{LaneId, MessageDetails, MessageNonce, UnrewardedRelayersState};
 use bp_runtime::Chain;
 use frame_support::{
-	weights::{constants::WEIGHT_PER_SECOND, DispatchClass, Weight},
+	weights::{constants::WEIGHT_PER_SECOND, DispatchClass, IdentityFee, Weight},
 	Parameter, RuntimeDebug,
 };
 use frame_system::limits;
@@ -148,6 +148,9 @@ pub type Balance = u128;
 /// An instant or duration in time.
 pub type Moment = u64;
 
+/// Weight-to-Fee type used by Rialto.
+pub type WeightToFee = IdentityFee<Balance>;
+
 /// Rialto chain.
 #[derive(RuntimeDebug)]
 pub struct Rialto;
diff --git a/bridges/primitives/chain-rococo/src/lib.rs b/bridges/primitives/chain-rococo/src/lib.rs
index 2d5769a39c2f3aebc3d04bd773c1a8706191af56..ce58e7ec9ab02d30220707ec366ccfdd6b025cf1 100644
--- a/bridges/primitives/chain-rococo/src/lib.rs
+++ b/bridges/primitives/chain-rococo/src/lib.rs
@@ -21,7 +21,7 @@
 #![allow(clippy::unnecessary_mut_passed)]
 
 use bp_messages::{LaneId, MessageDetails, MessageNonce, UnrewardedRelayersState};
-use frame_support::weights::{WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial};
+use frame_support::weights::{Weight, WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial};
 use sp_std::prelude::*;
 use sp_version::RuntimeVersion;
 
@@ -97,6 +97,13 @@ pub const FROM_ROCOCO_LATEST_CONFIRMED_NONCE_METHOD: &str = "FromRococoInboundLa
 /// Name of the `FromRococoInboundLaneApi::unrewarded_relayers_state` runtime method.
 pub const FROM_ROCOCO_UNREWARDED_RELAYERS_STATE: &str = "FromRococoInboundLaneApi_unrewarded_relayers_state";
 
+/// Weight of pay-dispatch-fee operation for inbound messages at Rococo chain.
+///
+/// This value corresponds to the result of `pallet_bridge_messages::WeightInfoExt::pay_inbound_dispatch_fee_overhead()`
+/// call for your chain. Don't put too much reserve there, because it is used to **decrease**
+/// `DEFAULT_MESSAGE_DELIVERY_TX_WEIGHT` cost. So putting large reserve would make delivery transactions cheaper.
+pub const PAY_INBOUND_DISPATCH_FEE_WEIGHT: Weight = 600_000_000;
+
 sp_api::decl_runtime_apis! {
 	/// API for querying information about the finalized Rococo headers.
 	///
diff --git a/bridges/primitives/chain-westend/Cargo.toml b/bridges/primitives/chain-westend/Cargo.toml
index 42a9e67a7174fc3b333df5e23a7cc455de25d092..2fb8df091f5571f7fdf79acc2b7839987fa9e4e7 100644
--- a/bridges/primitives/chain-westend/Cargo.toml
+++ b/bridges/primitives/chain-westend/Cargo.toml
@@ -8,14 +8,18 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
 
 [dependencies]
 parity-scale-codec = { version = "2.2.0", default-features = false, features = ["derive"] }
+smallvec = "1.6"
 
 # Bridge Dependencies
+
 bp-header-chain = { path = "../header-chain", default-features = false }
 bp-messages = { path = "../messages", default-features = false }
 bp-polkadot-core = { path = "../polkadot-core", default-features = false }
 bp-runtime = { path = "../runtime", default-features = false }
 
 # Substrate Based Dependencies
+
+frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" , default-features = false }
 sp-api = { 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 }
@@ -28,6 +32,7 @@ std = [
 	"bp-messages/std",
 	"bp-polkadot-core/std",
 	"bp-runtime/std",
+	"frame-support/std",
 	"parity-scale-codec/std",
 	"sp-api/std",
 	"sp-runtime/std",
diff --git a/bridges/primitives/chain-westend/src/lib.rs b/bridges/primitives/chain-westend/src/lib.rs
index 4f8b9cccf4c75520bdba0dd7aff7c8253ba7ae0a..595c41e443e59e1e275630db450a2d61449d568a 100644
--- a/bridges/primitives/chain-westend/src/lib.rs
+++ b/bridges/primitives/chain-westend/src/lib.rs
@@ -22,6 +22,7 @@
 
 use bp_messages::{LaneId, MessageDetails, MessageNonce, UnrewardedRelayersState};
 use bp_runtime::Chain;
+use frame_support::weights::{WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial};
 use sp_std::prelude::*;
 use sp_version::RuntimeVersion;
 
@@ -30,6 +31,24 @@ pub use bp_polkadot_core::*;
 /// Westend Chain
 pub type Westend = PolkadotLike;
 
+// NOTE: This needs to be kept up to date with the Westend runtime found in the Polkadot repo.
+pub struct WeightToFee;
+impl WeightToFeePolynomial for WeightToFee {
+	type Balance = Balance;
+	fn polynomial() -> WeightToFeeCoefficients<Self::Balance> {
+		const CENTS: Balance = 1_000_000_000_000 / 1_000;
+		// in Westend, extrinsic base weight (smallest non-zero weight) is mapped to 1/10 CENT:
+		let p = CENTS;
+		let q = 10 * Balance::from(ExtrinsicBaseWeight::get());
+		smallvec::smallvec![WeightToFeeCoefficient {
+			degree: 1,
+			negative: false,
+			coeff_frac: Perbill::from_rational(p % q, q),
+			coeff_integer: p / q,
+		}]
+	}
+}
+
 pub type UncheckedExtrinsic = bp_polkadot_core::UncheckedExtrinsic<Call>;
 
 // NOTE: This needs to be kept up to date with the Westend runtime found in the Polkadot repo.
diff --git a/bridges/primitives/chain-wococo/src/lib.rs b/bridges/primitives/chain-wococo/src/lib.rs
index b846e00321fd01ea031ad09193f01cf70e2a9e8a..f962973d6c1c8f943a49f4f8793cafb008a0a597 100644
--- a/bridges/primitives/chain-wococo/src/lib.rs
+++ b/bridges/primitives/chain-wococo/src/lib.rs
@@ -25,7 +25,7 @@ use sp_std::prelude::*;
 
 pub use bp_polkadot_core::*;
 // Rococo runtime = Wococo runtime
-pub use bp_rococo::{WeightToFee, SESSION_LENGTH, VERSION};
+pub use bp_rococo::{WeightToFee, PAY_INBOUND_DISPATCH_FEE_WEIGHT, SESSION_LENGTH, VERSION};
 
 /// Wococo Chain
 pub type Wococo = PolkadotLike;
diff --git a/bridges/relays/bin-substrate/src/chains/millau_messages_to_rialto.rs b/bridges/relays/bin-substrate/src/chains/millau_messages_to_rialto.rs
index 4195e452d9e0d646d680a2c080ac7364364f52c5..51eb4e961b68a7d9cda24b67ec5ff5eff49a26ec 100644
--- a/bridges/relays/bin-substrate/src/chains/millau_messages_to_rialto.rs
+++ b/bridges/relays/bin-substrate/src/chains/millau_messages_to_rialto.rs
@@ -24,6 +24,7 @@ use sp_core::{Bytes, Pair};
 
 use bp_messages::MessageNonce;
 use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
+use frame_support::weights::Weight;
 use messages_relay::message_lane::MessageLane;
 use relay_millau_client::{HeaderId as MillauHeaderId, Millau, SigningParams as MillauSigningParams};
 use relay_rialto_client::{HeaderId as RialtoHeaderId, Rialto, SigningParams as RialtoSigningParams};
@@ -64,6 +65,8 @@ impl SubstrateMessageLane for MillauMessagesToRialto {
 	const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str = bp_millau::WITH_RIALTO_MESSAGES_PALLET_NAME;
 	const MESSAGE_PALLET_NAME_AT_TARGET: &'static str = bp_rialto::WITH_MILLAU_MESSAGES_PALLET_NAME;
 
+	const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight = bp_rialto::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
+
 	type SourceChain = Millau;
 	type TargetChain = Rialto;
 
diff --git a/bridges/relays/bin-substrate/src/chains/rialto_messages_to_millau.rs b/bridges/relays/bin-substrate/src/chains/rialto_messages_to_millau.rs
index 6fc7ee5b0828c99ba64e911dad622ea0d77163f0..0ced49a0a31463fd9a3d0ec9725d5c6c799ae742 100644
--- a/bridges/relays/bin-substrate/src/chains/rialto_messages_to_millau.rs
+++ b/bridges/relays/bin-substrate/src/chains/rialto_messages_to_millau.rs
@@ -24,6 +24,7 @@ use sp_core::{Bytes, Pair};
 
 use bp_messages::MessageNonce;
 use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
+use frame_support::weights::Weight;
 use messages_relay::message_lane::MessageLane;
 use relay_millau_client::{HeaderId as MillauHeaderId, Millau, SigningParams as MillauSigningParams};
 use relay_rialto_client::{HeaderId as RialtoHeaderId, Rialto, SigningParams as RialtoSigningParams};
@@ -64,6 +65,8 @@ impl SubstrateMessageLane for RialtoMessagesToMillau {
 	const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str = bp_rialto::WITH_MILLAU_MESSAGES_PALLET_NAME;
 	const MESSAGE_PALLET_NAME_AT_TARGET: &'static str = bp_millau::WITH_RIALTO_MESSAGES_PALLET_NAME;
 
+	const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight = bp_millau::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
+
 	type SourceChain = Rialto;
 	type TargetChain = Millau;
 
diff --git a/bridges/relays/bin-substrate/src/chains/rococo_messages_to_wococo.rs b/bridges/relays/bin-substrate/src/chains/rococo_messages_to_wococo.rs
index 9fa06770523e962411bd740a7749b45863298cfe..51f89c0dbe6dfce22d45f8c3ff0268160d7e5a3d 100644
--- a/bridges/relays/bin-substrate/src/chains/rococo_messages_to_wococo.rs
+++ b/bridges/relays/bin-substrate/src/chains/rococo_messages_to_wococo.rs
@@ -23,6 +23,7 @@ use sp_core::{Bytes, Pair};
 
 use bp_messages::MessageNonce;
 use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
+use frame_support::weights::Weight;
 use messages_relay::message_lane::MessageLane;
 use relay_rococo_client::{HeaderId as RococoHeaderId, Rococo, SigningParams as RococoSigningParams};
 use relay_substrate_client::{Chain, Client, TransactionSignScheme};
@@ -63,6 +64,8 @@ impl SubstrateMessageLane for RococoMessagesToWococo {
 	const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str = bp_rococo::WITH_WOCOCO_MESSAGES_PALLET_NAME;
 	const MESSAGE_PALLET_NAME_AT_TARGET: &'static str = bp_wococo::WITH_ROCOCO_MESSAGES_PALLET_NAME;
 
+	const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight = bp_wococo::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
+
 	type SourceChain = Rococo;
 	type TargetChain = Wococo;
 
diff --git a/bridges/relays/bin-substrate/src/chains/wococo_messages_to_rococo.rs b/bridges/relays/bin-substrate/src/chains/wococo_messages_to_rococo.rs
index 6e03d752f00034734f06492950cf691e745573db..9bc13dec1440d3a71c1a84d6192bc07acca52dd1 100644
--- a/bridges/relays/bin-substrate/src/chains/wococo_messages_to_rococo.rs
+++ b/bridges/relays/bin-substrate/src/chains/wococo_messages_to_rococo.rs
@@ -23,6 +23,7 @@ use sp_core::{Bytes, Pair};
 
 use bp_messages::MessageNonce;
 use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
+use frame_support::weights::Weight;
 use messages_relay::message_lane::MessageLane;
 use relay_rococo_client::{HeaderId as RococoHeaderId, Rococo, SigningParams as RococoSigningParams};
 use relay_substrate_client::{Chain, Client, TransactionSignScheme};
@@ -62,6 +63,8 @@ impl SubstrateMessageLane for WococoMessagesToRococo {
 	const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str = bp_wococo::WITH_ROCOCO_MESSAGES_PALLET_NAME;
 	const MESSAGE_PALLET_NAME_AT_TARGET: &'static str = bp_rococo::WITH_WOCOCO_MESSAGES_PALLET_NAME;
 
+	const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight = bp_rococo::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
+
 	type SourceChain = Wococo;
 	type TargetChain = Rococo;
 
diff --git a/bridges/relays/bin-substrate/src/cli/send_message.rs b/bridges/relays/bin-substrate/src/cli/send_message.rs
index 75c3118eb49f23c76afc407f0f757ec953746ee8..5ab6b84c32df5c9e1bff336ac5a3c3a154cd795d 100644
--- a/bridges/relays/bin-substrate/src/cli/send_message.rs
+++ b/bridges/relays/bin-substrate/src/cli/send_message.rs
@@ -190,8 +190,9 @@ impl SendMessage {
 
 					log::info!(
 						target: "bridge",
-						"Sending message to {}. Size: {}. Dispatch weight: {}. Fee: {}",
+						"Sending message to {}. Lane: {:?}. Size: {}. Dispatch weight: {}. Fee: {}",
 						Target::NAME,
+						lane,
 						signed_source_call.len(),
 						dispatch_weight,
 						fee,
diff --git a/bridges/relays/client-kusama/src/lib.rs b/bridges/relays/client-kusama/src/lib.rs
index 01869fb7b1c4b0f2d71eb597970e56fbbed93456..0c94e73aecff6ffb807ad5ada9e59202851ff04f 100644
--- a/bridges/relays/client-kusama/src/lib.rs
+++ b/bridges/relays/client-kusama/src/lib.rs
@@ -44,6 +44,7 @@ impl Chain for Kusama {
 	type SignedBlock = bp_kusama::SignedBlock;
 	type Call = ();
 	type Balance = bp_kusama::Balance;
+	type WeightToFee = bp_kusama::WeightToFee;
 }
 
 /// Kusama header type used in headers sync.
diff --git a/bridges/relays/client-millau/src/lib.rs b/bridges/relays/client-millau/src/lib.rs
index ce1ab870141f5f88714f5e826ac7a659700d5ea2..36430dd83dc219a6866b782363fb119a23e0191d 100644
--- a/bridges/relays/client-millau/src/lib.rs
+++ b/bridges/relays/client-millau/src/lib.rs
@@ -47,6 +47,7 @@ impl Chain for Millau {
 	type SignedBlock = millau_runtime::SignedBlock;
 	type Call = millau_runtime::Call;
 	type Balance = millau_runtime::Balance;
+	type WeightToFee = bp_millau::WeightToFee;
 }
 
 impl ChainWithBalances for Millau {
diff --git a/bridges/relays/client-polkadot/src/lib.rs b/bridges/relays/client-polkadot/src/lib.rs
index 04ddce29d091d8db8d43667666cd73757d3c4dc2..dc5564cc17bb5c489366c7f75745106184fb6eea 100644
--- a/bridges/relays/client-polkadot/src/lib.rs
+++ b/bridges/relays/client-polkadot/src/lib.rs
@@ -44,6 +44,7 @@ impl Chain for Polkadot {
 	type SignedBlock = bp_polkadot::SignedBlock;
 	type Call = ();
 	type Balance = bp_polkadot::Balance;
+	type WeightToFee = bp_polkadot::WeightToFee;
 }
 
 /// Polkadot header type used in headers sync.
diff --git a/bridges/relays/client-rialto/src/lib.rs b/bridges/relays/client-rialto/src/lib.rs
index aaa62eea0e7f305ddb5bb84d548a9a035e8ea755..8024ab1fdce565d4bb74d2863518f65d797049c4 100644
--- a/bridges/relays/client-rialto/src/lib.rs
+++ b/bridges/relays/client-rialto/src/lib.rs
@@ -47,6 +47,7 @@ impl Chain for Rialto {
 	type SignedBlock = rialto_runtime::SignedBlock;
 	type Call = rialto_runtime::Call;
 	type Balance = rialto_runtime::Balance;
+	type WeightToFee = bp_rialto::WeightToFee;
 }
 
 impl ChainWithBalances for Rialto {
diff --git a/bridges/relays/client-rococo/src/lib.rs b/bridges/relays/client-rococo/src/lib.rs
index 25c10999c66a56162512ef2af896be13bd3c4f6a..c419610dad05cbb97bca1aa680b962225f2fd389 100644
--- a/bridges/relays/client-rococo/src/lib.rs
+++ b/bridges/relays/client-rococo/src/lib.rs
@@ -52,6 +52,7 @@ impl Chain for Rococo {
 	type SignedBlock = bp_rococo::SignedBlock;
 	type Call = crate::runtime::Call;
 	type Balance = bp_rococo::Balance;
+	type WeightToFee = bp_rococo::WeightToFee;
 }
 
 impl ChainWithBalances for Rococo {
diff --git a/bridges/relays/client-substrate/src/chain.rs b/bridges/relays/client-substrate/src/chain.rs
index 7bc5f711f0605a7708fe4add4d16d5a0bc6f9a55..81397e2c4ae30f67a0128647967a7a92363c846c 100644
--- a/bridges/relays/client-substrate/src/chain.rs
+++ b/bridges/relays/client-substrate/src/chain.rs
@@ -15,7 +15,7 @@
 // along with Parity Bridges Common.  If not, see <http://www.gnu.org/licenses/>.
 
 use bp_runtime::Chain as ChainBase;
-use frame_support::Parameter;
+use frame_support::{weights::WeightToFeePolynomial, Parameter};
 use jsonrpsee_ws_client::{DeserializeOwned, Serialize};
 use num_traits::{Bounded, CheckedSub, SaturatingAdd, Zero};
 use sp_core::{storage::StorageKey, Pair};
@@ -77,12 +77,17 @@ pub trait Chain: ChainBase + Clone {
 		+ SaturatingAdd
 		+ Zero
 		+ std::convert::TryFrom<sp_core::U256>;
+
+	/// Type that is used by the chain, to convert from weight to fee.
+	type WeightToFee: WeightToFeePolynomial<Balance = Self::Balance>;
 }
 
 /// Balance type used by the chain
 pub type BalanceOf<C> = <C as Chain>::Balance;
 /// Index type used by the chain
 pub type IndexOf<C> = <C as Chain>::Index;
+/// Weight-to-Fee type used by the chain
+pub type WeightToFeeOf<C> = <C as Chain>::WeightToFee;
 
 /// Substrate-based chain with `frame_system::Config::AccountData` set to
 /// the `pallet_balances::AccountData<Balance>`.
diff --git a/bridges/relays/client-substrate/src/client.rs b/bridges/relays/client-substrate/src/client.rs
index b63e7f30a6cc208febeb3a837e19d387d26ff498..9fb778651b475af5e6d216a94c10add1afb73bb6 100644
--- a/bridges/relays/client-substrate/src/client.rs
+++ b/bridges/relays/client-substrate/src/client.rs
@@ -310,23 +310,24 @@ impl<C: Chain> Client<C> {
 	}
 
 	/// Estimate fee that will be spent on given extrinsic.
-	pub async fn estimate_extrinsic_fee(&self, transaction: Bytes) -> Result<C::Balance> {
+	pub async fn estimate_extrinsic_fee(&self, transaction: Bytes) -> Result<InclusionFee<C::Balance>> {
 		self.jsonrpsee_execute(move |client| async move {
 			let fee_details = Substrate::<C>::payment_query_fee_details(&*client, transaction, None).await?;
 			let inclusion_fee = fee_details
 				.inclusion_fee
-				.map(|inclusion_fee| {
-					InclusionFee {
-						base_fee: C::Balance::try_from(inclusion_fee.base_fee.into_u256())
-							.unwrap_or_else(|_| C::Balance::max_value()),
-						len_fee: C::Balance::try_from(inclusion_fee.len_fee.into_u256())
-							.unwrap_or_else(|_| C::Balance::max_value()),
-						adjusted_weight_fee: C::Balance::try_from(inclusion_fee.adjusted_weight_fee.into_u256())
-							.unwrap_or_else(|_| C::Balance::max_value()),
-					}
-					.inclusion_fee()
+				.map(|inclusion_fee| InclusionFee {
+					base_fee: C::Balance::try_from(inclusion_fee.base_fee.into_u256())
+						.unwrap_or_else(|_| C::Balance::max_value()),
+					len_fee: C::Balance::try_from(inclusion_fee.len_fee.into_u256())
+						.unwrap_or_else(|_| C::Balance::max_value()),
+					adjusted_weight_fee: C::Balance::try_from(inclusion_fee.adjusted_weight_fee.into_u256())
+						.unwrap_or_else(|_| C::Balance::max_value()),
 				})
-				.unwrap_or_else(Zero::zero);
+				.unwrap_or_else(|| InclusionFee {
+					base_fee: Zero::zero(),
+					len_fee: Zero::zero(),
+					adjusted_weight_fee: Zero::zero(),
+				});
 			Ok(inclusion_fee)
 		})
 		.await
diff --git a/bridges/relays/client-substrate/src/guard.rs b/bridges/relays/client-substrate/src/guard.rs
index 37a7c2aa275c5b62e265cae7229566e776475a5b..f7df7dbb05c5b26a0b9d4ac359d88c8e879c35a7 100644
--- a/bridges/relays/client-substrate/src/guard.rs
+++ b/bridges/relays/client-substrate/src/guard.rs
@@ -194,6 +194,7 @@ mod tests {
 			sp_runtime::generic::SignedBlock<sp_runtime::generic::Block<Self::Header, sp_runtime::OpaqueExtrinsic>>;
 		type Call = ();
 		type Balance = u32;
+		type WeightToFee = frame_support::weights::IdentityFee<u32>;
 	}
 
 	impl ChainWithBalances for TestChain {
diff --git a/bridges/relays/client-substrate/src/lib.rs b/bridges/relays/client-substrate/src/lib.rs
index 6aa319c036773940530682ef1a6613981de31629..be1835df3227dc85d07b77e71365b89fb7238edc 100644
--- a/bridges/relays/client-substrate/src/lib.rs
+++ b/bridges/relays/client-substrate/src/lib.rs
@@ -31,7 +31,9 @@ pub mod metrics;
 
 use std::time::Duration;
 
-pub use crate::chain::{BalanceOf, BlockWithJustification, Chain, ChainWithBalances, IndexOf, TransactionSignScheme};
+pub use crate::chain::{
+	BalanceOf, BlockWithJustification, Chain, ChainWithBalances, IndexOf, TransactionSignScheme, WeightToFeeOf,
+};
 pub use crate::client::{Client, JustificationsSubscription, OpaqueGrandpaAuthoritiesSet};
 pub use crate::error::{Error, Result};
 pub use crate::sync_header::SyncHeader;
diff --git a/bridges/relays/client-westend/src/lib.rs b/bridges/relays/client-westend/src/lib.rs
index fefab00c5615f41fe103f3f70a1cc44797ece907..b33be7421cf44fe7e26ed46d561c1157a9f4ae86 100644
--- a/bridges/relays/client-westend/src/lib.rs
+++ b/bridges/relays/client-westend/src/lib.rs
@@ -50,6 +50,7 @@ impl Chain for Westend {
 	type SignedBlock = bp_westend::SignedBlock;
 	type Call = bp_westend::Call;
 	type Balance = bp_westend::Balance;
+	type WeightToFee = bp_westend::WeightToFee;
 }
 
 impl ChainWithBalances for Westend {
diff --git a/bridges/relays/client-wococo/src/lib.rs b/bridges/relays/client-wococo/src/lib.rs
index 4b3bdd7d84d1437a3c5db21dd53884d58582c128..03cb4d71563f7701b34de12c051081f7c57ac0b1 100644
--- a/bridges/relays/client-wococo/src/lib.rs
+++ b/bridges/relays/client-wococo/src/lib.rs
@@ -52,6 +52,7 @@ impl Chain for Wococo {
 	type SignedBlock = bp_wococo::SignedBlock;
 	type Call = crate::runtime::Call;
 	type Balance = bp_wococo::Balance;
+	type WeightToFee = bp_wococo::WeightToFee;
 }
 
 impl ChainWithBalances for Wococo {
diff --git a/bridges/relays/lib-substrate-relay/Cargo.toml b/bridges/relays/lib-substrate-relay/Cargo.toml
index 8a341c45ad62e1d10cfe4b1507935bd5285690ea..7ab81b786b5d033590c7e7c17d39bad9ef6019cc 100644
--- a/bridges/relays/lib-substrate-relay/Cargo.toml
+++ b/bridges/relays/lib-substrate-relay/Cargo.toml
@@ -40,8 +40,9 @@ sp-finality-grandpa = { git = "https://github.com/paritytech/substrate", branch
 sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" }
 
 [dev-dependencies]
-relay-millau-client = { path = "../client-millau" }
-relay-rialto-client = { path = "../client-rialto" }
-bp-rialto = { path = "../../primitives/chain-rialto" }
 bp-millau = { path = "../../primitives/chain-millau" }
+bp-rococo = { path = "../../primitives/chain-rococo" }
+bp-wococo = { path = "../../primitives/chain-wococo" }
+relay-rococo-client = { path = "../client-rococo" }
+relay-wococo-client = { path = "../client-wococo" }
 rialto-runtime = { path = "../../bin/rialto/runtime" }
diff --git a/bridges/relays/lib-substrate-relay/src/messages_lane.rs b/bridges/relays/lib-substrate-relay/src/messages_lane.rs
index 0b648e8cc83291d2216438c8e43d57f6eb4bbc92..4614a50883a020b94db670b223b8d54b9a68490b 100644
--- a/bridges/relays/lib-substrate-relay/src/messages_lane.rs
+++ b/bridges/relays/lib-substrate-relay/src/messages_lane.rs
@@ -88,6 +88,13 @@ pub trait SubstrateMessageLane: 'static + Clone + Send + Sync {
 	/// Name of the messages pallet as it is declared in the `construct_runtime!()` at target chain.
 	const MESSAGE_PALLET_NAME_AT_TARGET: &'static str;
 
+	/// Extra weight of the delivery transaction at the target chain, that is paid to cover
+	/// dispatch fee payment.
+	///
+	/// If dispatch fee is paid at the source chain, then this weight is refunded by the
+	/// delivery transaction.
+	const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight;
+
 	/// Source chain.
 	type SourceChain: Chain;
 	/// Target chain.
diff --git a/bridges/relays/lib-substrate-relay/src/messages_source.rs b/bridges/relays/lib-substrate-relay/src/messages_source.rs
index 9a98b9b1d48c3ce390d6b6c19a4b22d490af92ce..d6e92d1e51c4f7c0e8d797781e79ed838baa19fd 100644
--- a/bridges/relays/lib-substrate-relay/src/messages_source.rs
+++ b/bridges/relays/lib-substrate-relay/src/messages_source.rs
@@ -24,7 +24,6 @@ use crate::on_demand_headers::OnDemandHeadersRelay;
 
 use async_trait::async_trait;
 use bp_messages::{LaneId, MessageNonce, UnrewardedRelayersState};
-use bp_runtime::messages::DispatchFeePayment;
 use bridge_runtime_common::messages::{
 	source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof,
 };
@@ -43,7 +42,10 @@ use relay_substrate_client::{
 };
 use relay_utils::{relay_loop::Client as RelayClient, BlockNumberBase, HeaderId};
 use sp_core::Bytes;
-use sp_runtime::{traits::Header as HeaderT, DeserializeOwned};
+use sp_runtime::{
+	traits::{AtLeast32BitUnsigned, Header as HeaderT},
+	DeserializeOwned,
+};
 use std::ops::RangeInclusive;
 
 /// Intermediate message proof returned by the source Substrate node. Includes everything
@@ -121,6 +123,7 @@ where
 	>,
 	<P::MessageLane as MessageLane>::TargetHeaderNumber: Decode,
 	<P::MessageLane as MessageLane>::TargetHeaderHash: Decode,
+	<P::MessageLane as MessageLane>::SourceChainBalance: AtLeast32BitUnsigned,
 {
 	async fn state(&self) -> Result<SourceClientState<P::MessageLane>, SubstrateError> {
 		// we can't continue to deliver confirmations if source node is out of sync, because
@@ -264,6 +267,7 @@ where
 				prepare_dummy_messages_delivery_proof::<P::SourceChain, P::TargetChain>(),
 			))
 			.await
+			.map(|fee| fee.inclusion_fee())
 			.unwrap_or_else(|_| BalanceOf::<P::SourceChain>::max_value())
 	}
 }
@@ -397,7 +401,7 @@ fn make_message_details_map<C: Chain>(
 				dispatch_weight: details.dispatch_weight,
 				size: details.size as _,
 				reward: details.delivery_and_dispatch_fee,
-				dispatch_fee_payment: DispatchFeePayment::AtSourceChain,
+				dispatch_fee_payment: details.dispatch_fee_payment,
 			},
 		);
 		expected_nonce = details.nonce + 1;
@@ -411,12 +415,12 @@ fn make_message_details_map<C: Chain>(
 mod tests {
 	use super::*;
 	use bp_runtime::messages::DispatchFeePayment;
-	use relay_millau_client::Millau;
-	use relay_rialto_client::Rialto;
+	use relay_rococo_client::Rococo;
+	use relay_wococo_client::Wococo;
 
 	fn message_details_from_rpc(
 		nonces: RangeInclusive<MessageNonce>,
-	) -> Vec<bp_messages::MessageDetails<bp_rialto::Balance>> {
+	) -> Vec<bp_messages::MessageDetails<bp_wococo::Balance>> {
 		nonces
 			.into_iter()
 			.map(|nonce| bp_messages::MessageDetails {
@@ -432,7 +436,7 @@ mod tests {
 	#[test]
 	fn make_message_details_map_succeeds_if_no_messages_are_missing() {
 		assert_eq!(
-			make_message_details_map::<relay_rialto_client::Rialto>(message_details_from_rpc(1..=3), 1..=3,).unwrap(),
+			make_message_details_map::<Wococo>(message_details_from_rpc(1..=3), 1..=3,).unwrap(),
 			vec![
 				(
 					1,
@@ -470,7 +474,7 @@ mod tests {
 	#[test]
 	fn make_message_details_map_succeeds_if_head_messages_are_missing() {
 		assert_eq!(
-			make_message_details_map::<relay_rialto_client::Rialto>(message_details_from_rpc(2..=3), 1..=3,).unwrap(),
+			make_message_details_map::<Wococo>(message_details_from_rpc(2..=3), 1..=3,).unwrap(),
 			vec![
 				(
 					2,
@@ -501,7 +505,7 @@ mod tests {
 		let mut message_details_from_rpc = message_details_from_rpc(1..=3);
 		message_details_from_rpc.remove(1);
 		assert!(matches!(
-			make_message_details_map::<relay_rialto_client::Rialto>(message_details_from_rpc, 1..=3,),
+			make_message_details_map::<Wococo>(message_details_from_rpc, 1..=3,),
 			Err(SubstrateError::Custom(_))
 		));
 	}
@@ -509,7 +513,7 @@ mod tests {
 	#[test]
 	fn make_message_details_map_fails_if_tail_messages_are_missing() {
 		assert!(matches!(
-			make_message_details_map::<relay_rialto_client::Rialto>(message_details_from_rpc(1..=2), 1..=3,),
+			make_message_details_map::<Wococo>(message_details_from_rpc(1..=2), 1..=3,),
 			Err(SubstrateError::Custom(_))
 		));
 	}
@@ -517,15 +521,15 @@ mod tests {
 	#[test]
 	fn make_message_details_map_fails_if_all_messages_are_missing() {
 		assert!(matches!(
-			make_message_details_map::<relay_rialto_client::Rialto>(vec![], 1..=3),
+			make_message_details_map::<Wococo>(vec![], 1..=3),
 			Err(SubstrateError::Custom(_))
 		));
 	}
 
 	#[test]
 	fn prepare_dummy_messages_delivery_proof_works() {
-		let expected_minimal_size = Rialto::MAXIMAL_ENCODED_ACCOUNT_ID_SIZE + Millau::STORAGE_PROOF_OVERHEAD;
-		let dummy_proof = prepare_dummy_messages_delivery_proof::<Rialto, Millau>();
+		let expected_minimal_size = Wococo::MAXIMAL_ENCODED_ACCOUNT_ID_SIZE + Rococo::STORAGE_PROOF_OVERHEAD;
+		let dummy_proof = prepare_dummy_messages_delivery_proof::<Wococo, Rococo>();
 		assert!(
 			dummy_proof.1.encode().len() as u32 > expected_minimal_size,
 			"Expected proof size at least {}. Got: {}",
diff --git a/bridges/relays/lib-substrate-relay/src/messages_target.rs b/bridges/relays/lib-substrate-relay/src/messages_target.rs
index 8e460769156ad5ecfe008eb763245e77d3195fcf..20eb7a54b383dc65bbd9902159029befc3074d0d 100644
--- a/bridges/relays/lib-substrate-relay/src/messages_target.rs
+++ b/bridges/relays/lib-substrate-relay/src/messages_target.rs
@@ -29,7 +29,7 @@ use bridge_runtime_common::messages::{
 	source::FromBridgedChainMessagesDeliveryProof, target::FromBridgedChainMessagesProof,
 };
 use codec::{Decode, Encode};
-use frame_support::weights::Weight;
+use frame_support::weights::{Weight, WeightToFeePolynomial};
 use messages_relay::message_lane::MessageLane;
 use messages_relay::{
 	message_lane::{SourceHeaderIdOf, TargetHeaderIdOf},
@@ -37,11 +37,11 @@ use messages_relay::{
 };
 use num_traits::{Bounded, Zero};
 use relay_substrate_client::{
-	BalanceOf, BlockNumberOf, Chain, Client, Error as SubstrateError, HashOf, HeaderOf, IndexOf,
+	BalanceOf, BlockNumberOf, Chain, Client, Error as SubstrateError, HashOf, HeaderOf, IndexOf, WeightToFeeOf,
 };
 use relay_utils::{relay_loop::Client as RelayClient, BlockNumberBase, HeaderId};
 use sp_core::Bytes;
-use sp_runtime::{DeserializeOwned, FixedPointNumber, FixedU128};
+use sp_runtime::{traits::Saturating, DeserializeOwned, FixedPointNumber, FixedU128};
 use std::{convert::TryFrom, ops::RangeInclusive};
 
 /// Message receiving proof returned by the target Substrate node.
@@ -118,7 +118,6 @@ where
 	BlockNumberOf<P::TargetChain>: Copy,
 	HeaderOf<P::TargetChain>: DeserializeOwned,
 	BlockNumberOf<P::TargetChain>: BlockNumberBase,
-
 	P::MessageLane: MessageLane<
 		MessagesProof = SubstrateMessagesProof<P::SourceChain>,
 		MessagesReceivingProof = SubstrateMessagesReceivingProof<P::TargetChain>,
@@ -244,6 +243,7 @@ where
 	async fn estimate_delivery_transaction_in_source_tokens(
 		&self,
 		nonces: RangeInclusive<MessageNonce>,
+		total_prepaid_nonces: MessageNonce,
 		total_dispatch_weight: Weight,
 		total_size: u32,
 	) -> Result<<P::MessageLane as MessageLane>::SourceChainBalance, SubstrateError> {
@@ -258,27 +258,88 @@ where
 					P::SourceChain::NAME,
 				))
 			})?;
+
+		// Prepare 'dummy' delivery transaction - we only care about its length and dispatch weight.
+		let delivery_tx = self.lane.make_messages_delivery_transaction(
+			Zero::zero(),
+			HeaderId(Default::default(), Default::default()),
+			nonces.clone(),
+			prepare_dummy_messages_proof::<P::SourceChain>(nonces.clone(), total_dispatch_weight, total_size),
+		);
+		let delivery_tx_fee = self.client.estimate_extrinsic_fee(delivery_tx).await?;
+		let inclusion_fee_in_target_tokens = delivery_tx_fee.inclusion_fee();
+
+		// The pre-dispatch cost of delivery transaction includes additional fee to cover dispatch fee payment
+		// (Currency::transfer in regular deployment). But if message dispatch has already been paid
+		// at the Source chain, the delivery transaction will refund relayer with this additional cost.
+		// But `estimate_extrinsic_fee` obviously just returns pre-dispatch cost of the transaction. So
+		// if transaction delivers prepaid message, then it may happen that pre-dispatch cost is larger
+		// than reward and `Rational` relayer will refuse to deliver this message.
+		//
+		// The most obvious solution would be to deduct total weight of dispatch fee payments from the
+		// `total_dispatch_weight` and use regular `estimate_extrinsic_fee` call. But what if
+		// `total_dispatch_weight` is less than total dispatch fee payments weight? Weight is strictly
+		// positive, so we can't use this option.
+		//
+		// Instead we'll be directly using `WeightToFee` and `NextFeeMultiplier` of the Target chain.
+		// This requires more knowledge of the Target chain, but seems there's no better way to solve
+		// this now.
+		let expected_refund_in_target_tokens = if total_prepaid_nonces != 0 {
+			const WEIGHT_DIFFERENCE: Weight = 100;
+
+			let larger_dispatch_weight = total_dispatch_weight.saturating_add(WEIGHT_DIFFERENCE);
+			let larger_delivery_tx_fee = self
+				.client
+				.estimate_extrinsic_fee(self.lane.make_messages_delivery_transaction(
+					Zero::zero(),
+					HeaderId(Default::default(), Default::default()),
+					nonces.clone(),
+					prepare_dummy_messages_proof::<P::SourceChain>(nonces.clone(), larger_dispatch_weight, total_size),
+				))
+				.await?;
+
+			compute_prepaid_messages_refund::<P>(
+				total_prepaid_nonces,
+				compute_fee_multiplier::<P::TargetChain>(
+					delivery_tx_fee.adjusted_weight_fee,
+					total_dispatch_weight,
+					larger_delivery_tx_fee.adjusted_weight_fee,
+					larger_dispatch_weight,
+				),
+			)
+		} else {
+			Zero::zero()
+		};
+
+		let delivery_fee_in_source_tokens = convert_target_tokens_to_source_tokens::<P::SourceChain, P::TargetChain>(
+			FixedU128::from_float(conversion_rate),
+			inclusion_fee_in_target_tokens.saturating_sub(expected_refund_in_target_tokens),
+		);
+
 		log::trace!(
 			target: "bridge",
-			"Using conversion rate {} when converting from {} tokens to {} tokens",
-			conversion_rate,
-			P::TargetChain::NAME,
-			P::SourceChain::NAME,
+			"Estimated {} -> {} messages delivery transaction.\n\t\
+				Total nonces: {:?}\n\t\
+				Prepaid messages: {}\n\t\
+				Total messages size: {}\n\t\
+				Total messages dispatch weight: {}\n\t\
+				Inclusion fee (in {1} tokens): {:?}\n\t\
+				Expected refund (in {1} tokens): {:?}\n\t\
+				{1} -> {0} conversion rate: {:?}\n\t\
+				Expected delivery tx fee (in {0} tokens): {:?}",
+				P::SourceChain::NAME,
+				P::TargetChain::NAME,
+				nonces,
+				total_prepaid_nonces,
+				total_size,
+				total_dispatch_weight,
+				inclusion_fee_in_target_tokens,
+				expected_refund_in_target_tokens,
+				conversion_rate,
+				delivery_fee_in_source_tokens,
 		);
-		Ok(
-			convert_target_tokens_to_source_tokens::<P::SourceChain, P::TargetChain>(
-				FixedU128::from_float(conversion_rate),
-				self.client
-					.estimate_extrinsic_fee(self.lane.make_messages_delivery_transaction(
-						Zero::zero(),
-						HeaderId(Default::default(), Default::default()),
-						nonces.clone(),
-						prepare_dummy_messages_proof::<P::SourceChain>(nonces, total_dispatch_weight, total_size),
-					))
-					.await
-					.unwrap_or_else(|_| <P::TargetChain as Chain>::Balance::max_value()),
-			),
-		)
+
+		Ok(delivery_fee_in_source_tokens)
 	}
 }
 
@@ -316,17 +377,110 @@ where
 		.unwrap_or_else(|_| SC::Balance::max_value())
 }
 
+/// Compute fee multiplier that is used by the chain, given couple of fees for transactions
+/// that are only differ in dispatch weights.
+///
+/// This function assumes that standard transaction payment pallet is used by the chain.
+/// The only fee component that depends on dispatch weight is the `adjusted_weight_fee`.
+///
+/// **WARNING**: this functions will only be accurate if weight-to-fee conversion function
+/// is linear. For non-linear polynomials the error will grow with `weight_difference` growth.
+/// So better to use smaller differences.
+fn compute_fee_multiplier<C: Chain>(
+	smaller_adjusted_weight_fee: BalanceOf<C>,
+	smaller_tx_weight: Weight,
+	larger_adjusted_weight_fee: BalanceOf<C>,
+	larger_tx_weight: Weight,
+) -> FixedU128 {
+	let adjusted_weight_fee_difference = larger_adjusted_weight_fee.saturating_sub(smaller_adjusted_weight_fee);
+	let smaller_tx_unadjusted_weight_fee = WeightToFeeOf::<C>::calc(&smaller_tx_weight);
+	let larger_tx_unadjusted_weight_fee = WeightToFeeOf::<C>::calc(&larger_tx_weight);
+	FixedU128::saturating_from_rational(
+		adjusted_weight_fee_difference,
+		larger_tx_unadjusted_weight_fee.saturating_sub(smaller_tx_unadjusted_weight_fee),
+	)
+}
+
+/// Compute fee that will be refunded to the relayer because dispatch of `total_prepaid_nonces`
+/// messages has been paid at the source chain.
+fn compute_prepaid_messages_refund<P: SubstrateMessageLane>(
+	total_prepaid_nonces: MessageNonce,
+	fee_multiplier: FixedU128,
+) -> BalanceOf<P::TargetChain> {
+	fee_multiplier.saturating_mul_int(WeightToFeeOf::<P::TargetChain>::calc(
+		&P::PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN.saturating_mul(total_prepaid_nonces),
+	))
+}
+
 #[cfg(test)]
 mod tests {
 	use super::*;
-	use relay_millau_client::Millau;
-	use relay_rialto_client::Rialto;
+	use relay_rococo_client::{Rococo, SigningParams as RococoSigningParams};
+	use relay_wococo_client::{SigningParams as WococoSigningParams, Wococo};
+
+	#[derive(Clone)]
+	struct TestSubstrateMessageLane;
+
+	impl SubstrateMessageLane for TestSubstrateMessageLane {
+		type MessageLane = crate::messages_lane::SubstrateMessageLaneToSubstrate<
+			Rococo,
+			RococoSigningParams,
+			Wococo,
+			WococoSigningParams,
+		>;
+
+		const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str = "";
+		const OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD: &'static str = "";
+		const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str = "";
+
+		const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str = "";
+		const INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD: &'static str = "";
+		const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str = "";
+
+		const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = "";
+		const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str = "";
+
+		const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str = "";
+		const MESSAGE_PALLET_NAME_AT_TARGET: &'static str = "";
+
+		const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight = 100_000;
+
+		type SourceChain = Rococo;
+		type TargetChain = Wococo;
+
+		fn source_transactions_author(&self) -> bp_rococo::AccountId {
+			unreachable!()
+		}
+
+		fn make_messages_receiving_proof_transaction(
+			&self,
+			_transaction_nonce: <Rococo as Chain>::Index,
+			_generated_at_block: TargetHeaderIdOf<Self::MessageLane>,
+			_proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
+		) -> Bytes {
+			unreachable!()
+		}
+
+		fn target_transactions_author(&self) -> bp_wococo::AccountId {
+			unreachable!()
+		}
+
+		fn make_messages_delivery_transaction(
+			&self,
+			_transaction_nonce: <Wococo as Chain>::Index,
+			_generated_at_header: SourceHeaderIdOf<Self::MessageLane>,
+			_nonces: RangeInclusive<MessageNonce>,
+			_proof: <Self::MessageLane as MessageLane>::MessagesProof,
+		) -> Bytes {
+			unreachable!()
+		}
+	}
 
 	#[test]
 	fn prepare_dummy_messages_proof_works() {
 		const DISPATCH_WEIGHT: Weight = 1_000_000;
 		const SIZE: u32 = 1_000;
-		let dummy_proof = prepare_dummy_messages_proof::<Rialto>(1..=10, DISPATCH_WEIGHT, SIZE);
+		let dummy_proof = prepare_dummy_messages_proof::<Rococo>(1..=10, DISPATCH_WEIGHT, SIZE);
 		assert_eq!(dummy_proof.0, DISPATCH_WEIGHT);
 		assert!(
 			dummy_proof.1.encode().len() as u32 > SIZE,
@@ -339,16 +493,47 @@ mod tests {
 	#[test]
 	fn convert_target_tokens_to_source_tokens_works() {
 		assert_eq!(
-			convert_target_tokens_to_source_tokens::<Rialto, Millau>((150, 100).into(), 1_000),
+			convert_target_tokens_to_source_tokens::<Rococo, Wococo>((150, 100).into(), 1_000),
 			1_500
 		);
 		assert_eq!(
-			convert_target_tokens_to_source_tokens::<Rialto, Millau>((50, 100).into(), 1_000),
+			convert_target_tokens_to_source_tokens::<Rococo, Wococo>((50, 100).into(), 1_000),
 			500
 		);
 		assert_eq!(
-			convert_target_tokens_to_source_tokens::<Rialto, Millau>((100, 100).into(), 1_000),
+			convert_target_tokens_to_source_tokens::<Rococo, Wococo>((100, 100).into(), 1_000),
 			1_000
 		);
 	}
+
+	#[test]
+	fn compute_fee_multiplier_returns_sane_results() {
+		let multiplier = FixedU128::saturating_from_rational(1, 1000);
+
+		let smaller_weight = 1_000_000;
+		let smaller_adjusted_weight_fee = multiplier.saturating_mul_int(WeightToFeeOf::<Rococo>::calc(&smaller_weight));
+
+		let larger_weight = smaller_weight + 200_000;
+		let larger_adjusted_weight_fee = multiplier.saturating_mul_int(WeightToFeeOf::<Rococo>::calc(&larger_weight));
+
+		assert_eq!(
+			compute_fee_multiplier::<Rococo>(
+				smaller_adjusted_weight_fee,
+				smaller_weight,
+				larger_adjusted_weight_fee,
+				larger_weight,
+			),
+			multiplier,
+		);
+	}
+
+	#[test]
+	fn compute_prepaid_messages_refund_returns_sane_results() {
+		assert!(
+			compute_prepaid_messages_refund::<TestSubstrateMessageLane>(
+				10,
+				FixedU128::saturating_from_rational(110, 100),
+			) > (10 * TestSubstrateMessageLane::PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN).into()
+		);
+	}
 }
diff --git a/bridges/relays/lib-substrate-relay/src/on_demand_headers.rs b/bridges/relays/lib-substrate-relay/src/on_demand_headers.rs
index 2f8441dbfc986ab67edb5ad6b839c1f98935cef6..8973d4fe3ec08a768d9419005124c7f7eb3ce427 100644
--- a/bridges/relays/lib-substrate-relay/src/on_demand_headers.rs
+++ b/bridges/relays/lib-substrate-relay/src/on_demand_headers.rs
@@ -420,10 +420,10 @@ fn on_demand_headers_relay_name<SourceChain: Chain, TargetChain: Chain>() -> Str
 mod tests {
 	use super::*;
 
-	type TestChain = relay_millau_client::Millau;
+	type TestChain = relay_rococo_client::Rococo;
 
-	const AT_SOURCE: Option<bp_millau::BlockNumber> = Some(10);
-	const AT_TARGET: Option<bp_millau::BlockNumber> = Some(1);
+	const AT_SOURCE: Option<bp_rococo::BlockNumber> = Some(10);
+	const AT_TARGET: Option<bp_rococo::BlockNumber> = Some(1);
 
 	#[async_std::test]
 	async fn mandatory_headers_scan_range_selects_range_if_too_many_headers_are_missing() {
diff --git a/bridges/relays/messages/src/message_lane_loop.rs b/bridges/relays/messages/src/message_lane_loop.rs
index 84b0c72408dfa691548088685e52a787e6a7a4f6..049b6e7b5f799581b472ebd2ff22ba3b41ade726 100644
--- a/bridges/relays/messages/src/message_lane_loop.rs
+++ b/bridges/relays/messages/src/message_lane_loop.rs
@@ -210,6 +210,7 @@ pub trait TargetClient<P: MessageLane>: RelayClient {
 	async fn estimate_delivery_transaction_in_source_tokens(
 		&self,
 		nonces: RangeInclusive<MessageNonce>,
+		total_prepaid_nonces: MessageNonce,
 		total_dispatch_weight: Weight,
 		total_size: u32,
 	) -> Result<P::SourceChainBalance, Self::Error>;
@@ -773,6 +774,7 @@ pub(crate) mod tests {
 		async fn estimate_delivery_transaction_in_source_tokens(
 			&self,
 			nonces: RangeInclusive<MessageNonce>,
+			_total_prepaid_nonces: MessageNonce,
 			total_dispatch_weight: Weight,
 			total_size: u32,
 		) -> Result<TestSourceChainBalance, TestError> {
diff --git a/bridges/relays/messages/src/message_race_delivery.rs b/bridges/relays/messages/src/message_race_delivery.rs
index 2e94bbee8b1c455daee1d2db02a1446f3277ef75..8a5f8d7df260dc7a2d958ce1f5a01859a302a4a5 100644
--- a/bridges/relays/messages/src/message_race_delivery.rs
+++ b/bridges/relays/messages/src/message_race_delivery.rs
@@ -561,6 +561,7 @@ async fn select_nonces_for_delivery_transaction<P: MessageLane>(
 
 	let mut selected_weight: Weight = 0;
 	let mut selected_unpaid_weight: Weight = 0;
+	let mut selected_prepaid_nonces = 0;
 	let mut selected_size: u32 = 0;
 	let mut selected_count: MessageNonce = 0;
 	let mut selected_reward = P::SourceChainBalance::zero();
@@ -570,6 +571,8 @@ async fn select_nonces_for_delivery_transaction<P: MessageLane>(
 	let mut total_confirmations_cost = P::SourceChainBalance::zero();
 	let mut total_cost = P::SourceChainBalance::zero();
 
+	let hard_selected_begin_nonce = nonces_queue[nonces_queue_range.start].1.begin();
+
 	// technically, multiple confirmations will be delivered in a single transaction,
 	// meaning less loses for relayer. But here we don't know the final relayer yet, so
 	// we're adding a separate transaction for every message. Normally, this cost is covered
@@ -637,8 +640,12 @@ async fn select_nonces_for_delivery_transaction<P: MessageLane>(
 		// dispatch origin account AND reward is not covering this fee.
 		//
 		// So in the latter case we're not adding the dispatch weight to the delivery transaction weight.
+		let mut new_selected_prepaid_nonces = selected_prepaid_nonces;
 		let new_selected_unpaid_weight = match details.dispatch_fee_payment {
-			DispatchFeePayment::AtSourceChain => selected_unpaid_weight.saturating_add(details.dispatch_weight),
+			DispatchFeePayment::AtSourceChain => {
+				new_selected_prepaid_nonces += 1;
+				selected_unpaid_weight.saturating_add(details.dispatch_weight)
+			}
 			DispatchFeePayment::AtTargetChain => selected_unpaid_weight,
 		};
 
@@ -651,7 +658,8 @@ async fn select_nonces_for_delivery_transaction<P: MessageLane>(
 			RelayerMode::Rational => {
 				let delivery_transaction_cost = lane_target_client
 					.estimate_delivery_transaction_in_source_tokens(
-						0..=(new_selected_count as MessageNonce - 1),
+						hard_selected_begin_nonce..=(hard_selected_begin_nonce + index as MessageNonce),
+						new_selected_prepaid_nonces,
 						new_selected_unpaid_weight,
 						new_selected_size as u32,
 					)
@@ -711,11 +719,11 @@ async fn select_nonces_for_delivery_transaction<P: MessageLane>(
 		hard_selected_count = index + 1;
 		selected_weight = new_selected_weight;
 		selected_unpaid_weight = new_selected_unpaid_weight;
+		selected_prepaid_nonces = new_selected_prepaid_nonces;
 		selected_size = new_selected_size;
 		selected_count = new_selected_count;
 	}
 
-	let hard_selected_begin_nonce = nonces_queue[nonces_queue_range.start].1.begin();
 	if hard_selected_count != soft_selected_count {
 		let hard_selected_end_nonce = hard_selected_begin_nonce + hard_selected_count as MessageNonce - 1;
 		let soft_selected_begin_nonce = hard_selected_begin_nonce;