diff --git a/bridges/relays/bin-ethereum/src/rialto_client.rs b/bridges/relays/bin-ethereum/src/rialto_client.rs
index 11eb200a1c3160adb9f484861d603ce044ffc333..c6d731b841c1252af775b8a517d9ec8e71fdafbb 100644
--- a/bridges/relays/bin-ethereum/src/rialto_client.rs
+++ b/bridges/relays/bin-ethereum/src/rialto_client.rs
@@ -156,13 +156,21 @@ impl SubmitEthereumHeaders for SubstrateClient<Rialto> {
 	) -> SubmittedHeaders<EthereumHeaderId, RpcError> {
 		let ids = headers.iter().map(|header| header.id()).collect();
 		let submission_result = async {
-			let account_id = params.signer.public().as_array_ref().clone().into();
-			let nonce = self.next_account_index(account_id).await?;
-
-			let call = instance.build_signed_header_call(headers);
-			let transaction = Rialto::sign_transaction(*self.genesis_hash(), &params.signer, nonce, call);
-
-			let _ = self.submit_extrinsic(Bytes(transaction.encode())).await?;
+			self.submit_signed_extrinsic(
+				params.signer.public().as_array_ref().clone().into(),
+				|transaction_nonce| {
+					Bytes(
+						Rialto::sign_transaction(
+							*self.genesis_hash(),
+							&params.signer,
+							transaction_nonce,
+							instance.build_signed_header_call(headers),
+						)
+						.encode(),
+					)
+				},
+			)
+			.await?;
 			Ok(())
 		}
 		.await;
@@ -197,7 +205,7 @@ impl SubmitEthereumHeaders for SubstrateClient<Rialto> {
 			let call = instance.build_unsigned_header_call(header);
 			let transaction = create_unsigned_submit_transaction(call);
 
-			match self.submit_extrinsic(Bytes(transaction.encode())).await {
+			match self.submit_unsigned_extrinsic(Bytes(transaction.encode())).await {
 				Ok(_) => submitted_headers.submitted.push(id),
 				Err(error) => {
 					submitted_headers.rejected.push(id);
@@ -252,13 +260,21 @@ impl SubmitEthereumExchangeTransactionProof for SubstrateClient<Rialto> {
 		instance: Arc<dyn BridgeInstance>,
 		proof: rialto_runtime::exchange::EthereumTransactionInclusionProof,
 	) -> RpcResult<()> {
-		let account_id = params.signer.public().as_array_ref().clone().into();
-		let nonce = self.next_account_index(account_id).await?;
-
-		let call = instance.build_currency_exchange_call(proof);
-		let transaction = Rialto::sign_transaction(*self.genesis_hash(), &params.signer, nonce, call);
-
-		let _ = self.submit_extrinsic(Bytes(transaction.encode())).await?;
+		self.submit_signed_extrinsic(
+			params.signer.public().as_array_ref().clone().into(),
+			|transaction_nonce| {
+				Bytes(
+					Rialto::sign_transaction(
+						*self.genesis_hash(),
+						&params.signer,
+						transaction_nonce,
+						instance.build_currency_exchange_call(proof),
+					)
+					.encode(),
+				)
+			},
+		)
+		.await?;
 		Ok(())
 	}
 }
diff --git a/bridges/relays/bin-substrate/src/finality_pipeline.rs b/bridges/relays/bin-substrate/src/finality_pipeline.rs
index 0ddc4a9a8de980881ebeb8f36b22b0992bf36461..d5ae55162e3f8b012349caae6429c080b523e2b8 100644
--- a/bridges/relays/bin-substrate/src/finality_pipeline.rs
+++ b/bridges/relays/bin-substrate/src/finality_pipeline.rs
@@ -18,14 +18,13 @@
 
 use crate::finality_target::SubstrateFinalityTarget;
 
-use async_trait::async_trait;
-use codec::Encode;
 use finality_relay::{FinalitySyncParams, FinalitySyncPipeline};
 use relay_substrate_client::{
 	finality_source::{FinalitySource, Justification},
-	BlockNumberOf, Chain, Client, Error as SubstrateError, HashOf, SyncHeader,
+	BlockNumberOf, Chain, Client, HashOf, SyncHeader,
 };
 use relay_utils::BlockNumberBase;
+use sp_core::Bytes;
 use std::{fmt::Debug, marker::PhantomData, time::Duration};
 
 /// Default synchronization loop timeout.
@@ -37,20 +36,23 @@ const STALL_TIMEOUT: Duration = Duration::from_secs(120);
 const RECENT_FINALITY_PROOFS_LIMIT: usize = 4096;
 
 /// Headers sync pipeline for Substrate <-> Substrate relays.
-#[async_trait]
 pub trait SubstrateFinalitySyncPipeline: FinalitySyncPipeline {
 	/// Name of the runtime method that returns id of best finalized source header at target chain.
 	const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str;
 
-	/// Signed transaction type.
-	type SignedTransaction: Send + Sync + Encode;
+	/// Chain with GRANDPA bridge pallet.
+	type TargetChain: Chain;
+
+	/// Returns id of account that we're using to sign transactions at target chain.
+	fn transactions_author(&self) -> <Self::TargetChain as Chain>::AccountId;
 
 	/// Make submit header transaction.
-	async fn make_submit_finality_proof_transaction(
+	fn make_submit_finality_proof_transaction(
 		&self,
+		transaction_nonce: <Self::TargetChain as Chain>::Index,
 		header: Self::Header,
 		proof: Self::FinalityProof,
-	) -> Result<Self::SignedTransaction, SubstrateError>;
+	) -> Bytes;
 }
 
 /// Substrate-to-Substrate finality proof pipeline.
@@ -105,6 +107,7 @@ where
 		Number = BlockNumberOf<SourceChain>,
 		Header = SyncHeader<SourceChain::Header>,
 		FinalityProof = Justification<SourceChain::BlockNumber>,
+		TargetChain = TargetChain,
 	>,
 	SourceChain: Clone + Chain,
 	BlockNumberOf<SourceChain>: BlockNumberBase,
diff --git a/bridges/relays/bin-substrate/src/finality_target.rs b/bridges/relays/bin-substrate/src/finality_target.rs
index 18312556f9e172997270db0ddd9013d67b99c8df..37f433ad06d058780c7f9af4c73b27416e201048 100644
--- a/bridges/relays/bin-substrate/src/finality_target.rs
+++ b/bridges/relays/bin-substrate/src/finality_target.rs
@@ -21,12 +21,10 @@
 use crate::finality_pipeline::SubstrateFinalitySyncPipeline;
 
 use async_trait::async_trait;
-use codec::{Decode, Encode};
+use codec::Decode;
 use finality_relay::TargetClient;
-use futures::TryFutureExt;
 use relay_substrate_client::{Chain, Client, Error as SubstrateError};
 use relay_utils::relay_loop::Client as RelayClient;
-use sp_core::Bytes;
 
 /// Substrate client as Substrate finality target.
 pub struct SubstrateFinalityTarget<C: Chain, P> {
@@ -65,7 +63,7 @@ where
 	C: Chain,
 	P::Number: Decode,
 	P::Hash: Decode,
-	P: SubstrateFinalitySyncPipeline,
+	P: SubstrateFinalitySyncPipeline<TargetChain = C>,
 {
 	async fn best_finalized_source_block_number(&self) -> Result<P::Number, SubstrateError> {
 		// we can't continue to relay finality if target node is out of sync, because
@@ -82,9 +80,11 @@ where
 	}
 
 	async fn submit_finality_proof(&self, header: P::Header, proof: P::FinalityProof) -> Result<(), SubstrateError> {
-		self.pipeline
-			.make_submit_finality_proof_transaction(header, proof)
-			.and_then(|tx| self.client.submit_extrinsic(Bytes(tx.encode())))
+		self.client
+			.submit_signed_extrinsic(self.pipeline.transactions_author(), move |transaction_nonce| {
+				self.pipeline
+					.make_submit_finality_proof_transaction(transaction_nonce, header, proof)
+			})
 			.await
 			.map(drop)
 	}
diff --git a/bridges/relays/bin-substrate/src/headers_initialize.rs b/bridges/relays/bin-substrate/src/headers_initialize.rs
index 768d299acde773962047b53565b7413c7bcd1453..bc191df3b71532e9fcf811eba3f63d9c7641da70 100644
--- a/bridges/relays/bin-substrate/src/headers_initialize.rs
+++ b/bridges/relays/bin-substrate/src/headers_initialize.rs
@@ -38,9 +38,16 @@ use sp_runtime::traits::Header as HeaderT;
 pub async fn initialize<SourceChain: Chain, TargetChain: Chain>(
 	source_client: Client<SourceChain>,
 	target_client: Client<TargetChain>,
-	prepare_initialize_transaction: impl FnOnce(InitializationData<SourceChain::Header>) -> Result<Bytes, String>,
+	target_transactions_signer: TargetChain::AccountId,
+	prepare_initialize_transaction: impl FnOnce(TargetChain::Index, InitializationData<SourceChain::Header>) -> Bytes,
 ) {
-	let result = do_initialize(source_client, target_client, prepare_initialize_transaction).await;
+	let result = do_initialize(
+		source_client,
+		target_client,
+		target_transactions_signer,
+		prepare_initialize_transaction,
+	)
+	.await;
 
 	match result {
 		Ok(tx_hash) => log::info!(
@@ -64,7 +71,8 @@ pub async fn initialize<SourceChain: Chain, TargetChain: Chain>(
 async fn do_initialize<SourceChain: Chain, TargetChain: Chain>(
 	source_client: Client<SourceChain>,
 	target_client: Client<TargetChain>,
-	prepare_initialize_transaction: impl FnOnce(InitializationData<SourceChain::Header>) -> Result<Bytes, String>,
+	target_transactions_signer: TargetChain::AccountId,
+	prepare_initialize_transaction: impl FnOnce(TargetChain::Index, InitializationData<SourceChain::Header>) -> Bytes,
 ) -> Result<TargetChain::Hash, String> {
 	let initialization_data = prepare_initialization_data(source_client).await?;
 	log::info!(
@@ -75,9 +83,10 @@ async fn do_initialize<SourceChain: Chain, TargetChain: Chain>(
 		initialization_data,
 	);
 
-	let initialization_tx = prepare_initialize_transaction(initialization_data)?;
 	let initialization_tx_hash = target_client
-		.submit_extrinsic(initialization_tx)
+		.submit_signed_extrinsic(target_transactions_signer, move |transaction_nonce| {
+			prepare_initialize_transaction(transaction_nonce, initialization_data)
+		})
 		.await
 		.map_err(|err| format!("Failed to submit {} transaction: {:?}", TargetChain::NAME, err))?;
 	Ok(initialization_tx_hash)
diff --git a/bridges/relays/bin-substrate/src/messages_lane.rs b/bridges/relays/bin-substrate/src/messages_lane.rs
index 372b47f2a2635da801cecc2e5e2e38120590d23b..01814ad6a9c6fedea1d1c9ce41a6e3ccd7fafda3 100644
--- a/bridges/relays/bin-substrate/src/messages_lane.rs
+++ b/bridges/relays/bin-substrate/src/messages_lane.rs
@@ -17,17 +17,15 @@
 use crate::messages_source::SubstrateMessagesProof;
 use crate::messages_target::SubstrateMessagesReceivingProof;
 
-use async_trait::async_trait;
 use bp_messages::MessageNonce;
-use codec::Encode;
 use frame_support::weights::Weight;
 use messages_relay::message_lane::{MessageLane, SourceHeaderIdOf, TargetHeaderIdOf};
-use relay_substrate_client::{BlockNumberOf, Chain, Client, Error as SubstrateError, HashOf};
+use relay_substrate_client::{BlockNumberOf, Chain, Client, HashOf};
 use relay_utils::BlockNumberBase;
+use sp_core::Bytes;
 use std::ops::RangeInclusive;
 
 /// Message sync pipeline for Substrate <-> Substrate relays.
-#[async_trait]
 pub trait SubstrateMessageLane: MessageLane {
 	/// Name of the runtime method that returns dispatch weight of outbound messages at the source chain.
 	const OUTBOUND_LANE_MESSAGES_DISPATCH_WEIGHT_METHOD: &'static str;
@@ -48,25 +46,33 @@ pub trait SubstrateMessageLane: MessageLane {
 	/// Name of the runtime method that returns id of best finalized target header at source chain.
 	const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str;
 
-	/// Signed transaction type of the source chain.
-	type SourceSignedTransaction: Send + Sync + Encode;
-	/// Signed transaction type of the target chain.
-	type TargetSignedTransaction: Send + Sync + Encode;
+	/// Source chain.
+	type SourceChain: Chain;
+	/// Target chain.
+	type TargetChain: Chain;
+
+	/// Returns id of account that we're using to sign transactions at target chain (messages proof).
+	fn target_transactions_author(&self) -> <Self::TargetChain as Chain>::AccountId;
 
 	/// Make messages delivery transaction.
-	async fn make_messages_delivery_transaction(
+	fn make_messages_delivery_transaction(
 		&self,
+		transaction_nonce: <Self::TargetChain as Chain>::Index,
 		generated_at_header: SourceHeaderIdOf<Self>,
 		nonces: RangeInclusive<MessageNonce>,
 		proof: Self::MessagesProof,
-	) -> Result<Self::TargetSignedTransaction, SubstrateError>;
+	) -> Bytes;
+
+	/// Returns id of account that we're using to sign transactions at source chain (delivery proof).
+	fn source_transactions_author(&self) -> <Self::SourceChain as Chain>::AccountId;
 
 	/// Make messages receiving proof transaction.
-	async fn make_messages_receiving_proof_transaction(
+	fn make_messages_receiving_proof_transaction(
 		&self,
+		transaction_nonce: <Self::SourceChain as Chain>::Index,
 		generated_at_header: TargetHeaderIdOf<Self>,
 		proof: Self::MessagesReceivingProof,
-	) -> Result<Self::SourceSignedTransaction, SubstrateError>;
+	) -> Bytes;
 }
 
 /// Substrate-to-Substrate message lane.
diff --git a/bridges/relays/bin-substrate/src/messages_source.rs b/bridges/relays/bin-substrate/src/messages_source.rs
index 607247f8c003380683328c66801d1b19b2976a63..278fff6d2d99ef23cb810a9f3b2a9ae6f91f9959 100644
--- a/bridges/relays/bin-substrate/src/messages_source.rs
+++ b/bridges/relays/bin-substrate/src/messages_source.rs
@@ -94,6 +94,7 @@ where
 		MessagesProof = SubstrateMessagesProof<C>,
 		SourceHeaderNumber = <C::Header as HeaderT>::Number,
 		SourceHeaderHash = <C::Header as HeaderT>::Hash,
+		SourceChain = C,
 	>,
 	P::TargetHeaderNumber: Decode,
 	P::TargetHeaderHash: Decode,
@@ -197,13 +198,16 @@ where
 		generated_at_block: TargetHeaderIdOf<P>,
 		proof: P::MessagesReceivingProof,
 	) -> Result<(), SubstrateError> {
-		let tx = self
-			.lane
-			.make_messages_receiving_proof_transaction(generated_at_block, proof)
+		self.client
+			.submit_signed_extrinsic(self.lane.source_transactions_author(), move |transaction_nonce| {
+				self.lane
+					.make_messages_receiving_proof_transaction(transaction_nonce, generated_at_block, proof)
+			})
 			.await?;
-		self.client.submit_extrinsic(Bytes(tx.encode())).await?;
 		Ok(())
 	}
+
+	async fn activate_target_to_source_headers_relay(&self, _activate: bool) {}
 }
 
 pub async fn read_client_state<SelfChain, BridgedHeaderHash, BridgedHeaderNumber>(
diff --git a/bridges/relays/bin-substrate/src/messages_target.rs b/bridges/relays/bin-substrate/src/messages_target.rs
index ec2d751d377859cbc60a058f927a73d8a9252c88..3d8ab4fd0045f7e7b8fb4703dfb529fe59412e4f 100644
--- a/bridges/relays/bin-substrate/src/messages_target.rs
+++ b/bridges/relays/bin-substrate/src/messages_target.rs
@@ -90,6 +90,7 @@ where
 	C::Index: DeserializeOwned,
 	<C::Header as HeaderT>::Number: BlockNumberBase,
 	P: SubstrateMessageLane<
+		TargetChain = C,
 		MessagesReceivingProof = SubstrateMessagesReceivingProof<C>,
 		TargetHeaderNumber = <C::Header as HeaderT>::Number,
 		TargetHeaderHash = <C::Header as HeaderT>::Hash,
@@ -183,11 +184,18 @@ where
 		nonces: RangeInclusive<MessageNonce>,
 		proof: P::MessagesProof,
 	) -> Result<RangeInclusive<MessageNonce>, SubstrateError> {
-		let tx = self
-			.lane
-			.make_messages_delivery_transaction(generated_at_header, nonces.clone(), proof)
+		self.client
+			.submit_signed_extrinsic(self.lane.target_transactions_author(), |transaction_nonce| {
+				self.lane.make_messages_delivery_transaction(
+					transaction_nonce,
+					generated_at_header,
+					nonces.clone(),
+					proof,
+				)
+			})
 			.await?;
-		self.client.submit_extrinsic(Bytes(tx.encode())).await?;
 		Ok(nonces)
 	}
+
+	async fn activate_source_to_target_headers_relay(&self, _activate: bool) {}
 }
diff --git a/bridges/relays/bin-substrate/src/rialto_millau/millau_headers_to_rialto.rs b/bridges/relays/bin-substrate/src/rialto_millau/millau_headers_to_rialto.rs
index a14fb691275a2de84631b67caf6a30d7934380d8..5a30974ba4c47794b248f917af2a6a500914313a 100644
--- a/bridges/relays/bin-substrate/src/rialto_millau/millau_headers_to_rialto.rs
+++ b/bridges/relays/bin-substrate/src/rialto_millau/millau_headers_to_rialto.rs
@@ -19,36 +19,38 @@
 use super::{MillauClient, RialtoClient};
 use crate::finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate};
 
-use async_trait::async_trait;
+use codec::Encode;
 use relay_millau_client::{Millau, SyncHeader as MillauSyncHeader};
 use relay_rialto_client::{Rialto, SigningParams as RialtoSigningParams};
-use relay_substrate_client::{finality_source::Justification, Error as SubstrateError, TransactionSignScheme};
-use sp_core::Pair;
+use relay_substrate_client::{finality_source::Justification, Chain, TransactionSignScheme};
+use sp_core::{Bytes, Pair};
 
 /// Millau-to-Rialto finality sync pipeline.
 pub(crate) type MillauFinalityToRialto = SubstrateFinalityToSubstrate<Millau, Rialto, RialtoSigningParams>;
 
-#[async_trait]
 impl SubstrateFinalitySyncPipeline for MillauFinalityToRialto {
 	const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_millau::BEST_FINALIZED_MILLAU_HEADER_METHOD;
 
-	type SignedTransaction = <Rialto as TransactionSignScheme>::SignedTransaction;
+	type TargetChain = Rialto;
 
-	async fn make_submit_finality_proof_transaction(
+	fn transactions_author(&self) -> bp_rialto::AccountId {
+		self.target_sign.signer.public().as_array_ref().clone().into()
+	}
+
+	fn make_submit_finality_proof_transaction(
 		&self,
+		transaction_nonce: <Rialto as Chain>::Index,
 		header: MillauSyncHeader,
 		proof: Justification<bp_millau::BlockNumber>,
-	) -> Result<Self::SignedTransaction, SubstrateError> {
-		let account_id = self.target_sign.signer.public().as_array_ref().clone().into();
-		let nonce = self.target_client.next_account_index(account_id).await?;
+	) -> Bytes {
 		let call =
 			rialto_runtime::BridgeGrandpaMillauCall::submit_finality_proof(header.into_inner(), proof.into_inner())
 				.into();
 
 		let genesis_hash = *self.target_client.genesis_hash();
-		let transaction = Rialto::sign_transaction(genesis_hash, &self.target_sign.signer, nonce, call);
+		let transaction = Rialto::sign_transaction(genesis_hash, &self.target_sign.signer, transaction_nonce, call);
 
-		Ok(transaction)
+		Bytes(transaction.encode())
 	}
 }
 
diff --git a/bridges/relays/bin-substrate/src/rialto_millau/millau_messages_to_rialto.rs b/bridges/relays/bin-substrate/src/rialto_millau/millau_messages_to_rialto.rs
index df2cb3a03dee73437d7aad73a87f08832f36f9fa..edfd907776500f277e2e5608e00b1ef4b6fafdde 100644
--- a/bridges/relays/bin-substrate/src/rialto_millau/millau_messages_to_rialto.rs
+++ b/bridges/relays/bin-substrate/src/rialto_millau/millau_messages_to_rialto.rs
@@ -21,7 +21,6 @@ use crate::messages_lane::{select_delivery_transaction_limits, SubstrateMessageL
 use crate::messages_source::SubstrateMessagesSource;
 use crate::messages_target::SubstrateMessagesTarget;
 
-use async_trait::async_trait;
 use bp_messages::{LaneId, MessageNonce};
 use bp_runtime::{MILLAU_BRIDGE_INSTANCE, RIALTO_BRIDGE_INSTANCE};
 use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
@@ -30,15 +29,14 @@ use frame_support::dispatch::GetDispatchInfo;
 use messages_relay::message_lane::MessageLane;
 use relay_millau_client::{HeaderId as MillauHeaderId, Millau, SigningParams as MillauSigningParams};
 use relay_rialto_client::{HeaderId as RialtoHeaderId, Rialto, SigningParams as RialtoSigningParams};
-use relay_substrate_client::{Chain, Error as SubstrateError, TransactionSignScheme};
+use relay_substrate_client::{Chain, TransactionSignScheme};
 use relay_utils::metrics::MetricsParams;
-use sp_core::Pair;
+use sp_core::{Bytes, Pair};
 use std::{ops::RangeInclusive, time::Duration};
 
 /// Millau-to-Rialto message lane.
 type MillauMessagesToRialto = SubstrateMessageLaneToSubstrate<Millau, MillauSigningParams, Rialto, RialtoSigningParams>;
 
-#[async_trait]
 impl SubstrateMessageLane for MillauMessagesToRialto {
 	const OUTBOUND_LANE_MESSAGES_DISPATCH_WEIGHT_METHOD: &'static str =
 		bp_rialto::TO_RIALTO_MESSAGES_DISPATCH_WEIGHT_METHOD;
@@ -54,22 +52,25 @@ impl SubstrateMessageLane for MillauMessagesToRialto {
 	const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_millau::BEST_FINALIZED_MILLAU_HEADER_METHOD;
 	const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str = bp_rialto::BEST_FINALIZED_RIALTO_HEADER_METHOD;
 
-	type SourceSignedTransaction = <Millau as TransactionSignScheme>::SignedTransaction;
-	type TargetSignedTransaction = <Rialto as TransactionSignScheme>::SignedTransaction;
+	type SourceChain = Millau;
+	type TargetChain = Rialto;
 
-	async fn make_messages_receiving_proof_transaction(
+	fn source_transactions_author(&self) -> bp_rialto::AccountId {
+		self.source_sign.signer.public().as_array_ref().clone().into()
+	}
+
+	fn make_messages_receiving_proof_transaction(
 		&self,
+		transaction_nonce: <Millau as Chain>::Index,
 		_generated_at_block: RialtoHeaderId,
 		proof: <Self as MessageLane>::MessagesReceivingProof,
-	) -> Result<Self::SourceSignedTransaction, SubstrateError> {
+	) -> Bytes {
 		let (relayers_state, proof) = proof;
-		let account_id = self.source_sign.signer.public().as_array_ref().clone().into();
-		let nonce = self.source_client.next_account_index(account_id).await?;
 		let call: millau_runtime::Call =
 			millau_runtime::MessagesCall::receive_messages_delivery_proof(proof, relayers_state).into();
 		let call_weight = call.get_dispatch_info().weight;
 		let genesis_hash = *self.source_client.genesis_hash();
-		let transaction = Millau::sign_transaction(genesis_hash, &self.source_sign.signer, nonce, call);
+		let transaction = Millau::sign_transaction(genesis_hash, &self.source_sign.signer, transaction_nonce, call);
 		log::trace!(
 			target: "bridge",
 			"Prepared Rialto -> Millau confirmation transaction. Weight: {}/{}, size: {}/{}",
@@ -78,15 +79,20 @@ impl SubstrateMessageLane for MillauMessagesToRialto {
 			transaction.encode().len(),
 			bp_millau::max_extrinsic_size(),
 		);
-		Ok(transaction)
+		Bytes(transaction.encode())
+	}
+
+	fn target_transactions_author(&self) -> bp_rialto::AccountId {
+		self.target_sign.signer.public().as_array_ref().clone().into()
 	}
 
-	async fn make_messages_delivery_transaction(
+	fn make_messages_delivery_transaction(
 		&self,
+		transaction_nonce: <Rialto as Chain>::Index,
 		_generated_at_header: MillauHeaderId,
 		_nonces: RangeInclusive<MessageNonce>,
 		proof: <Self as MessageLane>::MessagesProof,
-	) -> Result<Self::TargetSignedTransaction, SubstrateError> {
+	) -> Bytes {
 		let (dispatch_weight, proof) = proof;
 		let FromBridgedChainMessagesProof {
 			ref nonces_start,
@@ -94,8 +100,6 @@ impl SubstrateMessageLane for MillauMessagesToRialto {
 			..
 		} = proof;
 		let messages_count = nonces_end - nonces_start + 1;
-		let account_id = self.target_sign.signer.public().as_array_ref().clone().into();
-		let nonce = self.target_client.next_account_index(account_id).await?;
 		let call: rialto_runtime::Call = rialto_runtime::MessagesCall::receive_messages_proof(
 			self.relayer_id_at_source.clone(),
 			proof,
@@ -105,7 +109,7 @@ impl SubstrateMessageLane for MillauMessagesToRialto {
 		.into();
 		let call_weight = call.get_dispatch_info().weight;
 		let genesis_hash = *self.target_client.genesis_hash();
-		let transaction = Rialto::sign_transaction(genesis_hash, &self.target_sign.signer, nonce, call);
+		let transaction = Rialto::sign_transaction(genesis_hash, &self.target_sign.signer, transaction_nonce, call);
 		log::trace!(
 			target: "bridge",
 			"Prepared Millau -> Rialto delivery transaction. Weight: {}/{}, size: {}/{}",
@@ -114,7 +118,7 @@ impl SubstrateMessageLane for MillauMessagesToRialto {
 			transaction.encode().len(),
 			bp_rialto::max_extrinsic_size(),
 		);
-		Ok(transaction)
+		Bytes(transaction.encode())
 	}
 }
 
diff --git a/bridges/relays/bin-substrate/src/rialto_millau/mod.rs b/bridges/relays/bin-substrate/src/rialto_millau/mod.rs
index eadee1484f7b5c1108d58bdfda244abc040cc3e3..df9e36a0c450cf0daaafe8956289188ece7c1f2d 100644
--- a/bridges/relays/bin-substrate/src/rialto_millau/mod.rs
+++ b/bridges/relays/bin-substrate/src/rialto_millau/mod.rs
@@ -53,24 +53,25 @@ async fn run_init_bridge(command: cli::InitBridge) -> Result<(), String> {
 			let rialto_client = rialto.into_client().await?;
 			let rialto_sign = rialto_sign.parse()?;
 
-			let rialto_signer_next_index = rialto_client
-				.next_account_index(rialto_sign.signer.public().into())
-				.await?;
-
-			crate::headers_initialize::initialize(millau_client, rialto_client.clone(), move |initialization_data| {
-				Ok(Bytes(
-					Rialto::sign_transaction(
-						*rialto_client.genesis_hash(),
-						&rialto_sign.signer,
-						rialto_signer_next_index,
-						rialto_runtime::SudoCall::sudo(Box::new(
-							rialto_runtime::BridgeGrandpaMillauCall::initialize(initialization_data).into(),
-						))
-						.into(),
+			crate::headers_initialize::initialize(
+				millau_client,
+				rialto_client.clone(),
+				rialto_sign.signer.public().into(),
+				move |transaction_nonce, initialization_data| {
+					Bytes(
+						Rialto::sign_transaction(
+							*rialto_client.genesis_hash(),
+							&rialto_sign.signer,
+							transaction_nonce,
+							rialto_runtime::SudoCall::sudo(Box::new(
+								rialto_runtime::BridgeGrandpaMillauCall::initialize(initialization_data).into(),
+							))
+							.into(),
+						)
+						.encode(),
 					)
-					.encode(),
-				))
-			})
+				},
+			)
 			.await;
 		}
 		cli::InitBridge::RialtoToMillau {
@@ -81,26 +82,28 @@ async fn run_init_bridge(command: cli::InitBridge) -> Result<(), String> {
 			let rialto_client = rialto.into_client().await?;
 			let millau_client = millau.into_client().await?;
 			let millau_sign = millau_sign.parse()?;
-			let millau_signer_next_index = millau_client
-				.next_account_index(millau_sign.signer.public().into())
-				.await?;
 
-			crate::headers_initialize::initialize(rialto_client, millau_client.clone(), move |initialization_data| {
-				let initialize_call = millau_runtime::BridgeGrandpaRialtoCall::<
-					millau_runtime::Runtime,
-					millau_runtime::RialtoGrandpaInstance,
-				>::initialize(initialization_data);
-
-				Ok(Bytes(
-					Millau::sign_transaction(
-						*millau_client.genesis_hash(),
-						&millau_sign.signer,
-						millau_signer_next_index,
-						millau_runtime::SudoCall::sudo(Box::new(initialize_call.into())).into(),
+			crate::headers_initialize::initialize(
+				rialto_client,
+				millau_client.clone(),
+				millau_sign.signer.public().into(),
+				move |transaction_nonce, initialization_data| {
+					let initialize_call = millau_runtime::BridgeGrandpaRialtoCall::<
+						millau_runtime::Runtime,
+						millau_runtime::RialtoGrandpaInstance,
+					>::initialize(initialization_data);
+
+					Bytes(
+						Millau::sign_transaction(
+							*millau_client.genesis_hash(),
+							&millau_sign.signer,
+							transaction_nonce,
+							millau_runtime::SudoCall::sudo(Box::new(initialize_call.into())).into(),
+						)
+						.encode(),
 					)
-					.encode(),
-				))
-			})
+				},
+			)
 			.await;
 		}
 		cli::InitBridge::WestendToMillau {
@@ -111,29 +114,31 @@ async fn run_init_bridge(command: cli::InitBridge) -> Result<(), String> {
 			let westend_client = westend.into_client().await?;
 			let millau_client = millau.into_client().await?;
 			let millau_sign = millau_sign.parse()?;
-			let millau_signer_next_index = millau_client
-				.next_account_index(millau_sign.signer.public().into())
-				.await?;
 
 			// at Westend -> Millau initialization we're not using sudo, because otherwise our deployments
 			// may fail, because we need to initialize both Rialto -> Millau and Westend -> Millau bridge.
 			// => since there's single possible sudo account, one of transaction may fail with duplicate nonce error
-			crate::headers_initialize::initialize(westend_client, millau_client.clone(), move |initialization_data| {
-				let initialize_call = millau_runtime::BridgeGrandpaWestendCall::<
-					millau_runtime::Runtime,
-					millau_runtime::WestendGrandpaInstance,
-				>::initialize(initialization_data);
-
-				Ok(Bytes(
-					Millau::sign_transaction(
-						*millau_client.genesis_hash(),
-						&millau_sign.signer,
-						millau_signer_next_index,
-						initialize_call.into(),
+			crate::headers_initialize::initialize(
+				westend_client,
+				millau_client.clone(),
+				millau_sign.signer.public().into(),
+				move |transaction_nonce, initialization_data| {
+					let initialize_call = millau_runtime::BridgeGrandpaWestendCall::<
+						millau_runtime::Runtime,
+						millau_runtime::WestendGrandpaInstance,
+					>::initialize(initialization_data);
+
+					Bytes(
+						Millau::sign_transaction(
+							*millau_client.genesis_hash(),
+							&millau_sign.signer,
+							transaction_nonce,
+							initialize_call.into(),
+						)
+						.encode(),
 					)
-					.encode(),
-				))
-			})
+				},
+			)
 			.await;
 		}
 	}
@@ -262,30 +267,32 @@ async fn run_send_message(command: cli::SendMessage) -> Result<(), String> {
 			})
 			.await?;
 
-			let millau_call = millau_runtime::Call::BridgeRialtoMessages(millau_runtime::MessagesCall::send_message(
-				lane, payload, fee,
-			));
-
-			let signed_millau_call = Millau::sign_transaction(
-				*millau_client.genesis_hash(),
-				&millau_sign.signer,
-				millau_client
-					.next_account_index(millau_sign.signer.public().clone().into())
-					.await?,
-				millau_call,
-			)
-			.encode();
-
-			log::info!(
-				target: "bridge",
-				"Sending message to Rialto. Size: {}. Dispatch weight: {}. Fee: {}",
-				signed_millau_call.len(),
-				dispatch_weight,
-				fee,
-			);
-			log::info!(target: "bridge", "Signed Millau Call: {:?}", HexBytes::encode(&signed_millau_call));
-
-			millau_client.submit_extrinsic(Bytes(signed_millau_call)).await?;
+			millau_client
+				.submit_signed_extrinsic(millau_sign.signer.public().clone().into(), |transaction_nonce| {
+					let millau_call = millau_runtime::Call::BridgeRialtoMessages(
+						millau_runtime::MessagesCall::send_message(lane, payload, fee),
+					);
+
+					let signed_millau_call = Millau::sign_transaction(
+						*millau_client.genesis_hash(),
+						&millau_sign.signer,
+						transaction_nonce,
+						millau_call,
+					)
+					.encode();
+
+					log::info!(
+						target: "bridge",
+						"Sending message to Rialto. Size: {}. Dispatch weight: {}. Fee: {}",
+						signed_millau_call.len(),
+						dispatch_weight,
+						fee,
+					);
+					log::info!(target: "bridge", "Signed Millau Call: {:?}", HexBytes::encode(&signed_millau_call));
+
+					Bytes(signed_millau_call)
+				})
+				.await?;
 		}
 		cli::SendMessage::RialtoToMillau {
 			rialto,
@@ -318,30 +325,32 @@ async fn run_send_message(command: cli::SendMessage) -> Result<(), String> {
 			})
 			.await?;
 
-			let rialto_call = rialto_runtime::Call::BridgeMillauMessages(rialto_runtime::MessagesCall::send_message(
-				lane, payload, fee,
-			));
-
-			let signed_rialto_call = Rialto::sign_transaction(
-				*rialto_client.genesis_hash(),
-				&rialto_sign.signer,
-				rialto_client
-					.next_account_index(rialto_sign.signer.public().clone().into())
-					.await?,
-				rialto_call,
-			)
-			.encode();
-
-			log::info!(
-				target: "bridge",
-				"Sending message to Millau. Size: {}. Dispatch weight: {}. Fee: {}",
-				signed_rialto_call.len(),
-				dispatch_weight,
-				fee,
-			);
-			log::info!(target: "bridge", "Signed Rialto Call: {:?}", HexBytes::encode(&signed_rialto_call));
-
-			rialto_client.submit_extrinsic(Bytes(signed_rialto_call)).await?;
+			rialto_client
+				.submit_signed_extrinsic(rialto_sign.signer.public().clone().into(), |transaction_nonce| {
+					let rialto_call = rialto_runtime::Call::BridgeMillauMessages(
+						rialto_runtime::MessagesCall::send_message(lane, payload, fee),
+					);
+
+					let signed_rialto_call = Rialto::sign_transaction(
+						*rialto_client.genesis_hash(),
+						&rialto_sign.signer,
+						transaction_nonce,
+						rialto_call,
+					)
+					.encode();
+
+					log::info!(
+						target: "bridge",
+						"Sending message to Millau. Size: {}. Dispatch weight: {}. Fee: {}",
+						signed_rialto_call.len(),
+						dispatch_weight,
+						fee,
+					);
+					log::info!(target: "bridge", "Signed Rialto Call: {:?}", HexBytes::encode(&signed_rialto_call));
+
+					Bytes(signed_rialto_call)
+				})
+				.await?;
 		}
 	}
 	Ok(())
diff --git a/bridges/relays/bin-substrate/src/rialto_millau/rialto_headers_to_millau.rs b/bridges/relays/bin-substrate/src/rialto_millau/rialto_headers_to_millau.rs
index 3a6e7a8fc3028f104a95cde436aff83ae10187ef..910d2bf03bf46d7a8eb84d6428ab602b0e2f1b08 100644
--- a/bridges/relays/bin-substrate/src/rialto_millau/rialto_headers_to_millau.rs
+++ b/bridges/relays/bin-substrate/src/rialto_millau/rialto_headers_to_millau.rs
@@ -19,29 +19,30 @@
 use super::{MillauClient, RialtoClient};
 use crate::finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate};
 
-use async_trait::async_trait;
+use codec::Encode;
 use relay_millau_client::{Millau, SigningParams as MillauSigningParams};
 use relay_rialto_client::{Rialto, SyncHeader as RialtoSyncHeader};
-use relay_substrate_client::{finality_source::Justification, Error as SubstrateError, TransactionSignScheme};
-use sp_core::Pair;
+use relay_substrate_client::{finality_source::Justification, Chain, TransactionSignScheme};
+use sp_core::{Bytes, Pair};
 
 /// Rialto-to-Millau finality sync pipeline.
 pub(crate) type RialtoFinalityToMillau = SubstrateFinalityToSubstrate<Rialto, Millau, MillauSigningParams>;
 
-#[async_trait]
 impl SubstrateFinalitySyncPipeline for RialtoFinalityToMillau {
 	const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_rialto::BEST_FINALIZED_RIALTO_HEADER_METHOD;
 
-	type SignedTransaction = <Millau as TransactionSignScheme>::SignedTransaction;
+	type TargetChain = Millau;
 
-	async fn make_submit_finality_proof_transaction(
+	fn transactions_author(&self) -> bp_millau::AccountId {
+		self.target_sign.signer.public().as_array_ref().clone().into()
+	}
+
+	fn make_submit_finality_proof_transaction(
 		&self,
+		transaction_nonce: <Millau as Chain>::Index,
 		header: RialtoSyncHeader,
 		proof: Justification<bp_rialto::BlockNumber>,
-	) -> Result<Self::SignedTransaction, SubstrateError> {
-		let account_id = self.target_sign.signer.public().as_array_ref().clone().into();
-		let nonce = self.target_client.next_account_index(account_id).await?;
-
+	) -> Bytes {
 		let call = millau_runtime::BridgeGrandpaRialtoCall::<
 			millau_runtime::Runtime,
 			millau_runtime::RialtoGrandpaInstance,
@@ -49,9 +50,9 @@ impl SubstrateFinalitySyncPipeline for RialtoFinalityToMillau {
 		.into();
 
 		let genesis_hash = *self.target_client.genesis_hash();
-		let transaction = Millau::sign_transaction(genesis_hash, &self.target_sign.signer, nonce, call);
+		let transaction = Millau::sign_transaction(genesis_hash, &self.target_sign.signer, transaction_nonce, call);
 
-		Ok(transaction)
+		Bytes(transaction.encode())
 	}
 }
 
diff --git a/bridges/relays/bin-substrate/src/rialto_millau/rialto_messages_to_millau.rs b/bridges/relays/bin-substrate/src/rialto_millau/rialto_messages_to_millau.rs
index 97df3734ff7d81e534d1aaff17d512b23154ed2d..7268ff07e00db3a4671238f574bcb14bce242d29 100644
--- a/bridges/relays/bin-substrate/src/rialto_millau/rialto_messages_to_millau.rs
+++ b/bridges/relays/bin-substrate/src/rialto_millau/rialto_messages_to_millau.rs
@@ -21,7 +21,6 @@ use crate::messages_lane::{select_delivery_transaction_limits, SubstrateMessageL
 use crate::messages_source::SubstrateMessagesSource;
 use crate::messages_target::SubstrateMessagesTarget;
 
-use async_trait::async_trait;
 use bp_messages::{LaneId, MessageNonce};
 use bp_runtime::{MILLAU_BRIDGE_INSTANCE, RIALTO_BRIDGE_INSTANCE};
 use bridge_runtime_common::messages::target::FromBridgedChainMessagesProof;
@@ -30,15 +29,14 @@ use frame_support::dispatch::GetDispatchInfo;
 use messages_relay::message_lane::MessageLane;
 use relay_millau_client::{HeaderId as MillauHeaderId, Millau, SigningParams as MillauSigningParams};
 use relay_rialto_client::{HeaderId as RialtoHeaderId, Rialto, SigningParams as RialtoSigningParams};
-use relay_substrate_client::{Chain, Error as SubstrateError, TransactionSignScheme};
+use relay_substrate_client::{Chain, TransactionSignScheme};
 use relay_utils::metrics::MetricsParams;
-use sp_core::Pair;
+use sp_core::{Bytes, Pair};
 use std::{ops::RangeInclusive, time::Duration};
 
 /// Rialto-to-Millau message lane.
 type RialtoMessagesToMillau = SubstrateMessageLaneToSubstrate<Rialto, RialtoSigningParams, Millau, MillauSigningParams>;
 
-#[async_trait]
 impl SubstrateMessageLane for RialtoMessagesToMillau {
 	const OUTBOUND_LANE_MESSAGES_DISPATCH_WEIGHT_METHOD: &'static str =
 		bp_millau::TO_MILLAU_MESSAGES_DISPATCH_WEIGHT_METHOD;
@@ -54,22 +52,25 @@ impl SubstrateMessageLane for RialtoMessagesToMillau {
 	const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_rialto::BEST_FINALIZED_RIALTO_HEADER_METHOD;
 	const BEST_FINALIZED_TARGET_HEADER_ID_AT_SOURCE: &'static str = bp_millau::BEST_FINALIZED_MILLAU_HEADER_METHOD;
 
-	type SourceSignedTransaction = <Rialto as TransactionSignScheme>::SignedTransaction;
-	type TargetSignedTransaction = <Millau as TransactionSignScheme>::SignedTransaction;
+	type SourceChain = Rialto;
+	type TargetChain = Millau;
 
-	async fn make_messages_receiving_proof_transaction(
+	fn source_transactions_author(&self) -> bp_rialto::AccountId {
+		self.source_sign.signer.public().as_array_ref().clone().into()
+	}
+
+	fn make_messages_receiving_proof_transaction(
 		&self,
+		transaction_nonce: <Rialto as Chain>::Index,
 		_generated_at_block: MillauHeaderId,
 		proof: <Self as MessageLane>::MessagesReceivingProof,
-	) -> Result<Self::SourceSignedTransaction, SubstrateError> {
+	) -> Bytes {
 		let (relayers_state, proof) = proof;
-		let account_id = self.source_sign.signer.public().as_array_ref().clone().into();
-		let nonce = self.source_client.next_account_index(account_id).await?;
 		let call: rialto_runtime::Call =
 			rialto_runtime::MessagesCall::receive_messages_delivery_proof(proof, relayers_state).into();
 		let call_weight = call.get_dispatch_info().weight;
 		let genesis_hash = *self.source_client.genesis_hash();
-		let transaction = Rialto::sign_transaction(genesis_hash, &self.source_sign.signer, nonce, call);
+		let transaction = Rialto::sign_transaction(genesis_hash, &self.source_sign.signer, transaction_nonce, call);
 		log::trace!(
 			target: "bridge",
 			"Prepared Millau -> Rialto confirmation transaction. Weight: {}/{}, size: {}/{}",
@@ -78,15 +79,20 @@ impl SubstrateMessageLane for RialtoMessagesToMillau {
 			transaction.encode().len(),
 			bp_rialto::max_extrinsic_size(),
 		);
-		Ok(transaction)
+		Bytes(transaction.encode())
+	}
+
+	fn target_transactions_author(&self) -> bp_rialto::AccountId {
+		self.target_sign.signer.public().as_array_ref().clone().into()
 	}
 
-	async fn make_messages_delivery_transaction(
+	fn make_messages_delivery_transaction(
 		&self,
+		transaction_nonce: <Millau as Chain>::Index,
 		_generated_at_header: RialtoHeaderId,
 		_nonces: RangeInclusive<MessageNonce>,
 		proof: <Self as MessageLane>::MessagesProof,
-	) -> Result<Self::TargetSignedTransaction, SubstrateError> {
+	) -> Bytes {
 		let (dispatch_weight, proof) = proof;
 		let FromBridgedChainMessagesProof {
 			ref nonces_start,
@@ -94,8 +100,6 @@ impl SubstrateMessageLane for RialtoMessagesToMillau {
 			..
 		} = proof;
 		let messages_count = nonces_end - nonces_start + 1;
-		let account_id = self.target_sign.signer.public().as_array_ref().clone().into();
-		let nonce = self.target_client.next_account_index(account_id).await?;
 		let call: millau_runtime::Call = millau_runtime::MessagesCall::receive_messages_proof(
 			self.relayer_id_at_source.clone(),
 			proof,
@@ -105,7 +109,7 @@ impl SubstrateMessageLane for RialtoMessagesToMillau {
 		.into();
 		let call_weight = call.get_dispatch_info().weight;
 		let genesis_hash = *self.target_client.genesis_hash();
-		let transaction = Millau::sign_transaction(genesis_hash, &self.target_sign.signer, nonce, call);
+		let transaction = Millau::sign_transaction(genesis_hash, &self.target_sign.signer, transaction_nonce, call);
 		log::trace!(
 			target: "bridge",
 			"Prepared Rialto -> Millau delivery transaction. Weight: {}/{}, size: {}/{}",
@@ -114,7 +118,7 @@ impl SubstrateMessageLane for RialtoMessagesToMillau {
 			transaction.encode().len(),
 			bp_millau::max_extrinsic_size(),
 		);
-		Ok(transaction)
+		Bytes(transaction.encode())
 	}
 }
 
diff --git a/bridges/relays/bin-substrate/src/rialto_millau/westend_headers_to_millau.rs b/bridges/relays/bin-substrate/src/rialto_millau/westend_headers_to_millau.rs
index 4f2ed8a211734440dcba1b484a79cda72a5676c0..ca670e05f3285705dfad1aa6fb676592701f9079 100644
--- a/bridges/relays/bin-substrate/src/rialto_millau/westend_headers_to_millau.rs
+++ b/bridges/relays/bin-substrate/src/rialto_millau/westend_headers_to_millau.rs
@@ -19,29 +19,30 @@
 use super::{MillauClient, WestendClient};
 use crate::finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate};
 
-use async_trait::async_trait;
+use codec::Encode;
 use relay_millau_client::{Millau, SigningParams as MillauSigningParams};
-use relay_substrate_client::{finality_source::Justification, Error as SubstrateError, TransactionSignScheme};
+use relay_substrate_client::{finality_source::Justification, Chain, TransactionSignScheme};
 use relay_westend_client::{SyncHeader as WestendSyncHeader, Westend};
-use sp_core::Pair;
+use sp_core::{Bytes, Pair};
 
 /// Westend-to-Millau finality sync pipeline.
 pub(crate) type WestendFinalityToMillau = SubstrateFinalityToSubstrate<Westend, Millau, MillauSigningParams>;
 
-#[async_trait]
 impl SubstrateFinalitySyncPipeline for WestendFinalityToMillau {
 	const BEST_FINALIZED_SOURCE_HEADER_ID_AT_TARGET: &'static str = bp_westend::BEST_FINALIZED_WESTEND_HEADER_METHOD;
 
-	type SignedTransaction = <Millau as TransactionSignScheme>::SignedTransaction;
+	type TargetChain = Millau;
 
-	async fn make_submit_finality_proof_transaction(
+	fn transactions_author(&self) -> bp_millau::AccountId {
+		self.target_sign.signer.public().as_array_ref().clone().into()
+	}
+
+	fn make_submit_finality_proof_transaction(
 		&self,
+		transaction_nonce: <Millau as Chain>::Index,
 		header: WestendSyncHeader,
 		proof: Justification<bp_westend::BlockNumber>,
-	) -> Result<Self::SignedTransaction, SubstrateError> {
-		let account_id = self.target_sign.signer.public().as_array_ref().clone().into();
-		let nonce = self.target_client.next_account_index(account_id).await?;
-
+	) -> Bytes {
 		let call = millau_runtime::BridgeGrandpaWestendCall::<
 			millau_runtime::Runtime,
 			millau_runtime::WestendGrandpaInstance,
@@ -49,9 +50,9 @@ impl SubstrateFinalitySyncPipeline for WestendFinalityToMillau {
 		.into();
 
 		let genesis_hash = *self.target_client.genesis_hash();
-		let transaction = Millau::sign_transaction(genesis_hash, &self.target_sign.signer, nonce, call);
+		let transaction = Millau::sign_transaction(genesis_hash, &self.target_sign.signer, transaction_nonce, call);
 
-		Ok(transaction)
+		Bytes(transaction.encode())
 	}
 }
 
diff --git a/bridges/relays/client-substrate/src/client.rs b/bridges/relays/client-substrate/src/client.rs
index 56fcb33bf92cb85e9844921fdb89565783bc4c7a..a03cf493ca10508d5d3e766c45da474da72a004d 100644
--- a/bridges/relays/client-substrate/src/client.rs
+++ b/bridges/relays/client-substrate/src/client.rs
@@ -20,6 +20,7 @@ use crate::chain::{Chain, ChainWithBalances};
 use crate::rpc::{Substrate, SubstrateMessages};
 use crate::{ConnectionParams, Error, Result};
 
+use async_std::sync::{Arc, Mutex};
 use bp_messages::{LaneId, MessageNonce};
 use bp_runtime::InstanceId;
 use codec::Decode;
@@ -52,6 +53,10 @@ pub struct Client<C: Chain> {
 	client: RpcClient,
 	/// Genesis block hash.
 	genesis_hash: C::Hash,
+	/// If several tasks are submitting their transactions simultaneously using `submit_signed_extrinsic`
+	/// method, they may get the same transaction nonce. So one of transactions will be rejected
+	/// from the pool. This lock is here to prevent situations like that.
+	submit_signed_extrinsic_lock: Arc<Mutex<()>>,
 }
 
 impl<C: Chain> Clone for Client<C> {
@@ -60,6 +65,7 @@ impl<C: Chain> Clone for Client<C> {
 			params: self.params.clone(),
 			client: self.client.clone(),
 			genesis_hash: self.genesis_hash,
+			submit_signed_extrinsic_lock: self.submit_signed_extrinsic_lock.clone(),
 		}
 	}
 }
@@ -84,6 +90,7 @@ impl<C: Chain> Client<C> {
 			params,
 			client,
 			genesis_hash,
+			submit_signed_extrinsic_lock: Arc::new(Mutex::new(())),
 		})
 	}
 
@@ -192,15 +199,35 @@ impl<C: Chain> Client<C> {
 		Ok(Substrate::<C>::system_account_next_index(&self.client, account).await?)
 	}
 
-	/// Submit an extrinsic for inclusion in a block.
+	/// Submit unsigned extrinsic for inclusion in a block.
 	///
-	/// Note: The given transaction does not need be SCALE encoded beforehand.
-	pub async fn submit_extrinsic(&self, transaction: Bytes) -> Result<C::Hash> {
+	/// Note: The given transaction needs to be SCALE encoded beforehand.
+	pub async fn submit_unsigned_extrinsic(&self, transaction: Bytes) -> Result<C::Hash> {
 		let tx_hash = Substrate::<C>::author_submit_extrinsic(&self.client, transaction).await?;
 		log::trace!(target: "bridge", "Sent transaction to Substrate node: {:?}", tx_hash);
 		Ok(tx_hash)
 	}
 
+	/// Submit an extrinsic signed by given account.
+	///
+	/// All calls of this method are synchronized, so there can't be more than one active
+	/// `submit_signed_extrinsic()` call. This guarantees that no nonces collision may happen
+	/// if all client instances are clones of the same initial `Client`.
+	///
+	/// Note: The given transaction needs to be SCALE encoded beforehand.
+	pub async fn submit_signed_extrinsic(
+		&self,
+		extrinsic_signer: C::AccountId,
+		prepare_extrinsic: impl FnOnce(C::Index) -> Bytes,
+	) -> Result<C::Hash> {
+		let _guard = self.submit_signed_extrinsic_lock.lock().await;
+		let transaction_nonce = self.next_account_index(extrinsic_signer).await?;
+		let extrinsic = prepare_extrinsic(transaction_nonce);
+		let tx_hash = Substrate::<C>::author_submit_extrinsic(&self.client, extrinsic).await?;
+		log::trace!(target: "bridge", "Sent transaction to {} node: {:?}", C::NAME, tx_hash);
+		Ok(tx_hash)
+	}
+
 	/// Get the GRANDPA authority set at given block.
 	pub async fn grandpa_authorities_set(&self, block: C::Hash) -> Result<OpaqueGrandpaAuthoritiesSet> {
 		let call = SUB_API_GRANDPA_AUTHORITIES.to_string();
diff --git a/bridges/relays/messages/src/message_lane_loop.rs b/bridges/relays/messages/src/message_lane_loop.rs
index 77265f4ac96b7f38505eed5c3c41b01bffdc4c4b..159bd4c76959284d1c88f1d1e895ee45d112c2f1 100644
--- a/bridges/relays/messages/src/message_lane_loop.rs
+++ b/bridges/relays/messages/src/message_lane_loop.rs
@@ -139,6 +139,9 @@ pub trait SourceClient<P: MessageLane>: RelayClient {
 		generated_at_block: TargetHeaderIdOf<P>,
 		proof: P::MessagesReceivingProof,
 	) -> Result<(), Self::Error>;
+
+	/// Activate (or deactivate) headers relay that relays target headers to source node.
+	async fn activate_target_to_source_headers_relay(&self, activate: bool);
 }
 
 /// Target client trait.
@@ -177,6 +180,9 @@ pub trait TargetClient<P: MessageLane>: RelayClient {
 		nonces: RangeInclusive<MessageNonce>,
 		proof: P::MessagesProof,
 	) -> Result<RangeInclusive<MessageNonce>, Self::Error>;
+
+	/// Activate (or deactivate) headers relay that relays source headers to target node.
+	async fn activate_source_to_target_headers_relay(&self, activate: bool);
 }
 
 /// State of the client.
@@ -463,6 +469,8 @@ pub(crate) mod tests {
 		target_latest_received_nonce: MessageNonce,
 		target_latest_confirmed_received_nonce: MessageNonce,
 		submitted_messages_proofs: Vec<TestMessagesProof>,
+		is_target_to_source_headers_relay_activated: bool,
+		is_source_to_target_headers_relay_activated: bool,
 	}
 
 	#[derive(Clone)]
@@ -567,6 +575,12 @@ pub(crate) mod tests {
 			data.source_latest_confirmed_received_nonce = proof;
 			Ok(())
 		}
+
+		async fn activate_target_to_source_headers_relay(&self, activate: bool) {
+			let mut data = self.data.lock();
+			data.is_target_to_source_headers_relay_activated = activate;
+			(self.tick)(&mut *data);
+		}
 	}
 
 	#[derive(Clone)]
@@ -665,6 +679,12 @@ pub(crate) mod tests {
 			data.submitted_messages_proofs.push(proof);
 			Ok(nonces)
 		}
+
+		async fn activate_source_to_target_headers_relay(&self, activate: bool) {
+			let mut data = self.data.lock();
+			data.is_source_to_target_headers_relay_activated = activate;
+			(self.tick)(&mut *data);
+		}
 	}
 
 	fn run_loop_test(
@@ -778,8 +798,19 @@ pub(crate) mod tests {
 				target_latest_received_nonce: 0,
 				..Default::default()
 			},
-			Arc::new(|_: &mut TestClientData| {}),
+			Arc::new(|data: &mut TestClientData| {
+				// headers relay must only be started when we need new target headers at source node
+				if data.is_target_to_source_headers_relay_activated {
+					assert!(data.source_state.best_finalized_peer_at_best_self.0 < data.target_state.best_self.0);
+					data.is_target_to_source_headers_relay_activated = false;
+				}
+			}),
 			Arc::new(move |data: &mut TestClientData| {
+				// headers relay must only be started when we need new source headers at target node
+				if data.is_target_to_source_headers_relay_activated {
+					assert!(data.target_state.best_finalized_peer_at_best_self.0 < data.source_state.best_self.0);
+					data.is_target_to_source_headers_relay_activated = false;
+				}
 				// syncing source headers -> target chain (all at once)
 				if data.target_state.best_finalized_peer_at_best_self.0 < data.source_state.best_finalized_self.0 {
 					data.target_state.best_finalized_peer_at_best_self = data.source_state.best_finalized_self;
diff --git a/bridges/relays/messages/src/message_race_delivery.rs b/bridges/relays/messages/src/message_race_delivery.rs
index 1ee39c9c9f90865c26a901f31989a63db4ca9ce6..80709055be0e99a0269fc7f66a688d9eb5653050 100644
--- a/bridges/relays/messages/src/message_race_delivery.rs
+++ b/bridges/relays/messages/src/message_race_delivery.rs
@@ -166,6 +166,10 @@ where
 	type Error = C::Error;
 	type TargetNoncesData = DeliveryRaceTargetNoncesData;
 
+	async fn require_more_source_headers(&self, activate: bool) {
+		self.client.activate_source_to_target_headers_relay(activate).await
+	}
+
 	async fn nonces(
 		&self,
 		at_block: TargetHeaderIdOf<P>,
diff --git a/bridges/relays/messages/src/message_race_loop.rs b/bridges/relays/messages/src/message_race_loop.rs
index 2c04466f1fb5fcaf45ffe2b0e58bab2bf26aae9a..56bc881bf7a9af3db7aa31f3319c0f1471bfaed5 100644
--- a/bridges/relays/messages/src/message_race_loop.rs
+++ b/bridges/relays/messages/src/message_race_loop.rs
@@ -123,6 +123,9 @@ pub trait TargetClient<P: MessageRace> {
 	/// Type of the additional data from the target client, used by the race.
 	type TargetNoncesData: std::fmt::Debug;
 
+	/// Ask headers relay to relay more headers from race source to race target.
+	async fn require_more_source_headers(&self, activate: bool);
+
 	/// Return nonces that are known to the target client.
 	async fn nonces(
 		&self,
@@ -216,6 +219,7 @@ pub async fn run<P: MessageRace, SC: SourceClient<P>, TC: TargetClient<P>>(
 		TargetNoncesData = TC::TargetNoncesData,
 	>,
 ) -> Result<(), FailedClient> {
+	let mut is_strategy_empty = true;
 	let mut progress_context = Instant::now();
 	let mut race_state = RaceState::default();
 	let mut stall_countdown = Instant::now();
@@ -404,6 +408,13 @@ pub async fn run<P: MessageRace, SC: SourceClient<P>, TC: TargetClient<P>>(
 
 		progress_context = print_race_progress::<P, _>(progress_context, &strategy);
 
+		// ask for more headers if we have nonces to deliver
+		let prev_is_strategy_empty = is_strategy_empty;
+		is_strategy_empty = strategy.is_empty();
+		if is_strategy_empty != prev_is_strategy_empty {
+			race_target.require_more_source_headers(!is_strategy_empty).await;
+		}
+
 		if stall_countdown.elapsed() > stall_timeout {
 			log::warn!(
 				target: "bridge",
diff --git a/bridges/relays/messages/src/message_race_receiving.rs b/bridges/relays/messages/src/message_race_receiving.rs
index 0fc025abb7d1ab87a8f617591c0580a1edc2ccf2..2178882437eb69bccadcda42609b8cd511634a47 100644
--- a/bridges/relays/messages/src/message_race_receiving.rs
+++ b/bridges/relays/messages/src/message_race_receiving.rs
@@ -159,6 +159,10 @@ where
 	type Error = C::Error;
 	type TargetNoncesData = ();
 
+	async fn require_more_source_headers(&self, activate: bool) {
+		self.client.activate_target_to_source_headers_relay(activate).await
+	}
+
 	async fn nonces(
 		&self,
 		at_block: SourceHeaderIdOf<P>,