From 2db84b74cc035c4d20eb3216763e70d73cb04bef Mon Sep 17 00:00:00 2001
From: Svyatoslav Nikolsky <svyatonik@gmail.com>
Date: Tue, 21 Sep 2021 16:53:37 +0300
Subject: [PATCH] Polkadot <> Kusama relayers (#1122)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* relay headers between Kusama and Polkadot

* relay messages between Kusama and Polkadot

* complex Kusama <> Polkadot relayer

* expose relayer_fund_account_id from messages pallet

* create relayers fund accounts on Kusama/Polkadot + some more fixes

* fmt

* fix compilation

* compilation + clippy

* compilation

* MAXIMAL_BALANCE_DECREASE_PER_DAY for K<>P header relays

* fmt

* deduplicate tests

* Update modules/messages/src/lib.rs

Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com>

* extract storage_parameter_key function

* other grumbles

* fix

* fmt

Co-authored-by: Tomasz Drwięga <tomusdrw@users.noreply.github.com>
---
 bridges/bin/millau/node/src/chain_spec.rs     |   8 +-
 bridges/bin/rialto/node/src/chain_spec.rs     |   8 +-
 bridges/modules/messages/src/benchmarking.rs  |  10 +-
 bridges/modules/messages/src/lib.rs           |  33 +-
 bridges/primitives/chain-kusama/Cargo.toml    |   2 +
 bridges/primitives/chain-kusama/src/lib.rs    |  25 ++
 bridges/primitives/chain-polkadot/Cargo.toml  |   2 +
 bridges/primitives/chain-polkadot/src/lib.rs  |  25 ++
 bridges/primitives/polkadot-core/src/lib.rs   |   5 +-
 bridges/primitives/runtime/src/lib.rs         |  16 +-
 bridges/relays/bin-substrate/Cargo.toml       |   1 +
 .../relays/bin-substrate/src/chains/kusama.rs | 101 ++++++
 .../src/chains/kusama_headers_to_polkadot.rs  | 158 +++++++++
 .../src/chains/kusama_messages_to_polkadot.rs | 304 ++++++++++++++++++
 .../relays/bin-substrate/src/chains/mod.rs    |  10 +-
 .../bin-substrate/src/chains/polkadot.rs      | 101 ++++++
 .../src/chains/polkadot_headers_to_kusama.rs  | 125 +++++++
 .../src/chains/polkadot_messages_to_kusama.rs | 303 +++++++++++++++++
 .../src/chains/wococo_headers_to_rococo.rs    |  33 +-
 .../relays/bin-substrate/src/cli/bridge.rs    |  50 +++
 .../bin-substrate/src/cli/init_bridge.rs      |  30 ++
 .../bin-substrate/src/cli/relay_headers.rs    |  16 +
 .../src/cli/relay_headers_and_messages.rs     | 161 +++++++++-
 bridges/relays/client-kusama/Cargo.toml       |  15 +
 bridges/relays/client-kusama/src/lib.rs       |  70 +++-
 bridges/relays/client-kusama/src/runtime.rs   | 151 +++++++++
 bridges/relays/client-polkadot/Cargo.toml     |  15 +
 bridges/relays/client-polkadot/src/lib.rs     |  70 +++-
 bridges/relays/client-polkadot/src/runtime.rs | 151 +++++++++
 bridges/relays/client-rococo/Cargo.toml       |   2 +
 bridges/relays/client-substrate/src/lib.rs    |   3 +-
 31 files changed, 1936 insertions(+), 68 deletions(-)
 create mode 100644 bridges/relays/bin-substrate/src/chains/kusama.rs
 create mode 100644 bridges/relays/bin-substrate/src/chains/kusama_headers_to_polkadot.rs
 create mode 100644 bridges/relays/bin-substrate/src/chains/kusama_messages_to_polkadot.rs
 create mode 100644 bridges/relays/bin-substrate/src/chains/polkadot.rs
 create mode 100644 bridges/relays/bin-substrate/src/chains/polkadot_headers_to_kusama.rs
 create mode 100644 bridges/relays/bin-substrate/src/chains/polkadot_messages_to_kusama.rs
 create mode 100644 bridges/relays/client-kusama/src/runtime.rs
 create mode 100644 bridges/relays/client-polkadot/src/runtime.rs

diff --git a/bridges/bin/millau/node/src/chain_spec.rs b/bridges/bin/millau/node/src/chain_spec.rs
index 66ce0e23118..28c8be33041 100644
--- a/bridges/bin/millau/node/src/chain_spec.rs
+++ b/bridges/bin/millau/node/src/chain_spec.rs
@@ -135,10 +135,10 @@ impl Alternative {
 							get_account_id_from_seed::<sr25519::Public>("George//stash"),
 							get_account_id_from_seed::<sr25519::Public>("Harry//stash"),
 							get_account_id_from_seed::<sr25519::Public>("RialtoMessagesOwner"),
-							pallet_bridge_messages::Pallet::<
-								millau_runtime::Runtime,
-								millau_runtime::WithRialtoMessagesInstance,
-							>::relayer_fund_account_id(),
+							pallet_bridge_messages::relayer_fund_account_id::<
+								bp_millau::AccountId,
+								bp_millau::AccountIdConverter,
+							>(),
 							derive_account_from_rialto_id(bp_runtime::SourceAccount::Account(
 								get_account_id_from_seed::<sr25519::Public>("Alice"),
 							)),
diff --git a/bridges/bin/rialto/node/src/chain_spec.rs b/bridges/bin/rialto/node/src/chain_spec.rs
index b77b1d6acf6..39443215147 100644
--- a/bridges/bin/rialto/node/src/chain_spec.rs
+++ b/bridges/bin/rialto/node/src/chain_spec.rs
@@ -151,10 +151,10 @@ impl Alternative {
 							get_account_id_from_seed::<sr25519::Public>("George//stash"),
 							get_account_id_from_seed::<sr25519::Public>("Harry//stash"),
 							get_account_id_from_seed::<sr25519::Public>("MillauMessagesOwner"),
-							pallet_bridge_messages::Pallet::<
-								rialto_runtime::Runtime,
-								rialto_runtime::WithMillauMessagesInstance,
-							>::relayer_fund_account_id(),
+							pallet_bridge_messages::relayer_fund_account_id::<
+								bp_rialto::AccountId,
+								bp_rialto::AccountIdConverter,
+							>(),
 							derive_account_from_millau_id(bp_runtime::SourceAccount::Account(
 								get_account_id_from_seed::<sr25519::Public>("Alice"),
 							)),
diff --git a/bridges/modules/messages/src/benchmarking.rs b/bridges/modules/messages/src/benchmarking.rs
index 4adf073191b..d6ec0032449 100644
--- a/bridges/modules/messages/src/benchmarking.rs
+++ b/bridges/modules/messages/src/benchmarking.rs
@@ -484,7 +484,7 @@ benchmarks_instance_pallet! {
 	//
 	// This is base benchmark for all other confirmations delivery benchmarks.
 	receive_delivery_proof_for_single_message {
-		let relayers_fund_id = crate::Pallet::<T, I>::relayer_fund_account_id();
+		let relayers_fund_id = crate::relayer_fund_account_id::<T::AccountId, T::AccountIdConverter>();
 		let relayer_id: T::AccountId = account("relayer", 0, SEED);
 		let relayer_balance = T::account_balance(&relayer_id);
 		T::endow_account(&relayers_fund_id);
@@ -524,7 +524,7 @@ benchmarks_instance_pallet! {
 	// as `weight(receive_delivery_proof_for_two_messages_by_single_relayer)
 	//   - weight(receive_delivery_proof_for_single_message)`.
 	receive_delivery_proof_for_two_messages_by_single_relayer {
-		let relayers_fund_id = crate::Pallet::<T, I>::relayer_fund_account_id();
+		let relayers_fund_id = crate::relayer_fund_account_id::<T::AccountId, T::AccountIdConverter>();
 		let relayer_id: T::AccountId = account("relayer", 0, SEED);
 		let relayer_balance = T::account_balance(&relayer_id);
 		T::endow_account(&relayers_fund_id);
@@ -564,7 +564,7 @@ benchmarks_instance_pallet! {
 	// as `weight(receive_delivery_proof_for_two_messages_by_two_relayers)
 	//   - weight(receive_delivery_proof_for_two_messages_by_single_relayer)`.
 	receive_delivery_proof_for_two_messages_by_two_relayers {
-		let relayers_fund_id = crate::Pallet::<T, I>::relayer_fund_account_id();
+		let relayers_fund_id = crate::relayer_fund_account_id::<T::AccountId, T::AccountIdConverter>();
 		let relayer1_id: T::AccountId = account("relayer1", 1, SEED);
 		let relayer1_balance = T::account_balance(&relayer1_id);
 		let relayer2_id: T::AccountId = account("relayer2", 2, SEED);
@@ -811,7 +811,7 @@ benchmarks_instance_pallet! {
 			.try_into()
 			.expect("Value of MaxUnrewardedRelayerEntriesAtInboundLane is too large");
 
-		let relayers_fund_id = crate::Pallet::<T, I>::relayer_fund_account_id();
+		let relayers_fund_id = crate::relayer_fund_account_id::<T::AccountId, T::AccountIdConverter>();
 		let relayer_id: T::AccountId = account("relayer", 0, SEED);
 		let relayer_balance = T::account_balance(&relayer_id);
 		T::endow_account(&relayers_fund_id);
@@ -854,7 +854,7 @@ benchmarks_instance_pallet! {
 			.try_into()
 			.expect("Value of MaxUnconfirmedMessagesAtInboundLane is too large ");
 
-		let relayers_fund_id = crate::Pallet::<T, I>::relayer_fund_account_id();
+		let relayers_fund_id = crate::relayer_fund_account_id::<T::AccountId, T::AccountIdConverter>();
 		let confirmation_relayer_id = account("relayer", 0, SEED);
 		let relayers: BTreeMap<T::AccountId, T::OutboundMessageFee> = (1..=i)
 			.map(|j| {
diff --git a/bridges/modules/messages/src/lib.rs b/bridges/modules/messages/src/lib.rs
index 6e1ce21a585..208d1d048cf 100644
--- a/bridges/modules/messages/src/lib.rs
+++ b/bridges/modules/messages/src/lib.rs
@@ -64,7 +64,8 @@ use frame_support::{
 };
 use frame_system::RawOrigin;
 use num_traits::{SaturatingAdd, Zero};
-use sp_runtime::traits::BadOrigin;
+use sp_core::H256;
+use sp_runtime::traits::{BadOrigin, Convert};
 use sp_std::{cell::RefCell, cmp::PartialOrd, marker::PhantomData, prelude::*};
 
 mod inbound_lane;
@@ -286,16 +287,17 @@ pub mod pallet {
 			T::MessageDeliveryAndDispatchPayment::pay_delivery_and_dispatch_fee(
 				&submitter,
 				&additional_fee,
-				&Self::relayer_fund_account_id(),
+				&relayer_fund_account_id::<T::AccountId, T::AccountIdConverter>(),
 			)
 			.map_err(|err| {
 				log::trace!(
 					target: "runtime::bridge-messages",
-					"Submitter {:?} can't pay additional fee {:?} for the message {:?}/{:?}: {:?}",
+					"Submitter {:?} can't pay additional fee {:?} for the message {:?}/{:?} to {:?}: {:?}",
 					submitter,
 					additional_fee,
 					lane_id,
 					nonce,
+					relayer_fund_account_id::<T::AccountId, T::AccountIdConverter>(),
 					err,
 				);
 
@@ -604,7 +606,7 @@ pub mod pallet {
 
 			// if some new messages have been confirmed, reward relayers
 			if !relayers_rewards.is_empty() {
-				let relayer_fund_account = Self::relayer_fund_account_id();
+				let relayer_fund_account = relayer_fund_account_id::<T::AccountId, T::AccountIdConverter>();
 				<T as Config<I>>::MessageDeliveryAndDispatchPayment::pay_relayers_rewards(
 					&confirmation_relayer,
 					relayers_rewards,
@@ -768,17 +770,6 @@ pub mod pallet {
 				total_messages: total_unrewarded_messages(&relayers).unwrap_or(MessageNonce::MAX),
 			}
 		}
-
-		/// AccountId of the shared relayer fund account.
-		///
-		/// This account is passed to `MessageDeliveryAndDispatchPayment` trait, and depending
-		/// on the implementation it can be used to store relayers rewards.
-		/// See [InstantCurrencyPayments] for a concrete implementation.
-		pub fn relayer_fund_account_id() -> T::AccountId {
-			use sp_runtime::traits::Convert;
-			let encoded_id = bp_runtime::derive_relayer_fund_account_id(bp_runtime::NO_INSTANCE_ID);
-			T::AccountIdConverter::convert(encoded_id)
-		}
 	}
 }
 
@@ -825,6 +816,16 @@ pub mod storage_keys {
 	}
 }
 
+/// AccountId of the shared relayer fund account.
+///
+/// This account is passed to `MessageDeliveryAndDispatchPayment` trait, and depending
+/// on the implementation it can be used to store relayers rewards.
+/// See [`InstantCurrencyPayments`] for a concrete implementation.
+pub fn relayer_fund_account_id<AccountId, AccountIdConverter: Convert<H256, AccountId>>() -> AccountId {
+	let encoded_id = bp_runtime::derive_relayer_fund_account_id(bp_runtime::NO_INSTANCE_ID);
+	AccountIdConverter::convert(encoded_id)
+}
+
 impl<T, I> bp_messages::source_chain::MessagesBridge<T::AccountId, T::OutboundMessageFee, T::OutboundPayload>
 	for Pallet<T, I>
 where
@@ -894,7 +895,7 @@ fn send_message<T: Config<I>, I: 'static>(
 	T::MessageDeliveryAndDispatchPayment::pay_delivery_and_dispatch_fee(
 		&submitter,
 		&delivery_and_dispatch_fee,
-		&Pallet::<T, I>::relayer_fund_account_id(),
+		&relayer_fund_account_id::<T::AccountId, T::AccountIdConverter>(),
 	)
 	.map_err(|err| {
 		log::trace!(
diff --git a/bridges/primitives/chain-kusama/Cargo.toml b/bridges/primitives/chain-kusama/Cargo.toml
index 4b26a2ba97c..061a89a7d87 100644
--- a/bridges/primitives/chain-kusama/Cargo.toml
+++ b/bridges/primitives/chain-kusama/Cargo.toml
@@ -20,6 +20,7 @@ bp-runtime = { path = "../runtime", default-features = false }
 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 }
+sp-version = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
 
 [features]
 default = ["std"]
@@ -30,4 +31,5 @@ std = [
 	"frame-support/std",
 	"sp-api/std",
 	"sp-std/std",
+	"sp-version/std",
 ]
diff --git a/bridges/primitives/chain-kusama/src/lib.rs b/bridges/primitives/chain-kusama/src/lib.rs
index af93d4108cc..4dec9cd2a0e 100644
--- a/bridges/primitives/chain-kusama/src/lib.rs
+++ b/bridges/primitives/chain-kusama/src/lib.rs
@@ -23,12 +23,24 @@
 use bp_messages::{LaneId, MessageDetails, MessageNonce, UnrewardedRelayersState};
 use frame_support::weights::{WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial};
 use sp_std::prelude::*;
+use sp_version::RuntimeVersion;
 
 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 const VERSION: RuntimeVersion = RuntimeVersion {
+	spec_name: sp_version::create_runtime_str!("kusama"),
+	impl_name: sp_version::create_runtime_str!("parity-kusama"),
+	authoring_version: 2,
+	spec_version: 9100,
+	impl_version: 0,
+	apis: sp_version::create_apis_vec![[]],
+	transaction_version: 5,
+};
+
 // 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 {
@@ -57,9 +69,22 @@ pub fn derive_account_from_polkadot_id(id: bp_runtime::SourceAccount<AccountId>)
 /// Per-byte fee for Kusama transactions.
 pub const TRANSACTION_BYTE_FEE: Balance = 10 * 1_000_000_000_000 / 30_000 / 1_000;
 
+/// Existential deposit on Kusama.
+pub const EXISTENTIAL_DEPOSIT: Balance = 1_000_000_000_000 / 30_000;
+
+/// The target length of a session (how often authorities change) on Kusama measured in of number of
+/// blocks.
+///
+/// Note that since this is a target sessions may change before/after this time depending on network
+/// conditions.
+pub const SESSION_LENGTH: BlockNumber = 1 * time_units::HOURS;
+
 /// Name of the With-Polkadot messages pallet instance in the Kusama runtime.
 pub const WITH_POLKADOT_MESSAGES_PALLET_NAME: &str = "BridgePolkadotMessages";
 
+/// Name of the DOT->KSM conversion rate stored in the Kusama runtime.
+pub const POLKADOT_TO_KUSAMA_CONVERSION_RATE_PARAMETER_NAME: &str = "PolkadotToKusamaConversionRate";
+
 /// Name of the `KusamaFinalityApi::best_finalized` runtime method.
 pub const BEST_FINALIZED_KUSAMA_HEADER_METHOD: &str = "KusamaFinalityApi_best_finalized";
 /// Name of the `KusamaFinalityApi::is_known_header` runtime method.
diff --git a/bridges/primitives/chain-polkadot/Cargo.toml b/bridges/primitives/chain-polkadot/Cargo.toml
index 4877c6d5eaf..181b230d61d 100644
--- a/bridges/primitives/chain-polkadot/Cargo.toml
+++ b/bridges/primitives/chain-polkadot/Cargo.toml
@@ -20,6 +20,7 @@ bp-runtime = { path = "../runtime", default-features = false }
 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 }
+sp-version = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false }
 
 [features]
 default = ["std"]
@@ -30,4 +31,5 @@ std = [
 	"frame-support/std",
 	"sp-api/std",
 	"sp-std/std",
+	"sp-version/std",
 ]
diff --git a/bridges/primitives/chain-polkadot/src/lib.rs b/bridges/primitives/chain-polkadot/src/lib.rs
index 938f3fec8d1..d32165e6b79 100644
--- a/bridges/primitives/chain-polkadot/src/lib.rs
+++ b/bridges/primitives/chain-polkadot/src/lib.rs
@@ -23,12 +23,24 @@
 use bp_messages::{LaneId, MessageDetails, MessageNonce, UnrewardedRelayersState};
 use frame_support::weights::{WeightToFeeCoefficient, WeightToFeeCoefficients, WeightToFeePolynomial};
 use sp_std::prelude::*;
+use sp_version::RuntimeVersion;
 
 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 const VERSION: RuntimeVersion = RuntimeVersion {
+	spec_name: sp_version::create_runtime_str!("polkadot"),
+	impl_name: sp_version::create_runtime_str!("parity-polkadot"),
+	authoring_version: 0,
+	spec_version: 9100,
+	impl_version: 0,
+	apis: sp_version::create_apis_vec![[]],
+	transaction_version: 7,
+};
+
 // 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 {
@@ -57,9 +69,22 @@ pub fn derive_account_from_kusama_id(id: bp_runtime::SourceAccount<AccountId>) -
 /// Per-byte fee for Polkadot transactions.
 pub const TRANSACTION_BYTE_FEE: Balance = 10 * 10_000_000_000 / 100 / 1_000;
 
+/// Existential deposit on Polkadot.
+pub const EXISTENTIAL_DEPOSIT: Balance = 10_000_000_000;
+
+/// The target length of a session (how often authorities change) on Polkadot measured in of number of
+/// blocks.
+///
+/// Note that since this is a target sessions may change before/after this time depending on network
+/// conditions.
+pub const SESSION_LENGTH: BlockNumber = 4 * time_units::HOURS;
+
 /// Name of the With-Kusama messages pallet instance in the Polkadot runtime.
 pub const WITH_KUSAMA_MESSAGES_PALLET_NAME: &str = "BridgeKusamaMessages";
 
+/// Name of the KSM->DOT conversion rate stored in the Polkadot runtime.
+pub const KUSAMA_TO_POLKADOT_CONVERSION_RATE_PARAMETER_NAME: &str = "KusamaToPolkadotConversionRate";
+
 /// Name of the `PolkadotFinalityApi::best_finalized` runtime method.
 pub const BEST_FINALIZED_POLKADOT_HEADER_METHOD: &str = "PolkadotFinalityApi_best_finalized";
 /// Name of the `PolkadotFinalityApi::is_known_header` runtime method.
diff --git a/bridges/primitives/polkadot-core/src/lib.rs b/bridges/primitives/polkadot-core/src/lib.rs
index 9e8726734ee..a36f7ae6524 100644
--- a/bridges/primitives/polkadot-core/src/lib.rs
+++ b/bridges/primitives/polkadot-core/src/lib.rs
@@ -217,6 +217,9 @@ pub type AccountPublic = <Signature as Verify>::Signer;
 /// Id of account on Polkadot-like chains.
 pub type AccountId = <AccountPublic as IdentifyAccount>::AccountId;
 
+/// Address of account on Polkadot-like chains.
+pub type AccountAddress = MultiAddress<AccountId, ()>;
+
 /// Index of a transaction on the Polkadot-like chains.
 pub type Nonce = u32;
 
@@ -231,7 +234,7 @@ pub type Balance = u128;
 
 /// Unchecked Extrinsic type.
 pub type UncheckedExtrinsic<Call> =
-	generic::UncheckedExtrinsic<MultiAddress<AccountId, ()>, Call, Signature, SignedExtensions<Call>>;
+	generic::UncheckedExtrinsic<AccountAddress, Call, Signature, SignedExtensions<Call>>;
 
 /// Account address, used by the Polkadot-like chain.
 pub type Address = MultiAddress<AccountId, ()>;
diff --git a/bridges/primitives/runtime/src/lib.rs b/bridges/primitives/runtime/src/lib.rs
index 4277af5e461..a439dbf9ff7 100644
--- a/bridges/primitives/runtime/src/lib.rs
+++ b/bridges/primitives/runtime/src/lib.rs
@@ -20,9 +20,9 @@
 
 use codec::Encode;
 use frame_support::RuntimeDebug;
-use sp_core::hash::H256;
+use sp_core::{hash::H256, storage::StorageKey};
 use sp_io::hashing::blake2_256;
-use sp_std::convert::TryFrom;
+use sp_std::{convert::TryFrom, vec::Vec};
 
 pub use chain::{
 	AccountIdOf, AccountPublicOf, BalanceOf, BlockNumberOf, Chain, HashOf, HasherOf, HeaderOf, IndexOf, SignatureOf,
@@ -183,3 +183,15 @@ impl<BlockNumber: Copy + Into<u64>, BlockHash: Copy> TransactionEra<BlockNumber,
 		}
 	}
 }
+
+/// This is how a storage key of storage parameter (`parameter_types! { storage Param: bool = false; }`) is computed.
+///
+/// Copypaste from `frame_support::parameter_types` macro
+pub fn storage_parameter_key(parameter_name: &str) -> StorageKey {
+	let mut buffer = Vec::with_capacity(1 + parameter_name.len() + 1 + 1);
+	buffer.push(':' as u8);
+	buffer.extend_from_slice(parameter_name.as_bytes());
+	buffer.push(':' as u8);
+	buffer.push(0);
+	StorageKey(sp_io::hashing::twox_128(&buffer).to_vec())
+}
diff --git a/bridges/relays/bin-substrate/Cargo.toml b/bridges/relays/bin-substrate/Cargo.toml
index 86898213513..3a7f63d8489 100644
--- a/bridges/relays/bin-substrate/Cargo.toml
+++ b/bridges/relays/bin-substrate/Cargo.toml
@@ -52,6 +52,7 @@ substrate-relay-helper = { path = "../lib-substrate-relay" }
 
 frame-support = { 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" }
 sp-version = { git = "https://github.com/paritytech/substrate", branch = "master" }
 
diff --git a/bridges/relays/bin-substrate/src/chains/kusama.rs b/bridges/relays/bin-substrate/src/chains/kusama.rs
new file mode 100644
index 00000000000..6c771131662
--- /dev/null
+++ b/bridges/relays/bin-substrate/src/chains/kusama.rs
@@ -0,0 +1,101 @@
+// 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/>.
+
+use codec::Decode;
+use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight};
+use relay_kusama_client::Kusama;
+use sp_version::RuntimeVersion;
+
+use crate::cli::{
+	bridge,
+	encode_call::{Call, CliEncodeCall},
+	encode_message, CliChain,
+};
+
+/// Weight of the `system::remark` call at Kusama.
+///
+/// This weight is larger (x2) than actual weight at current Kusama runtime to avoid unsuccessful
+/// calls in the future. But since it is used only in tests (and on test chains), this is ok.
+pub(crate) const SYSTEM_REMARK_CALL_WEIGHT: Weight = 2 * 1_345_000;
+
+/// Id of Kusama token that is used to fetch token price.
+pub(crate) const TOKEN_ID: &str = "kusama";
+
+impl CliEncodeCall for Kusama {
+	fn max_extrinsic_size() -> u32 {
+		bp_kusama::max_extrinsic_size()
+	}
+
+	fn encode_call(call: &Call) -> anyhow::Result<Self::Call> {
+		Ok(match call {
+			Call::Remark { remark_payload, .. } => {
+				relay_kusama_client::runtime::Call::System(relay_kusama_client::runtime::SystemCall::remark(
+					remark_payload.as_ref().map(|x| x.0.clone()).unwrap_or_default(),
+				))
+			}
+			Call::BridgeSendMessage {
+				lane,
+				payload,
+				fee,
+				bridge_instance_index,
+			} => match *bridge_instance_index {
+				bridge::KUSAMA_TO_POLKADOT_INDEX => {
+					let payload = Decode::decode(&mut &*payload.0)?;
+					relay_kusama_client::runtime::Call::BridgePolkadotMessages(
+						relay_kusama_client::runtime::BridgePolkadotMessagesCall::send_message(lane.0, payload, fee.0),
+					)
+				}
+				_ => anyhow::bail!(
+					"Unsupported target bridge pallet with instance index: {}",
+					bridge_instance_index
+				),
+			},
+			_ => anyhow::bail!("Unsupported Kusama call: {:?}", call),
+		})
+	}
+
+	fn get_dispatch_info(call: &relay_kusama_client::runtime::Call) -> anyhow::Result<DispatchInfo> {
+		match *call {
+			relay_kusama_client::runtime::Call::System(relay_kusama_client::runtime::SystemCall::remark(_)) => {
+				Ok(DispatchInfo {
+					weight: crate::chains::kusama::SYSTEM_REMARK_CALL_WEIGHT,
+					class: DispatchClass::Normal,
+					pays_fee: Pays::Yes,
+				})
+			}
+			_ => anyhow::bail!("Unsupported Kusama call: {:?}", call),
+		}
+	}
+}
+
+impl CliChain for Kusama {
+	const RUNTIME_VERSION: RuntimeVersion = bp_kusama::VERSION;
+
+	type KeyPair = sp_core::sr25519::Pair;
+	type MessagePayload = ();
+
+	fn ss58_format() -> u16 {
+		42
+	}
+
+	fn max_extrinsic_weight() -> Weight {
+		bp_kusama::max_extrinsic_weight()
+	}
+
+	fn encode_message(_message: encode_message::MessagePayload) -> Result<Self::MessagePayload, String> {
+		Err("Sending messages from Kusama is not yet supported.".into())
+	}
+}
diff --git a/bridges/relays/bin-substrate/src/chains/kusama_headers_to_polkadot.rs b/bridges/relays/bin-substrate/src/chains/kusama_headers_to_polkadot.rs
new file mode 100644
index 00000000000..4e7703529e3
--- /dev/null
+++ b/bridges/relays/bin-substrate/src/chains/kusama_headers_to_polkadot.rs
@@ -0,0 +1,158 @@
+// 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/>.
+
+//! Kusama-to-Polkadot headers sync entrypoint.
+
+use codec::Encode;
+use sp_core::{Bytes, Pair};
+
+use bp_header_chain::justification::GrandpaJustification;
+use relay_kusama_client::{Kusama, SyncHeader as KusamaSyncHeader};
+use relay_polkadot_client::{Polkadot, SigningParams as PolkadotSigningParams};
+use relay_substrate_client::{Client, TransactionSignScheme, UnsignedTransaction};
+use relay_utils::metrics::MetricsParams;
+use substrate_relay_helper::finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate};
+
+/// Maximal saturating difference between `balance(now)` and `balance(now-24h)` to treat
+/// relay as gone wild.
+///
+/// Actual value, returned by `maximal_balance_decrease_per_day_is_sane` test is approximately 21 DOT,
+/// but let's round up to 30 DOT here.
+pub(crate) const MAXIMAL_BALANCE_DECREASE_PER_DAY: bp_polkadot::Balance = 30_000_000_000;
+
+/// Kusama-to-Polkadot finality sync pipeline.
+pub(crate) type FinalityPipelineKusamaFinalityToPolkadot =
+	SubstrateFinalityToSubstrate<Kusama, Polkadot, PolkadotSigningParams>;
+
+#[derive(Clone, Debug)]
+pub(crate) struct KusamaFinalityToPolkadot {
+	finality_pipeline: FinalityPipelineKusamaFinalityToPolkadot,
+}
+
+impl KusamaFinalityToPolkadot {
+	pub fn new(target_client: Client<Polkadot>, target_sign: PolkadotSigningParams) -> Self {
+		Self {
+			finality_pipeline: FinalityPipelineKusamaFinalityToPolkadot::new(target_client, target_sign),
+		}
+	}
+}
+
+impl SubstrateFinalitySyncPipeline for KusamaFinalityToPolkadot {
+	type FinalitySyncPipeline = FinalityPipelineKusamaFinalityToPolkadot;
+
+	const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_kusama::BEST_FINALIZED_KUSAMA_HEADER_METHOD;
+
+	type TargetChain = Polkadot;
+
+	fn customize_metrics(params: MetricsParams) -> anyhow::Result<MetricsParams> {
+		crate::chains::add_polkadot_kusama_price_metrics::<Self::FinalitySyncPipeline>(
+			Some(finality_relay::metrics_prefix::<Self::FinalitySyncPipeline>()),
+			params,
+		)
+	}
+
+	fn start_relay_guards(&self) {
+		relay_substrate_client::guard::abort_on_spec_version_change(
+			self.finality_pipeline.target_client.clone(),
+			bp_polkadot::VERSION.spec_version,
+		);
+		relay_substrate_client::guard::abort_when_account_balance_decreased(
+			self.finality_pipeline.target_client.clone(),
+			self.transactions_author(),
+			MAXIMAL_BALANCE_DECREASE_PER_DAY,
+		);
+	}
+
+	fn transactions_author(&self) -> bp_polkadot::AccountId {
+		(*self.finality_pipeline.target_sign.public().as_array_ref()).into()
+	}
+
+	fn make_submit_finality_proof_transaction(
+		&self,
+		era: bp_runtime::TransactionEraOf<Polkadot>,
+		transaction_nonce: bp_runtime::IndexOf<Polkadot>,
+		header: KusamaSyncHeader,
+		proof: GrandpaJustification<bp_kusama::Header>,
+	) -> Bytes {
+		let call = relay_polkadot_client::runtime::Call::BridgeKusamaGrandpa(
+			relay_polkadot_client::runtime::BridgeKusamaGrandpaCall::submit_finality_proof(header.into_inner(), proof),
+		);
+		let genesis_hash = *self.finality_pipeline.target_client.genesis_hash();
+		let transaction = Polkadot::sign_transaction(
+			genesis_hash,
+			&self.finality_pipeline.target_sign,
+			era,
+			UnsignedTransaction::new(call, transaction_nonce),
+		);
+
+		Bytes(transaction.encode())
+	}
+}
+
+#[cfg(test)]
+pub(crate) mod tests {
+	use super::*;
+	use frame_support::weights::WeightToFeePolynomial;
+	use pallet_bridge_grandpa::weights::WeightInfo;
+
+	pub fn compute_maximal_balance_decrease_per_day<B, W>(expected_source_headers_per_day: u32) -> B
+	where
+		B: From<u32> + std::ops::Mul<Output = B>,
+		W: WeightToFeePolynomial<Balance = B>,
+	{
+		// we assume that the GRANDPA is not lagging here => ancestry length will be near to 0 (let's round up to 2)
+		const AVG_VOTES_ANCESTRIES_LEN: u32 = 2;
+		// let's assume number of validators is 1024 (more than on any existing well-known chain atm)
+		// => number of precommits is *2/3 + 1
+		const AVG_PRECOMMITS_LEN: u32 = 1024 * 2 / 3 + 1;
+
+		// GRANDPA pallet weights. We're now using Rialto weights everywhere.
+		//
+		// Using Rialto runtime is slightly incorrect, because `DbWeight` of other runtimes may differ
+		// from the `DbWeight` of Rialto runtime. But now (and most probably forever) it is the same.
+		type GrandpaPalletWeights = pallet_bridge_grandpa::weights::RialtoWeight<rialto_runtime::Runtime>;
+
+		// The following formula shall not be treated as super-accurate - guard is to protect from mad relays,
+		// not to protect from over-average loses.
+
+		// increase number of headers a bit
+		let expected_source_headers_per_day = expected_source_headers_per_day * 110 / 100;
+		let single_source_header_submit_call_weight =
+			GrandpaPalletWeights::submit_finality_proof(AVG_VOTES_ANCESTRIES_LEN, AVG_PRECOMMITS_LEN);
+		// for simplicity - add extra weight for base tx fee + fee that is paid for the tx size + adjusted fee
+		let single_source_header_submit_tx_weight = single_source_header_submit_call_weight * 3 / 2;
+		let single_source_header_tx_cost = W::calc(&single_source_header_submit_tx_weight);
+		let maximal_expected_decrease = single_source_header_tx_cost * B::from(expected_source_headers_per_day);
+
+		maximal_expected_decrease
+	}
+
+	#[test]
+	fn maximal_balance_decrease_per_day_is_sane() {
+		// we expect Kusama -> Polkadot relay to be running in mandatory-headers-only mode
+		// => we expect single header for every Kusama session
+		let maximal_balance_decrease = compute_maximal_balance_decrease_per_day::<
+			bp_polkadot::Balance,
+			bp_polkadot::WeightToFee,
+		>(bp_kusama::DAYS / bp_kusama::SESSION_LENGTH + 1);
+		assert!(
+			MAXIMAL_BALANCE_DECREASE_PER_DAY >= maximal_balance_decrease,
+			"Maximal expected loss per day {} is larger than hardcoded {}",
+			maximal_balance_decrease,
+			MAXIMAL_BALANCE_DECREASE_PER_DAY,
+		);
+	}
+}
diff --git a/bridges/relays/bin-substrate/src/chains/kusama_messages_to_polkadot.rs b/bridges/relays/bin-substrate/src/chains/kusama_messages_to_polkadot.rs
new file mode 100644
index 00000000000..5b07e6d588a
--- /dev/null
+++ b/bridges/relays/bin-substrate/src/chains/kusama_messages_to_polkadot.rs
@@ -0,0 +1,304 @@
+// 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/>.
+
+//! Kusama-to-Polkadot messages sync entrypoint.
+
+use std::{ops::RangeInclusive, time::Duration};
+
+use codec::Encode;
+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_kusama_client::{HeaderId as KusamaHeaderId, Kusama, SigningParams as KusamaSigningParams};
+use relay_polkadot_client::{HeaderId as PolkadotHeaderId, Polkadot, SigningParams as PolkadotSigningParams};
+use relay_substrate_client::{Chain, Client, TransactionSignScheme, UnsignedTransaction};
+use relay_utils::metrics::MetricsParams;
+use sp_runtime::{FixedPointNumber, FixedU128};
+use substrate_relay_helper::messages_lane::{
+	select_delivery_transaction_limits, MessagesRelayParams, StandaloneMessagesMetrics, SubstrateMessageLane,
+	SubstrateMessageLaneToSubstrate,
+};
+use substrate_relay_helper::messages_source::SubstrateMessagesSource;
+use substrate_relay_helper::messages_target::SubstrateMessagesTarget;
+
+/// Kusama-to-Polkadot message lane.
+pub type MessageLaneKusamaMessagesToPolkadot =
+	SubstrateMessageLaneToSubstrate<Kusama, KusamaSigningParams, Polkadot, PolkadotSigningParams>;
+
+#[derive(Clone)]
+pub struct KusamaMessagesToPolkadot {
+	message_lane: MessageLaneKusamaMessagesToPolkadot,
+}
+
+impl SubstrateMessageLane for KusamaMessagesToPolkadot {
+	type MessageLane = MessageLaneKusamaMessagesToPolkadot;
+
+	const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str = bp_polkadot::TO_POLKADOT_MESSAGE_DETAILS_METHOD;
+	const OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD: &'static str =
+		bp_polkadot::TO_POLKADOT_LATEST_GENERATED_NONCE_METHOD;
+	const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
+		bp_polkadot::TO_POLKADOT_LATEST_RECEIVED_NONCE_METHOD;
+
+	const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str = bp_kusama::FROM_KUSAMA_LATEST_RECEIVED_NONCE_METHOD;
+	const INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD: &'static str =
+		bp_kusama::FROM_KUSAMA_LATEST_CONFIRMED_NONCE_METHOD;
+	const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str = bp_kusama::FROM_KUSAMA_UNREWARDED_RELAYERS_STATE;
+
+	const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_kusama::BEST_FINALIZED_KUSAMA_HEADER_METHOD;
+	const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str = bp_polkadot::BEST_FINALIZED_POLKADOT_HEADER_METHOD;
+
+	const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str = bp_kusama::WITH_POLKADOT_MESSAGES_PALLET_NAME;
+	const MESSAGE_PALLET_NAME_AT_TARGET: &'static str = bp_polkadot::WITH_KUSAMA_MESSAGES_PALLET_NAME;
+
+	const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight = bp_polkadot::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
+
+	type SourceChain = Kusama;
+	type TargetChain = Polkadot;
+
+	fn source_transactions_author(&self) -> bp_kusama::AccountId {
+		(*self.message_lane.source_sign.public().as_array_ref()).into()
+	}
+
+	fn make_messages_receiving_proof_transaction(
+		&self,
+		transaction_nonce: bp_runtime::IndexOf<Kusama>,
+		_generated_at_block: PolkadotHeaderId,
+		proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
+	) -> Bytes {
+		let (relayers_state, proof) = proof;
+		let call = relay_kusama_client::runtime::Call::BridgePolkadotMessages(
+			relay_kusama_client::runtime::BridgePolkadotMessagesCall::receive_messages_delivery_proof(
+				proof,
+				relayers_state,
+			),
+		);
+		let genesis_hash = *self.message_lane.source_client.genesis_hash();
+		let transaction = Kusama::sign_transaction(
+			genesis_hash,
+			&self.message_lane.source_sign,
+			relay_substrate_client::TransactionEra::immortal(),
+			UnsignedTransaction::new(call, transaction_nonce),
+		);
+		log::trace!(
+			target: "bridge",
+			"Prepared Polkadot -> Kusama confirmation transaction. Weight: <unknown>/{}, size: {}/{}",
+			bp_kusama::max_extrinsic_weight(),
+			transaction.encode().len(),
+			bp_kusama::max_extrinsic_size(),
+		);
+		Bytes(transaction.encode())
+	}
+
+	fn target_transactions_author(&self) -> bp_polkadot::AccountId {
+		(*self.message_lane.target_sign.public().as_array_ref()).into()
+	}
+
+	fn make_messages_delivery_transaction(
+		&self,
+		transaction_nonce: bp_runtime::IndexOf<Polkadot>,
+		_generated_at_header: KusamaHeaderId,
+		_nonces: RangeInclusive<MessageNonce>,
+		proof: <Self::MessageLane as MessageLane>::MessagesProof,
+	) -> Bytes {
+		let (dispatch_weight, proof) = proof;
+		let FromBridgedChainMessagesProof {
+			ref nonces_start,
+			ref nonces_end,
+			..
+		} = proof;
+		let messages_count = nonces_end - nonces_start + 1;
+
+		let call = relay_polkadot_client::runtime::Call::BridgeKusamaMessages(
+			relay_polkadot_client::runtime::BridgeKusamaMessagesCall::receive_messages_proof(
+				self.message_lane.relayer_id_at_source.clone(),
+				proof,
+				messages_count as _,
+				dispatch_weight,
+			),
+		);
+		let genesis_hash = *self.message_lane.target_client.genesis_hash();
+		let transaction = Polkadot::sign_transaction(
+			genesis_hash,
+			&self.message_lane.target_sign,
+			relay_substrate_client::TransactionEra::immortal(),
+			UnsignedTransaction::new(call, transaction_nonce),
+		);
+		log::trace!(
+			target: "bridge",
+			"Prepared Kusama -> Polkadot delivery transaction. Weight: <unknown>/{}, size: {}/{}",
+			bp_polkadot::max_extrinsic_weight(),
+			transaction.encode().len(),
+			bp_polkadot::max_extrinsic_size(),
+		);
+		Bytes(transaction.encode())
+	}
+}
+
+/// Kusama node as messages source.
+type KusamaSourceClient = SubstrateMessagesSource<KusamaMessagesToPolkadot>;
+
+/// Polkadot node as messages target.
+type PolkadotTargetClient = SubstrateMessagesTarget<KusamaMessagesToPolkadot>;
+
+/// Run Kusama-to-Polkadot messages sync.
+pub async fn run(
+	params: MessagesRelayParams<Kusama, KusamaSigningParams, Polkadot, PolkadotSigningParams>,
+) -> anyhow::Result<()> {
+	let stall_timeout = Duration::from_secs(5 * 60);
+	let relayer_id_at_kusama = (*params.source_sign.public().as_array_ref()).into();
+
+	let lane_id = params.lane_id;
+	let source_client = params.source_client;
+	let lane = KusamaMessagesToPolkadot {
+		message_lane: SubstrateMessageLaneToSubstrate {
+			source_client: source_client.clone(),
+			source_sign: params.source_sign,
+			target_client: params.target_client.clone(),
+			target_sign: params.target_sign,
+			relayer_id_at_source: relayer_id_at_kusama,
+		},
+	};
+
+	// 2/3 is reserved for proofs and tx overhead
+	let max_messages_size_in_single_batch = bp_polkadot::max_extrinsic_size() / 3;
+	// we don't know exact weights of the Polkadot runtime. So to guess weights we'll be using
+	// weights from Rialto and then simply dividing it by x2.
+	let (max_messages_in_single_batch, max_messages_weight_in_single_batch) =
+		select_delivery_transaction_limits::<pallet_bridge_messages::weights::RialtoWeight<rialto_runtime::Runtime>>(
+			bp_polkadot::max_extrinsic_weight(),
+			bp_polkadot::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
+		);
+	let (max_messages_in_single_batch, max_messages_weight_in_single_batch) = (
+		max_messages_in_single_batch / 2,
+		max_messages_weight_in_single_batch / 2,
+	);
+
+	log::info!(
+		target: "bridge",
+		"Starting Kusama -> Polkadot messages relay.\n\t\
+			Kusama relayer account id: {:?}\n\t\
+			Max messages in single transaction: {}\n\t\
+			Max messages size in single transaction: {}\n\t\
+			Max messages weight in single transaction: {}\n\t\
+			Relayer mode: {:?}",
+		lane.message_lane.relayer_id_at_source,
+		max_messages_in_single_batch,
+		max_messages_size_in_single_batch,
+		max_messages_weight_in_single_batch,
+		params.relayer_mode,
+	);
+
+	let (metrics_params, metrics_values) = add_standalone_metrics(
+		Some(messages_relay::message_lane_loop::metrics_prefix::<
+			<KusamaMessagesToPolkadot as SubstrateMessageLane>::MessageLane,
+		>(&lane_id)),
+		params.metrics_params,
+		source_client.clone(),
+	)?;
+	messages_relay::message_lane_loop::run(
+		messages_relay::message_lane_loop::Params {
+			lane: lane_id,
+			source_tick: Kusama::AVERAGE_BLOCK_INTERVAL,
+			target_tick: Polkadot::AVERAGE_BLOCK_INTERVAL,
+			reconnect_delay: relay_utils::relay_loop::RECONNECT_DELAY,
+			stall_timeout,
+			delivery_params: messages_relay::message_lane_loop::MessageDeliveryParams {
+				max_unrewarded_relayer_entries_at_target: bp_polkadot::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
+				max_unconfirmed_nonces_at_target: bp_polkadot::MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE,
+				max_messages_in_single_batch,
+				max_messages_weight_in_single_batch,
+				max_messages_size_in_single_batch,
+				relayer_mode: params.relayer_mode,
+			},
+		},
+		KusamaSourceClient::new(
+			source_client.clone(),
+			lane.clone(),
+			lane_id,
+			params.target_to_source_headers_relay,
+		),
+		PolkadotTargetClient::new(
+			params.target_client,
+			lane,
+			lane_id,
+			metrics_values,
+			params.source_to_target_headers_relay,
+		),
+		metrics_params,
+		futures::future::pending(),
+	)
+	.await
+}
+
+/// Add standalone metrics for the Kusama -> Polkadot messages loop.
+pub(crate) fn add_standalone_metrics(
+	metrics_prefix: Option<String>,
+	metrics_params: MetricsParams,
+	source_client: Client<Kusama>,
+) -> anyhow::Result<(MetricsParams, StandaloneMessagesMetrics)> {
+	let polkadot_to_kusama_conversion_rate_key =
+		bp_runtime::storage_parameter_key(bp_kusama::POLKADOT_TO_KUSAMA_CONVERSION_RATE_PARAMETER_NAME).0;
+
+	substrate_relay_helper::messages_lane::add_standalone_metrics::<KusamaMessagesToPolkadot>(
+		metrics_prefix,
+		metrics_params,
+		source_client,
+		Some(crate::chains::polkadot::TOKEN_ID),
+		Some(crate::chains::kusama::TOKEN_ID),
+		Some((
+			sp_core::storage::StorageKey(polkadot_to_kusama_conversion_rate_key),
+			// starting relay before this parameter will be set to some value may cause troubles
+			FixedU128::from_inner(FixedU128::DIV),
+		)),
+	)
+}
+
+/// Update Polkadot -> Kusama conversion rate, stored in Kusama runtime storage.
+pub(crate) async fn update_polkadot_to_kusama_conversion_rate(
+	client: Client<Kusama>,
+	signer: <Kusama as TransactionSignScheme>::AccountKeyPair,
+	updated_rate: f64,
+) -> anyhow::Result<()> {
+	let genesis_hash = *client.genesis_hash();
+	let signer_id = (*signer.public().as_array_ref()).into();
+	client
+		.submit_signed_extrinsic(signer_id, move |_, transaction_nonce| {
+			Bytes(
+				Kusama::sign_transaction(
+					genesis_hash,
+					&signer,
+					relay_substrate_client::TransactionEra::immortal(),
+					UnsignedTransaction::new(
+						relay_kusama_client::runtime::Call::BridgePolkadotMessages(
+							relay_kusama_client::runtime::BridgePolkadotMessagesCall::update_pallet_parameter(
+								relay_kusama_client::runtime::BridgePolkadotMessagesParameter::PolkadotToKusamaConversionRate(
+									sp_runtime::FixedU128::from_float(updated_rate),
+								)
+							)
+						),
+						transaction_nonce,
+					),
+				)
+				.encode(),
+			)
+		})
+		.await
+		.map(drop)
+		.map_err(|err| anyhow::format_err!("{:?}", err))
+}
diff --git a/bridges/relays/bin-substrate/src/chains/mod.rs b/bridges/relays/bin-substrate/src/chains/mod.rs
index 6f8273a7fcb..4c02e9b1c98 100644
--- a/bridges/relays/bin-substrate/src/chains/mod.rs
+++ b/bridges/relays/bin-substrate/src/chains/mod.rs
@@ -16,8 +16,12 @@
 
 //! Chain-specific relayer configuration.
 
+pub mod kusama_headers_to_polkadot;
+pub mod kusama_messages_to_polkadot;
 pub mod millau_headers_to_rialto;
 pub mod millau_messages_to_rialto;
+pub mod polkadot_headers_to_kusama;
+pub mod polkadot_messages_to_kusama;
 pub mod rialto_headers_to_millau;
 pub mod rialto_messages_to_millau;
 pub mod rococo_headers_to_wococo;
@@ -26,7 +30,9 @@ pub mod westend_headers_to_millau;
 pub mod wococo_headers_to_rococo;
 pub mod wococo_messages_to_rococo;
 
+mod kusama;
 mod millau;
+mod polkadot;
 mod rialto;
 mod rococo;
 mod westend;
@@ -37,9 +43,9 @@ mod wococo;
 // Rialto as BTC and Millau as wBTC (only in relayer).
 
 /// The identifier of token, which value is associated with Rialto token value by relayer.
-pub(crate) const RIALTO_ASSOCIATED_TOKEN_ID: &str = "polkadot";
+pub(crate) const RIALTO_ASSOCIATED_TOKEN_ID: &str = polkadot::TOKEN_ID;
 /// The identifier of token, which value is associated with Millau token value by relayer.
-pub(crate) const MILLAU_ASSOCIATED_TOKEN_ID: &str = "kusama";
+pub(crate) const MILLAU_ASSOCIATED_TOKEN_ID: &str = kusama::TOKEN_ID;
 
 use relay_utils::metrics::MetricsParams;
 
diff --git a/bridges/relays/bin-substrate/src/chains/polkadot.rs b/bridges/relays/bin-substrate/src/chains/polkadot.rs
new file mode 100644
index 00000000000..372bdb90efc
--- /dev/null
+++ b/bridges/relays/bin-substrate/src/chains/polkadot.rs
@@ -0,0 +1,101 @@
+// 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/>.
+
+use codec::Decode;
+use frame_support::weights::{DispatchClass, DispatchInfo, Pays, Weight};
+use relay_polkadot_client::Polkadot;
+use sp_version::RuntimeVersion;
+
+use crate::cli::{
+	bridge,
+	encode_call::{Call, CliEncodeCall},
+	encode_message, CliChain,
+};
+
+/// Weight of the `system::remark` call at Polkadot.
+///
+/// This weight is larger (x2) than actual weight at current Polkadot runtime to avoid unsuccessful
+/// calls in the future. But since it is used only in tests (and on test chains), this is ok.
+pub(crate) const SYSTEM_REMARK_CALL_WEIGHT: Weight = 2 * 1_345_000;
+
+/// Id of Polkadot token that is used to fetch token price.
+pub(crate) const TOKEN_ID: &str = "polkadot";
+
+impl CliEncodeCall for Polkadot {
+	fn max_extrinsic_size() -> u32 {
+		bp_polkadot::max_extrinsic_size()
+	}
+
+	fn encode_call(call: &Call) -> anyhow::Result<Self::Call> {
+		Ok(match call {
+			Call::Remark { remark_payload, .. } => {
+				relay_polkadot_client::runtime::Call::System(relay_polkadot_client::runtime::SystemCall::remark(
+					remark_payload.as_ref().map(|x| x.0.clone()).unwrap_or_default(),
+				))
+			}
+			Call::BridgeSendMessage {
+				lane,
+				payload,
+				fee,
+				bridge_instance_index,
+			} => match *bridge_instance_index {
+				bridge::POLKADOT_TO_KUSAMA_INDEX => {
+					let payload = Decode::decode(&mut &*payload.0)?;
+					relay_polkadot_client::runtime::Call::BridgeKusamaMessages(
+						relay_polkadot_client::runtime::BridgeKusamaMessagesCall::send_message(lane.0, payload, fee.0),
+					)
+				}
+				_ => anyhow::bail!(
+					"Unsupported target bridge pallet with instance index: {}",
+					bridge_instance_index
+				),
+			},
+			_ => anyhow::bail!("Unsupported Polkadot call: {:?}", call),
+		})
+	}
+
+	fn get_dispatch_info(call: &relay_polkadot_client::runtime::Call) -> anyhow::Result<DispatchInfo> {
+		match *call {
+			relay_polkadot_client::runtime::Call::System(relay_polkadot_client::runtime::SystemCall::remark(_)) => {
+				Ok(DispatchInfo {
+					weight: crate::chains::polkadot::SYSTEM_REMARK_CALL_WEIGHT,
+					class: DispatchClass::Normal,
+					pays_fee: Pays::Yes,
+				})
+			}
+			_ => anyhow::bail!("Unsupported Polkadot call: {:?}", call),
+		}
+	}
+}
+
+impl CliChain for Polkadot {
+	const RUNTIME_VERSION: RuntimeVersion = bp_polkadot::VERSION;
+
+	type KeyPair = sp_core::sr25519::Pair;
+	type MessagePayload = ();
+
+	fn ss58_format() -> u16 {
+		42
+	}
+
+	fn max_extrinsic_weight() -> Weight {
+		bp_polkadot::max_extrinsic_weight()
+	}
+
+	fn encode_message(_message: encode_message::MessagePayload) -> Result<Self::MessagePayload, String> {
+		Err("Sending messages from Polkadot is not yet supported.".into())
+	}
+}
diff --git a/bridges/relays/bin-substrate/src/chains/polkadot_headers_to_kusama.rs b/bridges/relays/bin-substrate/src/chains/polkadot_headers_to_kusama.rs
new file mode 100644
index 00000000000..aee66b8a3f2
--- /dev/null
+++ b/bridges/relays/bin-substrate/src/chains/polkadot_headers_to_kusama.rs
@@ -0,0 +1,125 @@
+// 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/>.
+
+//! Polkadot-to-Kusama headers sync entrypoint.
+
+use codec::Encode;
+use sp_core::{Bytes, Pair};
+
+use bp_header_chain::justification::GrandpaJustification;
+use relay_kusama_client::{Kusama, SigningParams as KusamaSigningParams};
+use relay_polkadot_client::{Polkadot, SyncHeader as PolkadotSyncHeader};
+use relay_substrate_client::{Client, TransactionSignScheme, UnsignedTransaction};
+use relay_utils::metrics::MetricsParams;
+use substrate_relay_helper::finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate};
+
+/// Maximal saturating difference between `balance(now)` and `balance(now-24h)` to treat
+/// relay as gone wild.
+///
+/// Actual value, returned by `maximal_balance_decrease_per_day_is_sane` test is approximately 0.001 KSM,
+/// but let's round up to 0.1 KSM here.
+pub(crate) const MAXIMAL_BALANCE_DECREASE_PER_DAY: bp_polkadot::Balance = 100_000_000_000;
+
+/// Polkadot-to-Kusama finality sync pipeline.
+pub(crate) type FinalityPipelinePolkadotFinalityToKusama =
+	SubstrateFinalityToSubstrate<Polkadot, Kusama, KusamaSigningParams>;
+
+#[derive(Clone, Debug)]
+pub(crate) struct PolkadotFinalityToKusama {
+	finality_pipeline: FinalityPipelinePolkadotFinalityToKusama,
+}
+
+impl PolkadotFinalityToKusama {
+	pub fn new(target_client: Client<Kusama>, target_sign: KusamaSigningParams) -> Self {
+		Self {
+			finality_pipeline: FinalityPipelinePolkadotFinalityToKusama::new(target_client, target_sign),
+		}
+	}
+}
+
+impl SubstrateFinalitySyncPipeline for PolkadotFinalityToKusama {
+	type FinalitySyncPipeline = FinalityPipelinePolkadotFinalityToKusama;
+
+	const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_polkadot::BEST_FINALIZED_POLKADOT_HEADER_METHOD;
+
+	type TargetChain = Kusama;
+
+	fn customize_metrics(params: MetricsParams) -> anyhow::Result<MetricsParams> {
+		crate::chains::add_polkadot_kusama_price_metrics::<Self::FinalitySyncPipeline>(
+			Some(finality_relay::metrics_prefix::<Self::FinalitySyncPipeline>()),
+			params,
+		)
+	}
+
+	fn start_relay_guards(&self) {
+		relay_substrate_client::guard::abort_on_spec_version_change(
+			self.finality_pipeline.target_client.clone(),
+			bp_kusama::VERSION.spec_version,
+		);
+		relay_substrate_client::guard::abort_when_account_balance_decreased(
+			self.finality_pipeline.target_client.clone(),
+			self.transactions_author(),
+			MAXIMAL_BALANCE_DECREASE_PER_DAY,
+		);
+	}
+
+	fn transactions_author(&self) -> bp_kusama::AccountId {
+		(*self.finality_pipeline.target_sign.public().as_array_ref()).into()
+	}
+
+	fn make_submit_finality_proof_transaction(
+		&self,
+		era: bp_runtime::TransactionEraOf<Kusama>,
+		transaction_nonce: bp_runtime::IndexOf<Kusama>,
+		header: PolkadotSyncHeader,
+		proof: GrandpaJustification<bp_polkadot::Header>,
+	) -> Bytes {
+		let call = relay_kusama_client::runtime::Call::BridgePolkadotGrandpa(
+			relay_kusama_client::runtime::BridgePolkadotGrandpaCall::submit_finality_proof(header.into_inner(), proof),
+		);
+		let genesis_hash = *self.finality_pipeline.target_client.genesis_hash();
+		let transaction = Kusama::sign_transaction(
+			genesis_hash,
+			&self.finality_pipeline.target_sign,
+			era,
+			UnsignedTransaction::new(call, transaction_nonce),
+		);
+
+		Bytes(transaction.encode())
+	}
+}
+
+#[cfg(test)]
+mod tests {
+	use super::*;
+	use crate::chains::kusama_headers_to_polkadot::tests::compute_maximal_balance_decrease_per_day;
+
+	#[test]
+	fn maximal_balance_decrease_per_day_is_sane() {
+		// we expect Polkadot -> Kusama relay to be running in mandatory-headers-only mode
+		// => we expect single header for every Polkadot session
+		let maximal_balance_decrease = compute_maximal_balance_decrease_per_day::<
+			bp_kusama::Balance,
+			bp_kusama::WeightToFee,
+		>(bp_polkadot::DAYS / bp_polkadot::SESSION_LENGTH + 1);
+		assert!(
+			MAXIMAL_BALANCE_DECREASE_PER_DAY >= maximal_balance_decrease,
+			"Maximal expected loss per day {} is larger than hardcoded {}",
+			maximal_balance_decrease,
+			MAXIMAL_BALANCE_DECREASE_PER_DAY,
+		);
+	}
+}
diff --git a/bridges/relays/bin-substrate/src/chains/polkadot_messages_to_kusama.rs b/bridges/relays/bin-substrate/src/chains/polkadot_messages_to_kusama.rs
new file mode 100644
index 00000000000..092a02598d1
--- /dev/null
+++ b/bridges/relays/bin-substrate/src/chains/polkadot_messages_to_kusama.rs
@@ -0,0 +1,303 @@
+// 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/>.
+
+//! Polkadot-to-Kusama messages sync entrypoint.
+
+use std::{ops::RangeInclusive, time::Duration};
+
+use codec::Encode;
+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_kusama_client::{HeaderId as KusamaHeaderId, Kusama, SigningParams as KusamaSigningParams};
+use relay_polkadot_client::{HeaderId as PolkadotHeaderId, Polkadot, SigningParams as PolkadotSigningParams};
+use relay_substrate_client::{Chain, Client, TransactionSignScheme, UnsignedTransaction};
+use relay_utils::metrics::MetricsParams;
+use sp_runtime::{FixedPointNumber, FixedU128};
+use substrate_relay_helper::messages_lane::{
+	select_delivery_transaction_limits, MessagesRelayParams, StandaloneMessagesMetrics, SubstrateMessageLane,
+	SubstrateMessageLaneToSubstrate,
+};
+use substrate_relay_helper::messages_source::SubstrateMessagesSource;
+use substrate_relay_helper::messages_target::SubstrateMessagesTarget;
+
+/// Polkadot-to-Kusama message lane.
+pub type MessageLanePolkadotMessagesToKusama =
+	SubstrateMessageLaneToSubstrate<Polkadot, PolkadotSigningParams, Kusama, KusamaSigningParams>;
+
+#[derive(Clone)]
+pub struct PolkadotMessagesToKusama {
+	message_lane: MessageLanePolkadotMessagesToKusama,
+}
+
+impl SubstrateMessageLane for PolkadotMessagesToKusama {
+	type MessageLane = MessageLanePolkadotMessagesToKusama;
+	const OUTBOUND_LANE_MESSAGE_DETAILS_METHOD: &'static str = bp_kusama::TO_KUSAMA_MESSAGE_DETAILS_METHOD;
+	const OUTBOUND_LANE_LATEST_GENERATED_NONCE_METHOD: &'static str =
+		bp_kusama::TO_KUSAMA_LATEST_GENERATED_NONCE_METHOD;
+	const OUTBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str = bp_kusama::TO_KUSAMA_LATEST_RECEIVED_NONCE_METHOD;
+
+	const INBOUND_LANE_LATEST_RECEIVED_NONCE_METHOD: &'static str =
+		bp_polkadot::FROM_POLKADOT_LATEST_RECEIVED_NONCE_METHOD;
+	const INBOUND_LANE_LATEST_CONFIRMED_NONCE_METHOD: &'static str =
+		bp_polkadot::FROM_POLKADOT_LATEST_CONFIRMED_NONCE_METHOD;
+	const INBOUND_LANE_UNREWARDED_RELAYERS_STATE: &'static str = bp_polkadot::FROM_POLKADOT_UNREWARDED_RELAYERS_STATE;
+
+	const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_polkadot::BEST_FINALIZED_POLKADOT_HEADER_METHOD;
+	const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str = bp_kusama::BEST_FINALIZED_KUSAMA_HEADER_METHOD;
+
+	const MESSAGE_PALLET_NAME_AT_SOURCE: &'static str = bp_polkadot::WITH_KUSAMA_MESSAGES_PALLET_NAME;
+	const MESSAGE_PALLET_NAME_AT_TARGET: &'static str = bp_kusama::WITH_POLKADOT_MESSAGES_PALLET_NAME;
+
+	const PAY_INBOUND_DISPATCH_FEE_WEIGHT_AT_TARGET_CHAIN: Weight = bp_kusama::PAY_INBOUND_DISPATCH_FEE_WEIGHT;
+
+	type SourceChain = Polkadot;
+	type TargetChain = Kusama;
+
+	fn source_transactions_author(&self) -> bp_polkadot::AccountId {
+		(*self.message_lane.source_sign.public().as_array_ref()).into()
+	}
+
+	fn make_messages_receiving_proof_transaction(
+		&self,
+		transaction_nonce: bp_runtime::IndexOf<Polkadot>,
+		_generated_at_block: KusamaHeaderId,
+		proof: <Self::MessageLane as MessageLane>::MessagesReceivingProof,
+	) -> Bytes {
+		let (relayers_state, proof) = proof;
+		let call = relay_polkadot_client::runtime::Call::BridgeKusamaMessages(
+			relay_polkadot_client::runtime::BridgeKusamaMessagesCall::receive_messages_delivery_proof(
+				proof,
+				relayers_state,
+			),
+		);
+		let genesis_hash = *self.message_lane.source_client.genesis_hash();
+		let transaction = Polkadot::sign_transaction(
+			genesis_hash,
+			&self.message_lane.source_sign,
+			relay_substrate_client::TransactionEra::immortal(),
+			UnsignedTransaction::new(call, transaction_nonce),
+		);
+		log::trace!(
+			target: "bridge",
+			"Prepared Kusama -> Polkadot confirmation transaction. Weight: <unknown>/{}, size: {}/{}",
+			bp_polkadot::max_extrinsic_weight(),
+			transaction.encode().len(),
+			bp_polkadot::max_extrinsic_size(),
+		);
+		Bytes(transaction.encode())
+	}
+
+	fn target_transactions_author(&self) -> bp_kusama::AccountId {
+		(*self.message_lane.target_sign.public().as_array_ref()).into()
+	}
+
+	fn make_messages_delivery_transaction(
+		&self,
+		transaction_nonce: bp_runtime::IndexOf<Kusama>,
+		_generated_at_header: PolkadotHeaderId,
+		_nonces: RangeInclusive<MessageNonce>,
+		proof: <Self::MessageLane as MessageLane>::MessagesProof,
+	) -> Bytes {
+		let (dispatch_weight, proof) = proof;
+		let FromBridgedChainMessagesProof {
+			ref nonces_start,
+			ref nonces_end,
+			..
+		} = proof;
+		let messages_count = nonces_end - nonces_start + 1;
+
+		let call = relay_kusama_client::runtime::Call::BridgePolkadotMessages(
+			relay_kusama_client::runtime::BridgePolkadotMessagesCall::receive_messages_proof(
+				self.message_lane.relayer_id_at_source.clone(),
+				proof,
+				messages_count as _,
+				dispatch_weight,
+			),
+		);
+		let genesis_hash = *self.message_lane.target_client.genesis_hash();
+		let transaction = Kusama::sign_transaction(
+			genesis_hash,
+			&self.message_lane.target_sign,
+			relay_substrate_client::TransactionEra::immortal(),
+			UnsignedTransaction::new(call, transaction_nonce),
+		);
+		log::trace!(
+			target: "bridge",
+			"Prepared Polkadot -> Kusama delivery transaction. Weight: <unknown>/{}, size: {}/{}",
+			bp_kusama::max_extrinsic_weight(),
+			transaction.encode().len(),
+			bp_kusama::max_extrinsic_size(),
+		);
+		Bytes(transaction.encode())
+	}
+}
+
+/// Polkadot node as messages source.
+type PolkadotSourceClient = SubstrateMessagesSource<PolkadotMessagesToKusama>;
+
+/// Kusama node as messages target.
+type KusamaTargetClient = SubstrateMessagesTarget<PolkadotMessagesToKusama>;
+
+/// Run Polkadot-to-Kusama messages sync.
+pub async fn run(
+	params: MessagesRelayParams<Polkadot, PolkadotSigningParams, Kusama, KusamaSigningParams>,
+) -> anyhow::Result<()> {
+	let stall_timeout = Duration::from_secs(5 * 60);
+	let relayer_id_at_polkadot = (*params.source_sign.public().as_array_ref()).into();
+
+	let lane_id = params.lane_id;
+	let source_client = params.source_client;
+	let lane = PolkadotMessagesToKusama {
+		message_lane: SubstrateMessageLaneToSubstrate {
+			source_client: source_client.clone(),
+			source_sign: params.source_sign,
+			target_client: params.target_client.clone(),
+			target_sign: params.target_sign,
+			relayer_id_at_source: relayer_id_at_polkadot,
+		},
+	};
+
+	// 2/3 is reserved for proofs and tx overhead
+	let max_messages_size_in_single_batch = bp_kusama::max_extrinsic_size() / 3;
+	// we don't know exact weights of the Kusama runtime. So to guess weights we'll be using
+	// weights from Rialto and then simply dividing it by x2.
+	let (max_messages_in_single_batch, max_messages_weight_in_single_batch) =
+		select_delivery_transaction_limits::<pallet_bridge_messages::weights::RialtoWeight<rialto_runtime::Runtime>>(
+			bp_kusama::max_extrinsic_weight(),
+			bp_kusama::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
+		);
+	let (max_messages_in_single_batch, max_messages_weight_in_single_batch) = (
+		max_messages_in_single_batch / 2,
+		max_messages_weight_in_single_batch / 2,
+	);
+
+	log::info!(
+		target: "bridge",
+		"Starting Polkadot -> Kusama messages relay.\n\t\
+			Polkadot relayer account id: {:?}\n\t\
+			Max messages in single transaction: {}\n\t\
+			Max messages size in single transaction: {}\n\t\
+			Max messages weight in single transaction: {}\n\t\
+			Relayer mode: {:?}",
+		lane.message_lane.relayer_id_at_source,
+		max_messages_in_single_batch,
+		max_messages_size_in_single_batch,
+		max_messages_weight_in_single_batch,
+		params.relayer_mode,
+	);
+
+	let (metrics_params, metrics_values) = add_standalone_metrics(
+		Some(messages_relay::message_lane_loop::metrics_prefix::<
+			<PolkadotMessagesToKusama as SubstrateMessageLane>::MessageLane,
+		>(&lane_id)),
+		params.metrics_params,
+		source_client.clone(),
+	)?;
+	messages_relay::message_lane_loop::run(
+		messages_relay::message_lane_loop::Params {
+			lane: lane_id,
+			source_tick: Polkadot::AVERAGE_BLOCK_INTERVAL,
+			target_tick: Kusama::AVERAGE_BLOCK_INTERVAL,
+			reconnect_delay: relay_utils::relay_loop::RECONNECT_DELAY,
+			stall_timeout,
+			delivery_params: messages_relay::message_lane_loop::MessageDeliveryParams {
+				max_unrewarded_relayer_entries_at_target: bp_kusama::MAX_UNREWARDED_RELAYER_ENTRIES_AT_INBOUND_LANE,
+				max_unconfirmed_nonces_at_target: bp_kusama::MAX_UNCONFIRMED_MESSAGES_AT_INBOUND_LANE,
+				max_messages_in_single_batch,
+				max_messages_weight_in_single_batch,
+				max_messages_size_in_single_batch,
+				relayer_mode: params.relayer_mode,
+			},
+		},
+		PolkadotSourceClient::new(
+			source_client.clone(),
+			lane.clone(),
+			lane_id,
+			params.target_to_source_headers_relay,
+		),
+		KusamaTargetClient::new(
+			params.target_client,
+			lane,
+			lane_id,
+			metrics_values,
+			params.source_to_target_headers_relay,
+		),
+		metrics_params,
+		futures::future::pending(),
+	)
+	.await
+}
+
+/// Add standalone metrics for the Polkadot -> Kusama messages loop.
+pub(crate) fn add_standalone_metrics(
+	metrics_prefix: Option<String>,
+	metrics_params: MetricsParams,
+	source_client: Client<Polkadot>,
+) -> anyhow::Result<(MetricsParams, StandaloneMessagesMetrics)> {
+	let kusama_to_polkadot_conversion_rate_key =
+		bp_runtime::storage_parameter_key(bp_polkadot::KUSAMA_TO_POLKADOT_CONVERSION_RATE_PARAMETER_NAME).0;
+
+	substrate_relay_helper::messages_lane::add_standalone_metrics::<PolkadotMessagesToKusama>(
+		metrics_prefix,
+		metrics_params,
+		source_client,
+		Some(crate::chains::kusama::TOKEN_ID),
+		Some(crate::chains::polkadot::TOKEN_ID),
+		Some((
+			sp_core::storage::StorageKey(kusama_to_polkadot_conversion_rate_key),
+			// starting relay before this parameter will be set to some value may cause troubles
+			FixedU128::from_inner(FixedU128::DIV),
+		)),
+	)
+}
+
+/// Update Kusama -> Polkadot conversion rate, stored in Polkadot runtime storage.
+pub(crate) async fn update_kusama_to_polkadot_conversion_rate(
+	client: Client<Polkadot>,
+	signer: <Polkadot as TransactionSignScheme>::AccountKeyPair,
+	updated_rate: f64,
+) -> anyhow::Result<()> {
+	let genesis_hash = *client.genesis_hash();
+	let signer_id = (*signer.public().as_array_ref()).into();
+	client
+		.submit_signed_extrinsic(signer_id, move |_, transaction_nonce| {
+			Bytes(
+				Polkadot::sign_transaction(
+					genesis_hash,
+					&signer,
+					relay_substrate_client::TransactionEra::immortal(),
+					UnsignedTransaction::new(
+						relay_polkadot_client::runtime::Call::BridgeKusamaMessages(
+							relay_polkadot_client::runtime::BridgeKusamaMessagesCall::update_pallet_parameter(
+								relay_polkadot_client::runtime::BridgeKusamaMessagesParameter::KusamaToPolkadotConversionRate(
+									sp_runtime::FixedU128::from_float(updated_rate),
+								)
+							)
+						),
+						transaction_nonce,
+					),
+				)
+				.encode(),
+			)
+		})
+		.await
+		.map(drop)
+		.map_err(|err| anyhow::format_err!("{:?}", err))
+}
diff --git a/bridges/relays/bin-substrate/src/chains/wococo_headers_to_rococo.rs b/bridges/relays/bin-substrate/src/chains/wococo_headers_to_rococo.rs
index b3e98d65421..b06921bfd9c 100644
--- a/bridges/relays/bin-substrate/src/chains/wococo_headers_to_rococo.rs
+++ b/bridges/relays/bin-substrate/src/chains/wococo_headers_to_rococo.rs
@@ -104,39 +104,18 @@ impl SubstrateFinalitySyncPipeline for WococoFinalityToRococo {
 
 #[cfg(test)]
 mod tests {
-	use frame_support::weights::WeightToFeePolynomial;
-
-	use pallet_bridge_grandpa::weights::WeightInfo;
-
 	use super::*;
+	use crate::chains::kusama_headers_to_polkadot::tests::compute_maximal_balance_decrease_per_day;
 
 	#[test]
 	fn maximal_balance_decrease_per_day_is_sane() {
-		// Rococo/Wococo GRANDPA pallet weights. They're now using Rialto weights => using `RialtoWeight` is justified.
-		//
-		// Using Rialto runtime this is slightly incorrect, because `DbWeight` of Rococo/Wococo runtime may differ
-		// from the `DbWeight` of Rialto runtime. But now (and most probably forever) it is the same.
-		type RococoGrandpaPalletWeights = pallet_bridge_grandpa::weights::RialtoWeight<rialto_runtime::Runtime>;
-
-		// The following formula shall not be treated as super-accurate - guard is to protect from mad relays,
-		// not to protect from over-average loses.
-		//
-		// Worst case: we're submitting proof for every source header. Since we submit every header, the number of
-		// headers in ancestry proof is near to 0 (let's round up to 2). And the number of authorities is 1024,
-		// which is (now) larger than on any existing chain => normally there'll be ~1024*2/3+1 commits.
-		const AVG_VOTES_ANCESTRIES_LEN: u32 = 2;
-		const AVG_PRECOMMITS_LEN: u32 = 1024 * 2 / 3 + 1;
-		let number_of_source_headers_per_day: bp_wococo::Balance = bp_wococo::DAYS as _;
-		let single_source_header_submit_call_weight =
-			RococoGrandpaPalletWeights::submit_finality_proof(AVG_VOTES_ANCESTRIES_LEN, AVG_PRECOMMITS_LEN);
-		// for simplicity - add extra weight for base tx fee + fee that is paid for the tx size + adjusted fee
-		let single_source_header_submit_tx_weight = single_source_header_submit_call_weight * 3 / 2;
-		let single_source_header_tx_cost = bp_rococo::WeightToFee::calc(&single_source_header_submit_tx_weight);
-		let maximal_expected_decrease = single_source_header_tx_cost * number_of_source_headers_per_day;
+		// we expect Wococo -> Rococo relay to be running in all-headers mode
+		let maximal_balance_decrease =
+			compute_maximal_balance_decrease_per_day::<bp_kusama::Balance, bp_kusama::WeightToFee>(bp_wococo::DAYS);
 		assert!(
-			MAXIMAL_BALANCE_DECREASE_PER_DAY >= maximal_expected_decrease,
+			MAXIMAL_BALANCE_DECREASE_PER_DAY >= maximal_balance_decrease,
 			"Maximal expected loss per day {} is larger than hardcoded {}",
-			maximal_expected_decrease,
+			maximal_balance_decrease,
 			MAXIMAL_BALANCE_DECREASE_PER_DAY,
 		);
 	}
diff --git a/bridges/relays/bin-substrate/src/cli/bridge.rs b/bridges/relays/bin-substrate/src/cli/bridge.rs
index 30950b289f5..1af6142c53e 100644
--- a/bridges/relays/bin-substrate/src/cli/bridge.rs
+++ b/bridges/relays/bin-substrate/src/cli/bridge.rs
@@ -24,6 +24,8 @@ pub enum FullBridge {
 	RialtoToMillau,
 	RococoToWococo,
 	WococoToRococo,
+	KusamaToPolkadot,
+	PolkadotToKusama,
 }
 
 impl FullBridge {
@@ -34,6 +36,8 @@ impl FullBridge {
 			Self::RialtoToMillau => RIALTO_TO_MILLAU_INDEX,
 			Self::RococoToWococo => ROCOCO_TO_WOCOCO_INDEX,
 			Self::WococoToRococo => WOCOCO_TO_ROCOCO_INDEX,
+			Self::KusamaToPolkadot => KUSAMA_TO_POLKADOT_INDEX,
+			Self::PolkadotToKusama => POLKADOT_TO_KUSAMA_INDEX,
 		}
 	}
 }
@@ -42,6 +46,8 @@ pub const RIALTO_TO_MILLAU_INDEX: u8 = 0;
 pub const MILLAU_TO_RIALTO_INDEX: u8 = 0;
 pub const ROCOCO_TO_WOCOCO_INDEX: u8 = 0;
 pub const WOCOCO_TO_ROCOCO_INDEX: u8 = 0;
+pub const KUSAMA_TO_POLKADOT_INDEX: u8 = 0;
+pub const POLKADOT_TO_KUSAMA_INDEX: u8 = 0;
 
 /// The macro allows executing bridge-specific code without going fully generic.
 ///
@@ -138,6 +144,50 @@ macro_rules! select_full_bridge {
 				#[allow(unused_imports)]
 				use relay_wococo_client::runtime::wococo_to_rococo_account_ownership_digest as account_ownership_digest;
 
+				$generic
+			}
+			FullBridge::KusamaToPolkadot => {
+				type Source = relay_kusama_client::Kusama;
+				#[allow(dead_code)]
+				type Target = relay_polkadot_client::Polkadot;
+
+				// Derive-account
+				#[allow(unused_imports)]
+				use bp_polkadot::derive_account_from_kusama_id as derive_account;
+
+				// Relay-messages
+				#[allow(unused_imports)]
+				use crate::chains::kusama_messages_to_polkadot::run as relay_messages;
+
+				// Send-message / Estimate-fee
+				#[allow(unused_imports)]
+				use bp_polkadot::TO_POLKADOT_ESTIMATE_MESSAGE_FEE_METHOD as ESTIMATE_MESSAGE_FEE_METHOD;
+				// Send-message
+				#[allow(unused_imports)]
+				use relay_kusama_client::runtime::kusama_to_polkadot_account_ownership_digest as account_ownership_digest;
+
+				$generic
+			}
+			FullBridge::PolkadotToKusama => {
+				type Source = relay_polkadot_client::Polkadot;
+				#[allow(dead_code)]
+				type Target = relay_kusama_client::Kusama;
+
+				// Derive-account
+				#[allow(unused_imports)]
+				use bp_kusama::derive_account_from_polkadot_id as derive_account;
+
+				// Relay-messages
+				#[allow(unused_imports)]
+				use crate::chains::polkadot_messages_to_kusama::run as relay_messages;
+
+				// Send-message / Estimate-fee
+				#[allow(unused_imports)]
+				use bp_kusama::TO_KUSAMA_ESTIMATE_MESSAGE_FEE_METHOD as ESTIMATE_MESSAGE_FEE_METHOD;
+				// Send-message
+				#[allow(unused_imports)]
+				use relay_polkadot_client::runtime::polkadot_to_kusama_account_ownership_digest as account_ownership_digest;
+
 				$generic
 			}
 		}
diff --git a/bridges/relays/bin-substrate/src/cli/init_bridge.rs b/bridges/relays/bin-substrate/src/cli/init_bridge.rs
index 81663ed429a..3e464e6f545 100644
--- a/bridges/relays/bin-substrate/src/cli/init_bridge.rs
+++ b/bridges/relays/bin-substrate/src/cli/init_bridge.rs
@@ -46,6 +46,8 @@ pub enum InitBridgeName {
 	WestendToMillau,
 	RococoToWococo,
 	WococoToRococo,
+	KusamaToPolkadot,
+	PolkadotToKusama,
 }
 
 macro_rules! select_bridge {
@@ -127,6 +129,34 @@ macro_rules! select_bridge {
 					)
 				}
 
+				$generic
+			}
+			InitBridgeName::KusamaToPolkadot => {
+				type Source = relay_kusama_client::Kusama;
+				type Target = relay_polkadot_client::Polkadot;
+
+				fn encode_init_bridge(
+					init_data: InitializationData<<Source as ChainBase>::Header>,
+				) -> <Target as Chain>::Call {
+					relay_polkadot_client::runtime::Call::BridgeKusamaGrandpa(
+						relay_polkadot_client::runtime::BridgeKusamaGrandpaCall::initialize(init_data),
+					)
+				}
+
+				$generic
+			}
+			InitBridgeName::PolkadotToKusama => {
+				type Source = relay_polkadot_client::Polkadot;
+				type Target = relay_kusama_client::Kusama;
+
+				fn encode_init_bridge(
+					init_data: InitializationData<<Source as ChainBase>::Header>,
+				) -> <Target as Chain>::Call {
+					relay_kusama_client::runtime::Call::BridgePolkadotGrandpa(
+						relay_kusama_client::runtime::BridgePolkadotGrandpaCall::initialize(init_data),
+					)
+				}
+
 				$generic
 			}
 		}
diff --git a/bridges/relays/bin-substrate/src/cli/relay_headers.rs b/bridges/relays/bin-substrate/src/cli/relay_headers.rs
index 0e527eb5b91..48e2d85efbc 100644
--- a/bridges/relays/bin-substrate/src/cli/relay_headers.rs
+++ b/bridges/relays/bin-substrate/src/cli/relay_headers.rs
@@ -49,6 +49,8 @@ pub enum RelayHeadersBridge {
 	WestendToMillau,
 	RococoToWococo,
 	WococoToRococo,
+	KusamaToPolkadot,
+	PolkadotToKusama,
 }
 
 macro_rules! select_bridge {
@@ -87,6 +89,20 @@ macro_rules! select_bridge {
 				type Target = relay_rococo_client::Rococo;
 				type Finality = crate::chains::wococo_headers_to_rococo::WococoFinalityToRococo;
 
+				$generic
+			}
+			RelayHeadersBridge::KusamaToPolkadot => {
+				type Source = relay_kusama_client::Kusama;
+				type Target = relay_polkadot_client::Polkadot;
+				type Finality = crate::chains::kusama_headers_to_polkadot::KusamaFinalityToPolkadot;
+
+				$generic
+			}
+			RelayHeadersBridge::PolkadotToKusama => {
+				type Source = relay_polkadot_client::Polkadot;
+				type Target = relay_kusama_client::Kusama;
+				type Finality = crate::chains::polkadot_headers_to_kusama::PolkadotFinalityToKusama;
+
 				$generic
 			}
 		}
diff --git a/bridges/relays/bin-substrate/src/cli/relay_headers_and_messages.rs b/bridges/relays/bin-substrate/src/cli/relay_headers_and_messages.rs
index 956f46f18cb..c6a09831411 100644
--- a/bridges/relays/bin-substrate/src/cli/relay_headers_and_messages.rs
+++ b/bridges/relays/bin-substrate/src/cli/relay_headers_and_messages.rs
@@ -26,8 +26,10 @@ use futures::{FutureExt, TryFutureExt};
 use structopt::StructOpt;
 use strum::VariantNames;
 
-use relay_substrate_client::{Chain, Client, TransactionSignScheme};
+use codec::Encode;
+use relay_substrate_client::{AccountIdOf, Chain, Client, TransactionSignScheme, UnsignedTransaction};
 use relay_utils::metrics::MetricsParams;
+use sp_core::{Bytes, Pair};
 use substrate_relay_helper::messages_lane::{MessagesRelayParams, SubstrateMessageLane};
 use substrate_relay_helper::on_demand_headers::OnDemandHeadersRelay;
 
@@ -47,6 +49,7 @@ const CONVERSION_RATE_ALLOWED_DIFFERENCE_RATIO: f64 = 0.05;
 pub enum RelayHeadersAndMessages {
 	MillauRialto(MillauRialtoHeadersAndMessages),
 	RococoWococo(RococoWococoHeadersAndMessages),
+	KusamaPolkadot(KusamaPolkadotHeadersAndMessages),
 }
 
 /// Parameters that have the same names across all bridges.
@@ -57,6 +60,9 @@ pub struct HeadersAndMessagesSharedParams {
 	lane: Vec<HexLaneId>,
 	#[structopt(long, possible_values = RelayerMode::VARIANTS, case_insensitive = true, default_value = "rational")]
 	relayer_mode: RelayerMode,
+	/// Create relayers fund accounts on both chains, if it does not exists yet.
+	#[structopt(long)]
+	create_relayers_fund_accounts: bool,
 	/// If passed, only mandatory headers (headers that are changing the GRANDPA authorities set) are relayed.
 	#[structopt(long)]
 	only_mandatory_headers: bool,
@@ -89,7 +95,6 @@ macro_rules! declare_bridge_options {
 				right_messages_pallet_owner: [<$chain2 MessagesPalletOwnerSigningParams>],
 			}
 
-			#[allow(unreachable_patterns)]
 			impl From<RelayHeadersAndMessages> for [<$chain1 $chain2 HeadersAndMessages>] {
 				fn from(relay_params: RelayHeadersAndMessages) -> [<$chain1 $chain2 HeadersAndMessages>] {
 					match relay_params {
@@ -117,6 +122,9 @@ macro_rules! select_bridge {
 				type LeftToRightMessages = crate::chains::millau_messages_to_rialto::MillauMessagesToRialto;
 				type RightToLeftMessages = crate::chains::rialto_messages_to_millau::RialtoMessagesToMillau;
 
+				type LeftAccountIdConverter = bp_millau::AccountIdConverter;
+				type RightAccountIdConverter = bp_rialto::AccountIdConverter;
+
 				const MAX_MISSING_LEFT_HEADERS_AT_RIGHT: bp_millau::BlockNumber = bp_millau::SESSION_LENGTH;
 				const MAX_MISSING_RIGHT_HEADERS_AT_LEFT: bp_rialto::BlockNumber = bp_rialto::SESSION_LENGTH;
 
@@ -129,6 +137,22 @@ macro_rules! select_bridge {
 					update_millau_to_rialto_conversion_rate as update_left_to_right_conversion_rate,
 				};
 
+				async fn left_create_account(
+					_left_client: Client<Left>,
+					_left_sign: <Left as TransactionSignScheme>::AccountKeyPair,
+					_account_id: AccountIdOf<Left>,
+				) -> anyhow::Result<()> {
+					Err(anyhow::format_err!("Account creation is not supported by this bridge"))
+				}
+
+				async fn right_create_account(
+					_right_client: Client<Right>,
+					_right_sign: <Right as TransactionSignScheme>::AccountKeyPair,
+					_account_id: AccountIdOf<Right>,
+				) -> anyhow::Result<()> {
+					Err(anyhow::format_err!("Account creation is not supported by this bridge"))
+				}
+
 				$generic
 			}
 			RelayHeadersAndMessages::RococoWococo(_) => {
@@ -143,6 +167,9 @@ macro_rules! select_bridge {
 				type LeftToRightMessages = crate::chains::rococo_messages_to_wococo::RococoMessagesToWococo;
 				type RightToLeftMessages = crate::chains::wococo_messages_to_rococo::WococoMessagesToRococo;
 
+				type LeftAccountIdConverter = bp_rococo::AccountIdConverter;
+				type RightAccountIdConverter = bp_wococo::AccountIdConverter;
+
 				const MAX_MISSING_LEFT_HEADERS_AT_RIGHT: bp_rococo::BlockNumber = bp_rococo::SESSION_LENGTH;
 				const MAX_MISSING_RIGHT_HEADERS_AT_LEFT: bp_wococo::BlockNumber = bp_wococo::SESSION_LENGTH;
 
@@ -169,6 +196,113 @@ macro_rules! select_bridge {
 					Err(anyhow::format_err!("Conversion rate is not supported by this bridge"))
 				}
 
+				async fn left_create_account(
+					_left_client: Client<Left>,
+					_left_sign: <Left as TransactionSignScheme>::AccountKeyPair,
+					_account_id: AccountIdOf<Left>,
+				) -> anyhow::Result<()> {
+					Err(anyhow::format_err!("Account creation is not supported by this bridge"))
+				}
+
+				async fn right_create_account(
+					_right_client: Client<Right>,
+					_right_sign: <Right as TransactionSignScheme>::AccountKeyPair,
+					_account_id: AccountIdOf<Right>,
+				) -> anyhow::Result<()> {
+					Err(anyhow::format_err!("Account creation is not supported by this bridge"))
+				}
+
+				$generic
+			}
+			RelayHeadersAndMessages::KusamaPolkadot(_) => {
+				type Params = KusamaPolkadotHeadersAndMessages;
+
+				type Left = relay_kusama_client::Kusama;
+				type Right = relay_polkadot_client::Polkadot;
+
+				type LeftToRightFinality = crate::chains::kusama_headers_to_polkadot::KusamaFinalityToPolkadot;
+				type RightToLeftFinality = crate::chains::polkadot_headers_to_kusama::PolkadotFinalityToKusama;
+
+				type LeftToRightMessages = crate::chains::kusama_messages_to_polkadot::KusamaMessagesToPolkadot;
+				type RightToLeftMessages = crate::chains::polkadot_messages_to_kusama::PolkadotMessagesToKusama;
+
+				type LeftAccountIdConverter = bp_kusama::AccountIdConverter;
+				type RightAccountIdConverter = bp_polkadot::AccountIdConverter;
+
+				const MAX_MISSING_LEFT_HEADERS_AT_RIGHT: bp_kusama::BlockNumber = bp_kusama::SESSION_LENGTH;
+				const MAX_MISSING_RIGHT_HEADERS_AT_LEFT: bp_polkadot::BlockNumber = bp_polkadot::SESSION_LENGTH;
+
+				use crate::chains::kusama_messages_to_polkadot::{
+					add_standalone_metrics as add_left_to_right_standalone_metrics, run as left_to_right_messages,
+					update_polkadot_to_kusama_conversion_rate as update_right_to_left_conversion_rate,
+				};
+				use crate::chains::polkadot_messages_to_kusama::{
+					add_standalone_metrics as add_right_to_left_standalone_metrics, run as right_to_left_messages,
+					update_kusama_to_polkadot_conversion_rate as update_left_to_right_conversion_rate,
+				};
+
+				async fn left_create_account(
+					left_client: Client<Left>,
+					left_sign: <Left as TransactionSignScheme>::AccountKeyPair,
+					account_id: AccountIdOf<Left>,
+				) -> anyhow::Result<()> {
+					let left_genesis_hash = *left_client.genesis_hash();
+					left_client
+						.submit_signed_extrinsic(left_sign.public().into(), move |_, transaction_nonce| {
+							Bytes(
+								Left::sign_transaction(
+									left_genesis_hash,
+									&left_sign,
+									relay_substrate_client::TransactionEra::immortal(),
+									UnsignedTransaction::new(
+										relay_kusama_client::runtime::Call::Balances(
+											relay_kusama_client::runtime::BalancesCall::transfer(
+												bp_kusama::AccountAddress::Id(account_id),
+												bp_kusama::EXISTENTIAL_DEPOSIT.into(),
+											),
+										),
+										transaction_nonce,
+									),
+								)
+								.encode(),
+							)
+						})
+						.await
+						.map(drop)
+						.map_err(|e| anyhow::format_err!("{}", e))
+				}
+
+				async fn right_create_account(
+					right_client: Client<Right>,
+					right_sign: <Right as TransactionSignScheme>::AccountKeyPair,
+					account_id: AccountIdOf<Right>,
+				) -> anyhow::Result<()> {
+					let right_genesis_hash = *right_client.genesis_hash();
+					right_client
+						.submit_signed_extrinsic(right_sign.public().into(), move |_, transaction_nonce| {
+							Bytes(
+								Right::sign_transaction(
+									right_genesis_hash,
+									&right_sign,
+									relay_substrate_client::TransactionEra::immortal(),
+									UnsignedTransaction::new(
+										relay_polkadot_client::runtime::Call::Balances(
+											relay_polkadot_client::runtime::BalancesCall::transfer(
+												bp_polkadot::AccountAddress::Id(account_id),
+												bp_polkadot::EXISTENTIAL_DEPOSIT.into(),
+											),
+										),
+										transaction_nonce,
+									),
+								)
+								.encode(),
+							)
+						})
+						.await
+						.map(drop)
+						.map_err(|e| anyhow::format_err!("{}", e))
+				}
+
 				$generic
 			}
 		}
@@ -180,9 +314,12 @@ declare_chain_options!(Millau, millau);
 declare_chain_options!(Rialto, rialto);
 declare_chain_options!(Rococo, rococo);
 declare_chain_options!(Wococo, wococo);
+declare_chain_options!(Kusama, kusama);
+declare_chain_options!(Polkadot, polkadot);
 // All supported bridges.
 declare_bridge_options!(Millau, Rialto);
 declare_bridge_options!(Rococo, Wococo);
+declare_bridge_options!(Kusama, Polkadot);
 
 impl RelayHeadersAndMessages {
 	/// Run the command.
@@ -275,6 +412,26 @@ impl RelayHeadersAndMessages {
 				);
 			}
 
+			if params.shared.create_relayers_fund_accounts {
+				let relayer_fund_acount_id =
+					pallet_bridge_messages::relayer_fund_account_id::<AccountIdOf<Left>, LeftAccountIdConverter>();
+				let relayers_fund_account_balance =
+					left_client.free_native_balance(relayer_fund_acount_id.clone()).await;
+				if let Err(relay_substrate_client::Error::AccountDoesNotExist) = relayers_fund_account_balance {
+					log::info!(target: "bridge", "Going to create relayers fund account at {}.", Left::NAME);
+					left_create_account(left_client.clone(), left_sign.clone(), relayer_fund_acount_id).await?;
+				}
+
+				let relayer_fund_acount_id =
+					pallet_bridge_messages::relayer_fund_account_id::<AccountIdOf<Right>, RightAccountIdConverter>();
+				let relayers_fund_account_balance =
+					right_client.free_native_balance(relayer_fund_acount_id.clone()).await;
+				if let Err(relay_substrate_client::Error::AccountDoesNotExist) = relayers_fund_account_balance {
+					log::info!(target: "bridge", "Going to create relayers fund account at {}.", Right::NAME);
+					right_create_account(right_client.clone(), right_sign.clone(), relayer_fund_acount_id).await?;
+				}
+			}
+
 			let left_to_right_on_demand_headers = OnDemandHeadersRelay::new(
 				left_client.clone(),
 				right_client.clone(),
diff --git a/bridges/relays/client-kusama/Cargo.toml b/bridges/relays/client-kusama/Cargo.toml
index 1092f73d5b7..33bbbeeacd9 100644
--- a/bridges/relays/client-kusama/Cargo.toml
+++ b/bridges/relays/client-kusama/Cargo.toml
@@ -6,9 +6,24 @@ edition = "2018"
 license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
 
 [dependencies]
+codec = { package = "parity-scale-codec", version = "2.2.0" }
 relay-substrate-client = { path = "../client-substrate" }
 relay-utils = { path = "../utils" }
 
 # Bridge dependencies
 
+bp-header-chain = { path = "../../primitives/header-chain" }
 bp-kusama = { path = "../../primitives/chain-kusama" }
+bp-message-dispatch = { path = "../../primitives/message-dispatch" }
+bp-messages = { path = "../../primitives/messages" }
+bp-polkadot = { path = "../../primitives/chain-polkadot" }
+bp-polkadot-core = { path = "../../primitives/polkadot-core" }
+bp-runtime = { path = "../../primitives/runtime" }
+bridge-runtime-common = { path = "../../bin/runtime-common" }
+pallet-bridge-dispatch = { path = "../../modules/dispatch" }
+
+# Substrate Dependencies
+
+frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" }
+sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
+sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" }
diff --git a/bridges/relays/client-kusama/src/lib.rs b/bridges/relays/client-kusama/src/lib.rs
index 95a8596c97f..608befaa9a3 100644
--- a/bridges/relays/client-kusama/src/lib.rs
+++ b/bridges/relays/client-kusama/src/lib.rs
@@ -16,9 +16,16 @@
 
 //! Types used to connect to the Kusama chain.
 
-use relay_substrate_client::{Chain, ChainBase};
+use codec::Encode;
+use relay_substrate_client::{
+	Chain, ChainBase, ChainWithBalances, TransactionEraOf, TransactionSignScheme, UnsignedTransaction,
+};
+use sp_core::{storage::StorageKey, Pair};
+use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount};
 use std::time::Duration;
 
+pub mod runtime;
+
 /// Kusama header id.
 pub type HeaderId = relay_utils::HeaderId<bp_kusama::Hash, bp_kusama::BlockNumber>;
 
@@ -45,9 +52,68 @@ impl Chain for Kusama {
 	const MAXIMAL_ENCODED_ACCOUNT_ID_SIZE: u32 = bp_kusama::MAXIMAL_ENCODED_ACCOUNT_ID_SIZE;
 
 	type SignedBlock = bp_kusama::SignedBlock;
-	type Call = ();
+	type Call = crate::runtime::Call;
 	type WeightToFee = bp_kusama::WeightToFee;
 }
 
+impl ChainWithBalances for Kusama {
+	fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey {
+		StorageKey(bp_kusama::account_info_storage_key(account_id))
+	}
+}
+
+impl TransactionSignScheme for Kusama {
+	type Chain = Kusama;
+	type AccountKeyPair = sp_core::sr25519::Pair;
+	type SignedTransaction = crate::runtime::UncheckedExtrinsic;
+
+	fn sign_transaction(
+		genesis_hash: <Self::Chain as ChainBase>::Hash,
+		signer: &Self::AccountKeyPair,
+		era: TransactionEraOf<Self::Chain>,
+		unsigned: UnsignedTransaction<Self::Chain>,
+	) -> Self::SignedTransaction {
+		let raw_payload = SignedPayload::new(
+			unsigned.call,
+			bp_kusama::SignedExtensions::new(bp_kusama::VERSION, era, genesis_hash, unsigned.nonce, unsigned.tip),
+		)
+		.expect("SignedExtension never fails.");
+
+		let signature = raw_payload.using_encoded(|payload| signer.sign(payload));
+		let signer: sp_runtime::MultiSigner = signer.public().into();
+		let (call, extra, _) = raw_payload.deconstruct();
+
+		bp_kusama::UncheckedExtrinsic::new_signed(
+			call,
+			sp_runtime::MultiAddress::Id(signer.into_account()),
+			signature.into(),
+			extra,
+		)
+	}
+
+	fn is_signed(tx: &Self::SignedTransaction) -> bool {
+		tx.signature.is_some()
+	}
+
+	fn is_signed_by(signer: &Self::AccountKeyPair, tx: &Self::SignedTransaction) -> bool {
+		tx.signature
+			.as_ref()
+			.map(|(address, _, _)| *address == bp_kusama::AccountId::from(*signer.public().as_array_ref()).into())
+			.unwrap_or(false)
+	}
+
+	fn parse_transaction(tx: Self::SignedTransaction) -> Option<UnsignedTransaction<Self::Chain>> {
+		let extra = &tx.signature.as_ref()?.2;
+		Some(UnsignedTransaction {
+			call: tx.function,
+			nonce: extra.nonce(),
+			tip: extra.tip(),
+		})
+	}
+}
+
 /// Kusama header type used in headers sync.
 pub type SyncHeader = relay_substrate_client::SyncHeader<bp_kusama::Header>;
+
+/// Kusama signing params.
+pub type SigningParams = sp_core::sr25519::Pair;
diff --git a/bridges/relays/client-kusama/src/runtime.rs b/bridges/relays/client-kusama/src/runtime.rs
new file mode 100644
index 00000000000..4b490b2799b
--- /dev/null
+++ b/bridges/relays/client-kusama/src/runtime.rs
@@ -0,0 +1,151 @@
+// 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/>.
+
+//! Types that are specific to the Kusama runtime.
+
+use bp_messages::{LaneId, UnrewardedRelayersState};
+use bp_polkadot_core::{AccountAddress, Balance, PolkadotLike};
+use bp_runtime::Chain;
+use codec::{Compact, Decode, Encode};
+use frame_support::weights::Weight;
+use sp_runtime::FixedU128;
+
+/// Unchecked Kusama extrinsic.
+pub type UncheckedExtrinsic = bp_polkadot_core::UncheckedExtrinsic<Call>;
+
+/// Polkadot account ownership digest from Kusama.
+///
+/// The byte vector returned by this function should be signed with a Polkadot account private key.
+/// This way, the owner of `kusama_account_id` on Kusama proves that the Polkadot account private key
+/// is also under his control.
+pub fn kusama_to_polkadot_account_ownership_digest<Call, AccountId, SpecVersion>(
+	polkadot_call: &Call,
+	kusama_account_id: AccountId,
+	polkadot_spec_version: SpecVersion,
+) -> Vec<u8>
+where
+	Call: codec::Encode,
+	AccountId: codec::Encode,
+	SpecVersion: codec::Encode,
+{
+	pallet_bridge_dispatch::account_ownership_digest(
+		polkadot_call,
+		kusama_account_id,
+		polkadot_spec_version,
+		bp_runtime::KUSAMA_CHAIN_ID,
+		bp_runtime::POLKADOT_CHAIN_ID,
+	)
+}
+
+/// Kusama Runtime `Call` enum.
+///
+/// The enum represents a subset of possible `Call`s we can send to Kusama chain.
+/// Ideally this code would be auto-generated from metadata, because we want to
+/// avoid depending directly on the ENTIRE runtime just to get the encoding of `Dispatchable`s.
+///
+/// All entries here (like pretty much in the entire file) must be kept in sync with Kusama
+/// `construct_runtime`, so that we maintain SCALE-compatibility.
+///
+/// See: [link](https://github.com/paritytech/polkadot/blob/master/runtime/kusama/src/lib.rs)
+#[allow(clippy::large_enum_variant)]
+#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
+pub enum Call {
+	/// System pallet.
+	#[codec(index = 0)]
+	System(SystemCall),
+	/// Balances pallet.
+	#[codec(index = 4)]
+	Balances(BalancesCall),
+	/// Polkadot bridge pallet.
+	#[codec(index = 110)]
+	BridgePolkadotGrandpa(BridgePolkadotGrandpaCall),
+	/// Polkadot messages pallet.
+	#[codec(index = 111)]
+	BridgePolkadotMessages(BridgePolkadotMessagesCall),
+}
+
+#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
+#[allow(non_camel_case_types)]
+pub enum SystemCall {
+	#[codec(index = 1)]
+	remark(Vec<u8>),
+}
+
+#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
+#[allow(non_camel_case_types)]
+pub enum BalancesCall {
+	#[codec(index = 0)]
+	transfer(AccountAddress, Compact<Balance>),
+}
+
+#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
+#[allow(non_camel_case_types)]
+pub enum BridgePolkadotGrandpaCall {
+	#[codec(index = 0)]
+	submit_finality_proof(
+		<PolkadotLike as Chain>::Header,
+		bp_header_chain::justification::GrandpaJustification<<PolkadotLike as Chain>::Header>,
+	),
+	#[codec(index = 1)]
+	initialize(bp_header_chain::InitializationData<<PolkadotLike as Chain>::Header>),
+}
+
+#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
+#[allow(non_camel_case_types)]
+pub enum BridgePolkadotMessagesCall {
+	#[codec(index = 2)]
+	update_pallet_parameter(BridgePolkadotMessagesParameter),
+	#[codec(index = 3)]
+	send_message(
+		LaneId,
+		bp_message_dispatch::MessagePayload<
+			bp_kusama::AccountId,
+			bp_polkadot::AccountId,
+			bp_polkadot::AccountPublic,
+			Vec<u8>,
+		>,
+		bp_kusama::Balance,
+	),
+	#[codec(index = 5)]
+	receive_messages_proof(
+		bp_polkadot::AccountId,
+		bridge_runtime_common::messages::target::FromBridgedChainMessagesProof<bp_polkadot::Hash>,
+		u32,
+		Weight,
+	),
+	#[codec(index = 6)]
+	receive_messages_delivery_proof(
+		bridge_runtime_common::messages::source::FromBridgedChainMessagesDeliveryProof<bp_polkadot::Hash>,
+		UnrewardedRelayersState,
+	),
+}
+
+#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
+pub enum BridgePolkadotMessagesParameter {
+	#[codec(index = 0)]
+	PolkadotToKusamaConversionRate(FixedU128),
+}
+
+impl sp_runtime::traits::Dispatchable for Call {
+	type Origin = ();
+	type Config = ();
+	type Info = ();
+	type PostInfo = ();
+
+	fn dispatch(self, _origin: Self::Origin) -> sp_runtime::DispatchResultWithInfo<Self::PostInfo> {
+		unimplemented!("The Call is not expected to be dispatched.")
+	}
+}
diff --git a/bridges/relays/client-polkadot/Cargo.toml b/bridges/relays/client-polkadot/Cargo.toml
index 261f0bee385..663969da66a 100644
--- a/bridges/relays/client-polkadot/Cargo.toml
+++ b/bridges/relays/client-polkadot/Cargo.toml
@@ -6,9 +6,24 @@ edition = "2018"
 license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
 
 [dependencies]
+codec = { package = "parity-scale-codec", version = "2.2.0" }
 relay-substrate-client = { path = "../client-substrate" }
 relay-utils = { path = "../utils" }
 
 # Bridge dependencies
 
+bp-header-chain = { path = "../../primitives/header-chain" }
+bp-kusama = { path = "../../primitives/chain-kusama" }
+bp-message-dispatch = { path = "../../primitives/message-dispatch" }
+bp-messages = { path = "../../primitives/messages" }
 bp-polkadot = { path = "../../primitives/chain-polkadot" }
+bp-polkadot-core = { path = "../../primitives/polkadot-core" }
+bp-runtime = { path = "../../primitives/runtime" }
+bridge-runtime-common = { path = "../../bin/runtime-common" }
+pallet-bridge-dispatch = { path = "../../modules/dispatch" }
+
+# Substrate Dependencies
+
+frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" }
+sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
+sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" }
diff --git a/bridges/relays/client-polkadot/src/lib.rs b/bridges/relays/client-polkadot/src/lib.rs
index 5882d917bf2..3ba84e05bff 100644
--- a/bridges/relays/client-polkadot/src/lib.rs
+++ b/bridges/relays/client-polkadot/src/lib.rs
@@ -16,9 +16,16 @@
 
 //! Types used to connect to the Polkadot chain.
 
-use relay_substrate_client::{Chain, ChainBase};
+use codec::Encode;
+use relay_substrate_client::{
+	Chain, ChainBase, ChainWithBalances, TransactionEraOf, TransactionSignScheme, UnsignedTransaction,
+};
+use sp_core::{storage::StorageKey, Pair};
+use sp_runtime::{generic::SignedPayload, traits::IdentifyAccount};
 use std::time::Duration;
 
+pub mod runtime;
+
 /// Polkadot header id.
 pub type HeaderId = relay_utils::HeaderId<bp_polkadot::Hash, bp_polkadot::BlockNumber>;
 
@@ -45,9 +52,68 @@ impl Chain for Polkadot {
 	const MAXIMAL_ENCODED_ACCOUNT_ID_SIZE: u32 = bp_polkadot::MAXIMAL_ENCODED_ACCOUNT_ID_SIZE;
 
 	type SignedBlock = bp_polkadot::SignedBlock;
-	type Call = ();
+	type Call = crate::runtime::Call;
 	type WeightToFee = bp_polkadot::WeightToFee;
 }
 
+impl ChainWithBalances for Polkadot {
+	fn account_info_storage_key(account_id: &Self::AccountId) -> StorageKey {
+		StorageKey(bp_polkadot::account_info_storage_key(account_id))
+	}
+}
+
+impl TransactionSignScheme for Polkadot {
+	type Chain = Polkadot;
+	type AccountKeyPair = sp_core::sr25519::Pair;
+	type SignedTransaction = crate::runtime::UncheckedExtrinsic;
+
+	fn sign_transaction(
+		genesis_hash: <Self::Chain as ChainBase>::Hash,
+		signer: &Self::AccountKeyPair,
+		era: TransactionEraOf<Self::Chain>,
+		unsigned: UnsignedTransaction<Self::Chain>,
+	) -> Self::SignedTransaction {
+		let raw_payload = SignedPayload::new(
+			unsigned.call,
+			bp_polkadot::SignedExtensions::new(bp_polkadot::VERSION, era, genesis_hash, unsigned.nonce, unsigned.tip),
+		)
+		.expect("SignedExtension never fails.");
+
+		let signature = raw_payload.using_encoded(|payload| signer.sign(payload));
+		let signer: sp_runtime::MultiSigner = signer.public().into();
+		let (call, extra, _) = raw_payload.deconstruct();
+
+		bp_polkadot::UncheckedExtrinsic::new_signed(
+			call,
+			sp_runtime::MultiAddress::Id(signer.into_account()),
+			signature.into(),
+			extra,
+		)
+	}
+
+	fn is_signed(tx: &Self::SignedTransaction) -> bool {
+		tx.signature.is_some()
+	}
+
+	fn is_signed_by(signer: &Self::AccountKeyPair, tx: &Self::SignedTransaction) -> bool {
+		tx.signature
+			.as_ref()
+			.map(|(address, _, _)| *address == bp_polkadot::AccountId::from(*signer.public().as_array_ref()).into())
+			.unwrap_or(false)
+	}
+
+	fn parse_transaction(tx: Self::SignedTransaction) -> Option<UnsignedTransaction<Self::Chain>> {
+		let extra = &tx.signature.as_ref()?.2;
+		Some(UnsignedTransaction {
+			call: tx.function,
+			nonce: extra.nonce(),
+			tip: extra.tip(),
+		})
+	}
+}
+
 /// Polkadot header type used in headers sync.
 pub type SyncHeader = relay_substrate_client::SyncHeader<bp_polkadot::Header>;
+
+/// Polkadot signing params.
+pub type SigningParams = sp_core::sr25519::Pair;
diff --git a/bridges/relays/client-polkadot/src/runtime.rs b/bridges/relays/client-polkadot/src/runtime.rs
new file mode 100644
index 00000000000..63d0987d323
--- /dev/null
+++ b/bridges/relays/client-polkadot/src/runtime.rs
@@ -0,0 +1,151 @@
+// 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/>.
+
+//! Types that are specific to the Polkadot runtime.
+
+use bp_messages::{LaneId, UnrewardedRelayersState};
+use bp_polkadot_core::{AccountAddress, Balance, PolkadotLike};
+use bp_runtime::Chain;
+use codec::{Compact, Decode, Encode};
+use frame_support::weights::Weight;
+use sp_runtime::FixedU128;
+
+/// Unchecked Polkadot extrinsic.
+pub type UncheckedExtrinsic = bp_polkadot_core::UncheckedExtrinsic<Call>;
+
+/// Kusama account ownership digest from Polkadot.
+///
+/// The byte vector returned by this function should be signed with a Kusama account private key.
+/// This way, the owner of `kusam_account_id` on Polkadot proves that the Kusama account private key
+/// is also under his control.
+pub fn polkadot_to_kusama_account_ownership_digest<Call, AccountId, SpecVersion>(
+	kusama_call: &Call,
+	kusam_account_id: AccountId,
+	kusama_spec_version: SpecVersion,
+) -> Vec<u8>
+where
+	Call: codec::Encode,
+	AccountId: codec::Encode,
+	SpecVersion: codec::Encode,
+{
+	pallet_bridge_dispatch::account_ownership_digest(
+		kusama_call,
+		kusam_account_id,
+		kusama_spec_version,
+		bp_runtime::POLKADOT_CHAIN_ID,
+		bp_runtime::KUSAMA_CHAIN_ID,
+	)
+}
+
+/// Polkadot Runtime `Call` enum.
+///
+/// The enum represents a subset of possible `Call`s we can send to Polkadot chain.
+/// Ideally this code would be auto-generated from metadata, because we want to
+/// avoid depending directly on the ENTIRE runtime just to get the encoding of `Dispatchable`s.
+///
+/// All entries here (like pretty much in the entire file) must be kept in sync with Polkadot
+/// `construct_runtime`, so that we maintain SCALE-compatibility.
+///
+/// See: [link](https://github.com/paritytech/kusama/blob/master/runtime/kusam/src/lib.rs)
+#[allow(clippy::large_enum_variant)]
+#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
+pub enum Call {
+	/// System pallet.
+	#[codec(index = 0)]
+	System(SystemCall),
+	/// Balances pallet.
+	#[codec(index = 5)]
+	Balances(BalancesCall),
+	/// Kusama bridge pallet.
+	#[codec(index = 110)]
+	BridgeKusamaGrandpa(BridgeKusamaGrandpaCall),
+	/// Kusama messages pallet.
+	#[codec(index = 111)]
+	BridgeKusamaMessages(BridgeKusamaMessagesCall),
+}
+
+#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
+#[allow(non_camel_case_types)]
+pub enum SystemCall {
+	#[codec(index = 1)]
+	remark(Vec<u8>),
+}
+
+#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
+#[allow(non_camel_case_types)]
+pub enum BalancesCall {
+	#[codec(index = 0)]
+	transfer(AccountAddress, Compact<Balance>),
+}
+
+#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
+#[allow(non_camel_case_types)]
+pub enum BridgeKusamaGrandpaCall {
+	#[codec(index = 0)]
+	submit_finality_proof(
+		<PolkadotLike as Chain>::Header,
+		bp_header_chain::justification::GrandpaJustification<<PolkadotLike as Chain>::Header>,
+	),
+	#[codec(index = 1)]
+	initialize(bp_header_chain::InitializationData<<PolkadotLike as Chain>::Header>),
+}
+
+#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
+#[allow(non_camel_case_types)]
+pub enum BridgeKusamaMessagesCall {
+	#[codec(index = 2)]
+	update_pallet_parameter(BridgeKusamaMessagesParameter),
+	#[codec(index = 3)]
+	send_message(
+		LaneId,
+		bp_message_dispatch::MessagePayload<
+			bp_polkadot::AccountId,
+			bp_kusama::AccountId,
+			bp_kusama::AccountPublic,
+			Vec<u8>,
+		>,
+		bp_polkadot::Balance,
+	),
+	#[codec(index = 5)]
+	receive_messages_proof(
+		bp_kusama::AccountId,
+		bridge_runtime_common::messages::target::FromBridgedChainMessagesProof<bp_kusama::Hash>,
+		u32,
+		Weight,
+	),
+	#[codec(index = 6)]
+	receive_messages_delivery_proof(
+		bridge_runtime_common::messages::source::FromBridgedChainMessagesDeliveryProof<bp_kusama::Hash>,
+		UnrewardedRelayersState,
+	),
+}
+
+#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone)]
+pub enum BridgeKusamaMessagesParameter {
+	#[codec(index = 0)]
+	KusamaToPolkadotConversionRate(FixedU128),
+}
+
+impl sp_runtime::traits::Dispatchable for Call {
+	type Origin = ();
+	type Config = ();
+	type Info = ();
+	type PostInfo = ();
+
+	fn dispatch(self, _origin: Self::Origin) -> sp_runtime::DispatchResultWithInfo<Self::PostInfo> {
+		unimplemented!("The Call is not expected to be dispatched.")
+	}
+}
diff --git a/bridges/relays/client-rococo/Cargo.toml b/bridges/relays/client-rococo/Cargo.toml
index 1ce781be72e..38ec9eb1887 100644
--- a/bridges/relays/client-rococo/Cargo.toml
+++ b/bridges/relays/client-rococo/Cargo.toml
@@ -11,6 +11,7 @@ relay-substrate-client = { path = "../client-substrate" }
 relay-utils = { path = "../utils" }
 
 # Bridge dependencies
+
 bridge-runtime-common = { path = "../../bin/runtime-common" }
 bp-header-chain = { path = "../../primitives/header-chain" }
 bp-message-dispatch = { path = "../../primitives/message-dispatch" }
@@ -23,6 +24,7 @@ pallet-bridge-dispatch = { path = "../../modules/dispatch" }
 pallet-bridge-messages = { path = "../../modules/messages" }
 
 # Substrate Dependencies
+
 frame-support = { git = "https://github.com/paritytech/substrate", branch = "master" }
 sp-core = { git = "https://github.com/paritytech/substrate", branch = "master" }
 sp-runtime = { git = "https://github.com/paritytech/substrate", branch = "master" }
diff --git a/bridges/relays/client-substrate/src/lib.rs b/bridges/relays/client-substrate/src/lib.rs
index 8415657060d..173e58d386f 100644
--- a/bridges/relays/client-substrate/src/lib.rs
+++ b/bridges/relays/client-substrate/src/lib.rs
@@ -38,7 +38,8 @@ pub use crate::client::{Client, JustificationsSubscription, OpaqueGrandpaAuthori
 pub use crate::error::{Error, Result};
 pub use crate::sync_header::SyncHeader;
 pub use bp_runtime::{
-	BalanceOf, BlockNumberOf, Chain as ChainBase, HashOf, HeaderOf, IndexOf, TransactionEra, TransactionEraOf,
+	AccountIdOf, BalanceOf, BlockNumberOf, Chain as ChainBase, HashOf, HeaderOf, IndexOf, TransactionEra,
+	TransactionEraOf,
 };
 
 /// Header id used by the chain.
-- 
GitLab