diff --git a/bridges/relays/client-substrate/src/client.rs b/bridges/relays/client-substrate/src/client.rs
index 067d3d89d2450d4ed20d393fc39b5b04dd9c99ee..a713b3ba12128d186aa441b541fc36921494a2c0 100644
--- a/bridges/relays/client-substrate/src/client.rs
+++ b/bridges/relays/client-substrate/src/client.rs
@@ -19,8 +19,8 @@
 use crate::{
 	chain::{Chain, ChainWithBalances},
 	rpc::{
-		SubstrateAuthorClient, SubstrateChainClient, SubstrateFrameSystemClient,
-		SubstrateGrandpaClient, SubstrateStateClient, SubstrateSystemClient,
+		SubstrateAuthorClient, SubstrateChainClient, SubstrateFinalityClient,
+		SubstrateFrameSystemClient, SubstrateStateClient, SubstrateSystemClient,
 		SubstrateTransactionPaymentClient,
 	},
 	transaction_stall_timeout, ConnectionParams, Error, HashOf, HeaderIdOf, Result, SignParam,
@@ -642,11 +642,13 @@ impl<C: Chain> Client<C> {
 		.await
 	}
 
-	/// Return new GRANDPA justifications stream.
-	pub async fn subscribe_grandpa_justifications(&self) -> Result<Subscription<Bytes>> {
+	/// Return new finality justifications stream.
+	pub async fn subscribe_finality_justifications<FC: SubstrateFinalityClient<C>>(
+		&self,
+	) -> Result<Subscription<Bytes>> {
 		let subscription = self
 			.jsonrpsee_execute(move |client| async move {
-				Ok(SubstrateGrandpaClient::<C>::subscribe_justifications(&*client).await?)
+				Ok(FC::subscribe_justifications(&client).await?)
 			})
 			.await?;
 		let (sender, receiver) = futures::channel::mpsc::channel(MAX_SUBSCRIPTION_CAPACITY);
diff --git a/bridges/relays/client-substrate/src/lib.rs b/bridges/relays/client-substrate/src/lib.rs
index bd38f3a928f83aa113528d27e58a91bd757fb385..9e6c73d83caf9899cc6d6c3706e1f0ec71dcc5dc 100644
--- a/bridges/relays/client-substrate/src/lib.rs
+++ b/bridges/relays/client-substrate/src/lib.rs
@@ -39,6 +39,7 @@ pub use crate::{
 	},
 	client::{ChainRuntimeVersion, Client, OpaqueGrandpaAuthoritiesSet, Subscription},
 	error::{Error, Result},
+	rpc::{SubstrateBeefyFinalityClient, SubstrateFinalityClient, SubstrateGrandpaFinalityClient},
 	sync_header::SyncHeader,
 	transaction_tracker::TransactionTracker,
 };
diff --git a/bridges/relays/client-substrate/src/rpc.rs b/bridges/relays/client-substrate/src/rpc.rs
index fdba424dd90fd23f8ec38bf9735ce99dfd688c0b..083b1dea761a1bc095ac53b8e0dabcd01cd80d0c 100644
--- a/bridges/relays/client-substrate/src/rpc.rs
+++ b/bridges/relays/client-substrate/src/rpc.rs
@@ -16,9 +16,15 @@
 
 //! The most generic Substrate node RPC interface.
 
-use crate::{Chain, TransactionStatusOf};
+use async_trait::async_trait;
 
-use jsonrpsee::{core::RpcResult, proc_macros::rpc};
+use crate::{Chain, ChainWithGrandpa, TransactionStatusOf};
+
+use jsonrpsee::{
+	core::{client::Subscription, RpcResult},
+	proc_macros::rpc,
+	ws_client::WsClient,
+};
 use pallet_transaction_payment_rpc_runtime_api::FeeDetails;
 use sc_rpc_api::{state::ReadProof, system::Health};
 use sp_core::{
@@ -100,14 +106,49 @@ pub(crate) trait SubstrateState<C> {
 	) -> RpcResult<ReadProof<C::Hash>>;
 }
 
+/// RPC methods that we are using for a certain finality gadget.
+#[async_trait]
+pub trait SubstrateFinalityClient<C: Chain> {
+	/// Subscribe to finality justifications.
+	async fn subscribe_justifications(client: &WsClient) -> RpcResult<Subscription<Bytes>>;
+}
+
 /// RPC methods of Substrate `grandpa` namespace, that we are using.
-#[rpc(client, client_bounds(C: Chain), namespace = "grandpa")]
+#[rpc(client, client_bounds(C: ChainWithGrandpa), namespace = "grandpa")]
 pub(crate) trait SubstrateGrandpa<C> {
 	/// Subscribe to GRANDPA justifications.
 	#[subscription(name = "subscribeJustifications", unsubscribe = "unsubscribeJustifications", item = Bytes)]
 	fn subscribe_justifications(&self);
 }
 
+/// RPC finality methods of Substrate `grandpa` namespace, that we are using.
+pub struct SubstrateGrandpaFinalityClient;
+#[async_trait]
+impl<C: ChainWithGrandpa> SubstrateFinalityClient<C> for SubstrateGrandpaFinalityClient {
+	async fn subscribe_justifications(client: &WsClient) -> RpcResult<Subscription<Bytes>> {
+		SubstrateGrandpaClient::<C>::subscribe_justifications(client).await
+	}
+}
+
+// TODO: Use `ChainWithBeefy` instead of `Chain` after #1606 is merged
+/// RPC methods of Substrate `beefy` namespace, that we are using.
+#[rpc(client, client_bounds(C: Chain), namespace = "beefy")]
+pub(crate) trait SubstrateBeefy<C> {
+	/// Subscribe to BEEFY justifications.
+	#[subscription(name = "subscribeJustifications", unsubscribe = "unsubscribeJustifications", item = Bytes)]
+	fn subscribe_justifications(&self);
+}
+
+/// RPC finality methods of Substrate `beefy` namespace, that we are using.
+pub struct SubstrateBeefyFinalityClient;
+// TODO: Use `ChainWithBeefy` instead of `Chain` after #1606 is merged
+#[async_trait]
+impl<C: Chain> SubstrateFinalityClient<C> for SubstrateBeefyFinalityClient {
+	async fn subscribe_justifications(client: &WsClient) -> RpcResult<Subscription<Bytes>> {
+		SubstrateBeefyClient::<C>::subscribe_justifications(client).await
+	}
+}
+
 /// RPC methods of Substrate `system` frame pallet, that we are using.
 #[rpc(client, client_bounds(C: Chain), namespace = "system")]
 pub(crate) trait SubstrateFrameSystem<C> {
diff --git a/bridges/relays/lib-substrate-relay/src/finality/engine.rs b/bridges/relays/lib-substrate-relay/src/finality/engine.rs
index b2b72e4f2c39f61a4e8327415abd0519aa2f348f..83ea074e93d0b7f59cb7c9655b35ef5569aaf086 100644
--- a/bridges/relays/lib-substrate-relay/src/finality/engine.rs
+++ b/bridges/relays/lib-substrate-relay/src/finality/engine.rs
@@ -29,18 +29,20 @@ use finality_grandpa::voter_set::VoterSet;
 use num_traits::{One, Zero};
 use relay_substrate_client::{
 	BlockNumberOf, Chain, ChainWithGrandpa, Client, Error as SubstrateError, HashOf, HeaderOf,
-	Subscription,
+	Subscription, SubstrateFinalityClient, SubstrateGrandpaFinalityClient,
 };
 use sp_core::{storage::StorageKey, Bytes};
 use sp_finality_grandpa::AuthorityList as GrandpaAuthoritiesSet;
 use sp_runtime::{traits::Header, ConsensusEngineId};
 use std::marker::PhantomData;
 
-/// Finality enfine, used by the Substrate chain.
+/// Finality engine, used by the Substrate chain.
 #[async_trait]
 pub trait Engine<C: Chain>: Send {
 	/// Unique consensus engine identifier.
 	const ID: ConsensusEngineId;
+	/// Type of Finality RPC client used by this engine.
+	type FinalityClient: SubstrateFinalityClient<C>;
 	/// Type of finality proofs, used by consensus engine.
 	type FinalityProof: FinalityProof<BlockNumberOf<C>> + Decode + Encode;
 	/// Type of bridge pallet initialization data.
@@ -57,7 +59,9 @@ pub trait Engine<C: Chain>: Send {
 	/// Note that we don't care about type of the value - just if it present or not.
 	fn is_initialized_key() -> StorageKey;
 	/// A method to subscribe to encoded finality proofs, given source client.
-	async fn finality_proofs(client: Client<C>) -> Result<Subscription<Bytes>, SubstrateError>;
+	async fn finality_proofs(client: &Client<C>) -> Result<Subscription<Bytes>, SubstrateError> {
+		client.subscribe_finality_justifications::<Self::FinalityClient>().await
+	}
 	/// Prepare initialization data for the finality bridge pallet.
 	async fn prepare_initialization_data(
 		client: Client<C>,
@@ -117,6 +121,7 @@ impl<C: ChainWithGrandpa> Grandpa<C> {
 #[async_trait]
 impl<C: ChainWithGrandpa> Engine<C> for Grandpa<C> {
 	const ID: ConsensusEngineId = sp_finality_grandpa::GRANDPA_ENGINE_ID;
+	type FinalityClient = SubstrateGrandpaFinalityClient;
 	type FinalityProof = GrandpaJustification<HeaderOf<C>>;
 	type InitializationData = bp_header_chain::InitializationData<C::Header>;
 	type OperatingMode = BasicOperatingMode;
@@ -129,10 +134,6 @@ impl<C: ChainWithGrandpa> Engine<C> for Grandpa<C> {
 		bp_header_chain::storage_keys::best_finalized_key(C::WITH_CHAIN_GRANDPA_PALLET_NAME)
 	}
 
-	async fn finality_proofs(client: Client<C>) -> Result<Subscription<Bytes>, SubstrateError> {
-		client.subscribe_grandpa_justifications().await
-	}
-
 	/// Prepare initialization data for the GRANDPA verifier pallet.
 	async fn prepare_initialization_data(
 		source_client: Client<C>,
@@ -144,8 +145,7 @@ impl<C: ChainWithGrandpa> Engine<C> for Grandpa<C> {
 		// But now there are problems with this approach - `CurrentSetId` may return invalid value.
 		// So here we're waiting for the next justification, read the authorities set and then try
 		// to figure out the set id with bruteforce.
-		let justifications = source_client
-			.subscribe_grandpa_justifications()
+		let justifications = Self::finality_proofs(&source_client)
 			.await
 			.map_err(|err| Error::Subscribe(C::NAME, err))?;
 		// Read next justification - the header that it finalizes will be used as initial header.
diff --git a/bridges/relays/lib-substrate-relay/src/finality/source.rs b/bridges/relays/lib-substrate-relay/src/finality/source.rs
index c8360bbddbc577fa761c4cc5c3de29aa8bb3d4ec..199de368137919c9903f27fb96753073d246c8ce 100644
--- a/bridges/relays/lib-substrate-relay/src/finality/source.rs
+++ b/bridges/relays/lib-substrate-relay/src/finality/source.rs
@@ -142,7 +142,7 @@ impl<P: SubstrateFinalitySyncPipeline> SourceClient<FinalitySyncPipelineAdapter<
 
 	async fn finality_proofs(&self) -> Result<Self::FinalityProofsStream, Error> {
 		Ok(unfold(
-			P::FinalityEngine::finality_proofs(self.client.clone()).await?,
+			P::FinalityEngine::finality_proofs(&self.client).await?,
 			move |subscription| async move {
 				loop {
 					let log_error = |err| {