diff --git a/bridges/relays/ethereum-client/Cargo.toml b/bridges/relays/clients/ethereum/Cargo.toml
similarity index 79%
rename from bridges/relays/ethereum-client/Cargo.toml
rename to bridges/relays/clients/ethereum/Cargo.toml
index 4c04f4889469b20696e75be32be8ab6b07c3f808..b73a66a44c0e1023525009117fc915433a48a01b 100644
--- a/bridges/relays/ethereum-client/Cargo.toml
+++ b/bridges/relays/clients/ethereum/Cargo.toml
@@ -6,14 +6,14 @@ edition = "2018"
 license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
 
 [dependencies]
-bp-eth-poa = { path = "../../primitives/ethereum-poa" }
+bp-eth-poa = { path = "../../../primitives/ethereum-poa" }
 codec = { package = "parity-scale-codec", version = "2.0.0" }
-headers-relay = { path = "../headers-relay" }
+headers-relay = { path = "../../generic/headers" }
 hex-literal = "0.3"
 jsonrpsee-proc-macros = "0.2.0-alpha"
 jsonrpsee-types = "0.2.0-alpha"
 jsonrpsee-ws-client = "0.2.0-alpha"
 libsecp256k1 = { version = "0.3.4", default-features = false, features = ["hmac"] }
 log = "0.4.11"
-relay-utils = { path = "../utils" }
+relay-utils = { path = "../../generic/utils" }
 web3 = { version = "0.15", git = "https://github.com/tomusdrw/rust-web3", branch ="td-ethabi", default-features = false }
diff --git a/bridges/relays/ethereum-client/src/client.rs b/bridges/relays/clients/ethereum/src/client.rs
similarity index 100%
rename from bridges/relays/ethereum-client/src/client.rs
rename to bridges/relays/clients/ethereum/src/client.rs
diff --git a/bridges/relays/ethereum-client/src/error.rs b/bridges/relays/clients/ethereum/src/error.rs
similarity index 100%
rename from bridges/relays/ethereum-client/src/error.rs
rename to bridges/relays/clients/ethereum/src/error.rs
diff --git a/bridges/relays/ethereum-client/src/lib.rs b/bridges/relays/clients/ethereum/src/lib.rs
similarity index 100%
rename from bridges/relays/ethereum-client/src/lib.rs
rename to bridges/relays/clients/ethereum/src/lib.rs
diff --git a/bridges/relays/ethereum-client/src/rpc.rs b/bridges/relays/clients/ethereum/src/rpc.rs
similarity index 100%
rename from bridges/relays/ethereum-client/src/rpc.rs
rename to bridges/relays/clients/ethereum/src/rpc.rs
diff --git a/bridges/relays/ethereum-client/src/sign.rs b/bridges/relays/clients/ethereum/src/sign.rs
similarity index 100%
rename from bridges/relays/ethereum-client/src/sign.rs
rename to bridges/relays/clients/ethereum/src/sign.rs
diff --git a/bridges/relays/ethereum-client/src/types.rs b/bridges/relays/clients/ethereum/src/types.rs
similarity index 100%
rename from bridges/relays/ethereum-client/src/types.rs
rename to bridges/relays/clients/ethereum/src/types.rs
diff --git a/bridges/relays/kusama-client/Cargo.toml b/bridges/relays/clients/kusama/Cargo.toml
similarity index 80%
rename from bridges/relays/kusama-client/Cargo.toml
rename to bridges/relays/clients/kusama/Cargo.toml
index cd311830ae6152e96c1fc1c1cebb499541e33249..e8e44ce2a87c0b2e918912323c6f5862dd77ac89 100644
--- a/bridges/relays/kusama-client/Cargo.toml
+++ b/bridges/relays/clients/kusama/Cargo.toml
@@ -7,13 +7,13 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
 
 [dependencies]
 codec = { package = "parity-scale-codec", version = "2.0.0" }
-headers-relay = { path = "../headers-relay" }
-relay-substrate-client = { path = "../substrate-client" }
-relay-utils = { path = "../utils" }
+headers-relay = { path = "../../generic/headers" }
+relay-substrate-client = { path = "../substrate" }
+relay-utils = { path = "../../generic/utils" }
 
 # Bridge dependencies
 
-bp-kusama = { path = "../../primitives/chains/kusama" }
+bp-kusama = { path = "../../../primitives/chains/kusama" }
 
 # Substrate Dependencies
 
diff --git a/bridges/relays/kusama-client/src/lib.rs b/bridges/relays/clients/kusama/src/lib.rs
similarity index 100%
rename from bridges/relays/kusama-client/src/lib.rs
rename to bridges/relays/clients/kusama/src/lib.rs
diff --git a/bridges/relays/millau-client/Cargo.toml b/bridges/relays/clients/millau/Cargo.toml
similarity index 79%
rename from bridges/relays/millau-client/Cargo.toml
rename to bridges/relays/clients/millau/Cargo.toml
index 5f9cbd170c938909c97c7194a51366b30104b679..f975022aea869f9602164adca70aae95ffd26837 100644
--- a/bridges/relays/millau-client/Cargo.toml
+++ b/bridges/relays/clients/millau/Cargo.toml
@@ -7,13 +7,13 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
 
 [dependencies]
 codec = { package = "parity-scale-codec", version = "2.0.0" }
-headers-relay = { path = "../headers-relay" }
-relay-substrate-client = { path = "../substrate-client" }
-relay-utils = { path = "../utils" }
+headers-relay = { path = "../../generic/headers" }
+relay-substrate-client = { path = "../../clients/substrate" }
+relay-utils = { path = "../../generic/utils" }
 
 # Supported Chains
 
-millau-runtime = { path = "../../bin/millau/runtime" }
+millau-runtime = { path = "../../../bin/millau/runtime" }
 
 # Substrate Dependencies
 
diff --git a/bridges/relays/millau-client/src/lib.rs b/bridges/relays/clients/millau/src/lib.rs
similarity index 100%
rename from bridges/relays/millau-client/src/lib.rs
rename to bridges/relays/clients/millau/src/lib.rs
diff --git a/bridges/relays/polkadot-client/Cargo.toml b/bridges/relays/clients/polkadot/Cargo.toml
similarity index 79%
rename from bridges/relays/polkadot-client/Cargo.toml
rename to bridges/relays/clients/polkadot/Cargo.toml
index a1d82758f8d62f1946734e9d3bf6bedaf611263f..b40397f194f3443e3e2b2b001d8be501761b53c6 100644
--- a/bridges/relays/polkadot-client/Cargo.toml
+++ b/bridges/relays/clients/polkadot/Cargo.toml
@@ -7,13 +7,13 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
 
 [dependencies]
 codec = { package = "parity-scale-codec", version = "2.0.0" }
-headers-relay = { path = "../headers-relay" }
-relay-substrate-client = { path = "../substrate-client" }
-relay-utils = { path = "../utils" }
+headers-relay = { path = "../../generic/headers" }
+relay-substrate-client = { path = "../substrate" }
+relay-utils = { path = "../../generic/utils" }
 
 # Bridge dependencies
 
-bp-polkadot = { path = "../../primitives/chains/polkadot" }
+bp-polkadot = { path = "../../../primitives/chains/polkadot" }
 
 # Substrate Dependencies
 
diff --git a/bridges/relays/polkadot-client/src/lib.rs b/bridges/relays/clients/polkadot/src/lib.rs
similarity index 100%
rename from bridges/relays/polkadot-client/src/lib.rs
rename to bridges/relays/clients/polkadot/src/lib.rs
diff --git a/bridges/relays/rialto-client/Cargo.toml b/bridges/relays/clients/rialto/Cargo.toml
similarity index 80%
rename from bridges/relays/rialto-client/Cargo.toml
rename to bridges/relays/clients/rialto/Cargo.toml
index 6142ba05c963c199e60ea4c72867b5ee0aff0f3c..74aeb8c30f00b00f2091cd5d51d64c13ac21cda0 100644
--- a/bridges/relays/rialto-client/Cargo.toml
+++ b/bridges/relays/clients/rialto/Cargo.toml
@@ -7,13 +7,13 @@ license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
 
 [dependencies]
 codec = { package = "parity-scale-codec", version = "2.0.0" }
-headers-relay = { path = "../headers-relay" }
-relay-substrate-client = { path = "../substrate-client" }
-relay-utils = { path = "../utils" }
+headers-relay = { path = "../../generic/headers" }
+relay-substrate-client = { path = "../substrate" }
+relay-utils = { path = "../../generic/utils" }
 
 # Bridge dependencies
 
-rialto-runtime = { path = "../../bin/rialto/runtime" }
+rialto-runtime = { path = "../../../bin/rialto/runtime" }
 
 # Substrate Dependencies
 
diff --git a/bridges/relays/rialto-client/src/lib.rs b/bridges/relays/clients/rialto/src/lib.rs
similarity index 100%
rename from bridges/relays/rialto-client/src/lib.rs
rename to bridges/relays/clients/rialto/src/lib.rs
diff --git a/bridges/relays/substrate-client/Cargo.toml b/bridges/relays/clients/substrate/Cargo.toml
similarity index 79%
rename from bridges/relays/substrate-client/Cargo.toml
rename to bridges/relays/clients/substrate/Cargo.toml
index d02c08ab7ad704c5cc1b847be80ea9a717933e67..bd73043e0a50d3ba2acc5383bcc718f6abef00a3 100644
--- a/bridges/relays/substrate-client/Cargo.toml
+++ b/bridges/relays/clients/substrate/Cargo.toml
@@ -18,12 +18,12 @@ rand = "0.7"
 
 # Bridge dependencies
 
-bp-header-chain = { path = "../../primitives/header-chain" }
-bp-message-lane = { path = "../../primitives/message-lane" }
-bp-runtime = { path = "../../primitives/runtime" }
-finality-relay = { path = "../finality-relay" }
-headers-relay = { path = "../headers-relay" }
-relay-utils = { path = "../utils" }
+bp-header-chain = { path = "../../../primitives/header-chain" }
+bp-message-lane = { path = "../../../primitives/message-lane" }
+bp-runtime = { path = "../../../primitives/runtime" }
+finality-relay = { path = "../../generic/finality" }
+headers-relay = { path = "../../generic/headers" }
+relay-utils = { path = "../../generic/utils" }
 
 # Substrate Dependencies
 
diff --git a/bridges/relays/substrate-client/src/chain.rs b/bridges/relays/clients/substrate/src/chain.rs
similarity index 100%
rename from bridges/relays/substrate-client/src/chain.rs
rename to bridges/relays/clients/substrate/src/chain.rs
diff --git a/bridges/relays/substrate-client/src/client.rs b/bridges/relays/clients/substrate/src/client.rs
similarity index 100%
rename from bridges/relays/substrate-client/src/client.rs
rename to bridges/relays/clients/substrate/src/client.rs
diff --git a/bridges/relays/substrate-client/src/error.rs b/bridges/relays/clients/substrate/src/error.rs
similarity index 100%
rename from bridges/relays/substrate-client/src/error.rs
rename to bridges/relays/clients/substrate/src/error.rs
diff --git a/bridges/relays/substrate-client/src/finality_source.rs b/bridges/relays/clients/substrate/src/finality_source.rs
similarity index 100%
rename from bridges/relays/substrate-client/src/finality_source.rs
rename to bridges/relays/clients/substrate/src/finality_source.rs
diff --git a/bridges/relays/substrate-client/src/guard.rs b/bridges/relays/clients/substrate/src/guard.rs
similarity index 100%
rename from bridges/relays/substrate-client/src/guard.rs
rename to bridges/relays/clients/substrate/src/guard.rs
diff --git a/bridges/relays/substrate-client/src/headers_source.rs b/bridges/relays/clients/substrate/src/headers_source.rs
similarity index 100%
rename from bridges/relays/substrate-client/src/headers_source.rs
rename to bridges/relays/clients/substrate/src/headers_source.rs
diff --git a/bridges/relays/substrate-client/src/lib.rs b/bridges/relays/clients/substrate/src/lib.rs
similarity index 100%
rename from bridges/relays/substrate-client/src/lib.rs
rename to bridges/relays/clients/substrate/src/lib.rs
diff --git a/bridges/relays/substrate-client/src/rpc.rs b/bridges/relays/clients/substrate/src/rpc.rs
similarity index 100%
rename from bridges/relays/substrate-client/src/rpc.rs
rename to bridges/relays/clients/substrate/src/rpc.rs
diff --git a/bridges/relays/substrate-client/src/sync_header.rs b/bridges/relays/clients/substrate/src/sync_header.rs
similarity index 100%
rename from bridges/relays/substrate-client/src/sync_header.rs
rename to bridges/relays/clients/substrate/src/sync_header.rs
diff --git a/bridges/relays/ethereum/Cargo.toml b/bridges/relays/ethereum/Cargo.toml
index fbc1ef00fcaa93ff86a1bd4b3fb4004708c65f7e..1975830e5a5a498cdc66ca22cc9c17d9e89256a5 100644
--- a/bridges/relays/ethereum/Cargo.toml
+++ b/bridges/relays/ethereum/Cargo.toml
@@ -29,13 +29,13 @@ time = "0.2"
 
 bp-currency-exchange = { path = "../../primitives/currency-exchange" }
 bp-eth-poa = { path = "../../primitives/ethereum-poa" }
-exchange-relay = { path = "../exchange-relay" }
-headers-relay = { path = "../headers-relay" }
-messages-relay = { path = "../messages-relay" }
-relay-ethereum-client = { path = "../ethereum-client" }
-relay-rialto-client = { path = "../rialto-client" }
-relay-substrate-client = { path = "../substrate-client" }
-relay-utils = { path = "../utils" }
+exchange-relay = { path = "../generic/exchange" }
+headers-relay = { path = "../generic/headers" }
+messages-relay = { path = "../generic/messages" }
+relay-ethereum-client = { path = "../clients/ethereum" }
+relay-rialto-client = { path = "../clients/rialto" }
+relay-substrate-client = { path = "../clients/substrate" }
+relay-utils = { path = "../generic/utils" }
 rialto-runtime = { path = "../../bin/rialto/runtime" }
 
 # Substrate Dependencies
diff --git a/bridges/relays/exchange-relay/Cargo.toml b/bridges/relays/generic/exchange/Cargo.toml
similarity index 100%
rename from bridges/relays/exchange-relay/Cargo.toml
rename to bridges/relays/generic/exchange/Cargo.toml
diff --git a/bridges/relays/exchange-relay/src/exchange.rs b/bridges/relays/generic/exchange/src/exchange.rs
similarity index 100%
rename from bridges/relays/exchange-relay/src/exchange.rs
rename to bridges/relays/generic/exchange/src/exchange.rs
diff --git a/bridges/relays/exchange-relay/src/exchange_loop.rs b/bridges/relays/generic/exchange/src/exchange_loop.rs
similarity index 100%
rename from bridges/relays/exchange-relay/src/exchange_loop.rs
rename to bridges/relays/generic/exchange/src/exchange_loop.rs
diff --git a/bridges/relays/exchange-relay/src/exchange_loop_metrics.rs b/bridges/relays/generic/exchange/src/exchange_loop_metrics.rs
similarity index 100%
rename from bridges/relays/exchange-relay/src/exchange_loop_metrics.rs
rename to bridges/relays/generic/exchange/src/exchange_loop_metrics.rs
diff --git a/bridges/relays/exchange-relay/src/lib.rs b/bridges/relays/generic/exchange/src/lib.rs
similarity index 100%
rename from bridges/relays/exchange-relay/src/lib.rs
rename to bridges/relays/generic/exchange/src/lib.rs
diff --git a/bridges/relays/finality-relay/Cargo.toml b/bridges/relays/generic/finality/Cargo.toml
similarity index 90%
rename from bridges/relays/finality-relay/Cargo.toml
rename to bridges/relays/generic/finality/Cargo.toml
index 9667ba2fa674c7239b03cb87b5173a6ebe16ef61..e70fb39d137e1cc220f956815e52d2c1219ee021 100644
--- a/bridges/relays/finality-relay/Cargo.toml
+++ b/bridges/relays/generic/finality/Cargo.toml
@@ -11,7 +11,7 @@ async-std = "1.6.5"
 async-trait = "0.1.40"
 backoff = "0.2"
 futures = "0.3.5"
-headers-relay = { path = "../headers-relay" }
+headers-relay = { path = "../headers" }
 log = "0.4.11"
 num-traits = "0.2"
 relay-utils = { path = "../utils" }
diff --git a/bridges/relays/finality-relay/src/finality_loop.rs b/bridges/relays/generic/finality/src/finality_loop.rs
similarity index 100%
rename from bridges/relays/finality-relay/src/finality_loop.rs
rename to bridges/relays/generic/finality/src/finality_loop.rs
diff --git a/bridges/relays/finality-relay/src/finality_loop_tests.rs b/bridges/relays/generic/finality/src/finality_loop_tests.rs
similarity index 100%
rename from bridges/relays/finality-relay/src/finality_loop_tests.rs
rename to bridges/relays/generic/finality/src/finality_loop_tests.rs
diff --git a/bridges/relays/finality-relay/src/lib.rs b/bridges/relays/generic/finality/src/lib.rs
similarity index 100%
rename from bridges/relays/finality-relay/src/lib.rs
rename to bridges/relays/generic/finality/src/lib.rs
diff --git a/bridges/relays/headers-relay/Cargo.toml b/bridges/relays/generic/headers/Cargo.toml
similarity index 100%
rename from bridges/relays/headers-relay/Cargo.toml
rename to bridges/relays/generic/headers/Cargo.toml
diff --git a/bridges/relays/headers-relay/src/headers.rs b/bridges/relays/generic/headers/src/headers.rs
similarity index 100%
rename from bridges/relays/headers-relay/src/headers.rs
rename to bridges/relays/generic/headers/src/headers.rs
diff --git a/bridges/relays/headers-relay/src/lib.rs b/bridges/relays/generic/headers/src/lib.rs
similarity index 100%
rename from bridges/relays/headers-relay/src/lib.rs
rename to bridges/relays/generic/headers/src/lib.rs
diff --git a/bridges/relays/headers-relay/src/sync.rs b/bridges/relays/generic/headers/src/sync.rs
similarity index 100%
rename from bridges/relays/headers-relay/src/sync.rs
rename to bridges/relays/generic/headers/src/sync.rs
diff --git a/bridges/relays/headers-relay/src/sync_loop.rs b/bridges/relays/generic/headers/src/sync_loop.rs
similarity index 100%
rename from bridges/relays/headers-relay/src/sync_loop.rs
rename to bridges/relays/generic/headers/src/sync_loop.rs
diff --git a/bridges/relays/headers-relay/src/sync_loop_metrics.rs b/bridges/relays/generic/headers/src/sync_loop_metrics.rs
similarity index 100%
rename from bridges/relays/headers-relay/src/sync_loop_metrics.rs
rename to bridges/relays/generic/headers/src/sync_loop_metrics.rs
diff --git a/bridges/relays/headers-relay/src/sync_loop_tests.rs b/bridges/relays/generic/headers/src/sync_loop_tests.rs
similarity index 100%
rename from bridges/relays/headers-relay/src/sync_loop_tests.rs
rename to bridges/relays/generic/headers/src/sync_loop_tests.rs
diff --git a/bridges/relays/headers-relay/src/sync_types.rs b/bridges/relays/generic/headers/src/sync_types.rs
similarity index 100%
rename from bridges/relays/headers-relay/src/sync_types.rs
rename to bridges/relays/generic/headers/src/sync_types.rs
diff --git a/bridges/relays/messages-relay/Cargo.toml b/bridges/relays/generic/messages/Cargo.toml
similarity index 85%
rename from bridges/relays/messages-relay/Cargo.toml
rename to bridges/relays/generic/messages/Cargo.toml
index 9c2daefdb4271b93b733a4b9ed67de5b2e64a79e..427fe4829d7be5ae3a73a4e4af3803ffd30e0f3a 100644
--- a/bridges/relays/messages-relay/Cargo.toml
+++ b/bridges/relays/generic/messages/Cargo.toml
@@ -15,5 +15,5 @@ parking_lot = "0.11.0"
 
 # Bridge Dependencies
 
-bp-message-lane = { path = "../../primitives/message-lane" }
+bp-message-lane = { path = "../../../primitives/message-lane" }
 relay-utils = { path = "../utils" }
diff --git a/bridges/relays/messages-relay/src/lib.rs b/bridges/relays/generic/messages/src/lib.rs
similarity index 100%
rename from bridges/relays/messages-relay/src/lib.rs
rename to bridges/relays/generic/messages/src/lib.rs
diff --git a/bridges/relays/messages-relay/src/message_lane.rs b/bridges/relays/generic/messages/src/message_lane.rs
similarity index 100%
rename from bridges/relays/messages-relay/src/message_lane.rs
rename to bridges/relays/generic/messages/src/message_lane.rs
diff --git a/bridges/relays/messages-relay/src/message_lane_loop.rs b/bridges/relays/generic/messages/src/message_lane_loop.rs
similarity index 100%
rename from bridges/relays/messages-relay/src/message_lane_loop.rs
rename to bridges/relays/generic/messages/src/message_lane_loop.rs
diff --git a/bridges/relays/messages-relay/src/message_race_delivery.rs b/bridges/relays/generic/messages/src/message_race_delivery.rs
similarity index 100%
rename from bridges/relays/messages-relay/src/message_race_delivery.rs
rename to bridges/relays/generic/messages/src/message_race_delivery.rs
diff --git a/bridges/relays/messages-relay/src/message_race_loop.rs b/bridges/relays/generic/messages/src/message_race_loop.rs
similarity index 100%
rename from bridges/relays/messages-relay/src/message_race_loop.rs
rename to bridges/relays/generic/messages/src/message_race_loop.rs
diff --git a/bridges/relays/messages-relay/src/message_race_receiving.rs b/bridges/relays/generic/messages/src/message_race_receiving.rs
similarity index 100%
rename from bridges/relays/messages-relay/src/message_race_receiving.rs
rename to bridges/relays/generic/messages/src/message_race_receiving.rs
diff --git a/bridges/relays/messages-relay/src/message_race_strategy.rs b/bridges/relays/generic/messages/src/message_race_strategy.rs
similarity index 100%
rename from bridges/relays/messages-relay/src/message_race_strategy.rs
rename to bridges/relays/generic/messages/src/message_race_strategy.rs
diff --git a/bridges/relays/messages-relay/src/metrics.rs b/bridges/relays/generic/messages/src/metrics.rs
similarity index 100%
rename from bridges/relays/messages-relay/src/metrics.rs
rename to bridges/relays/generic/messages/src/metrics.rs
diff --git a/bridges/relays/utils/Cargo.toml b/bridges/relays/generic/utils/Cargo.toml
similarity index 100%
rename from bridges/relays/utils/Cargo.toml
rename to bridges/relays/generic/utils/Cargo.toml
diff --git a/bridges/relays/utils/src/initialize.rs b/bridges/relays/generic/utils/src/initialize.rs
similarity index 100%
rename from bridges/relays/utils/src/initialize.rs
rename to bridges/relays/generic/utils/src/initialize.rs
diff --git a/bridges/relays/utils/src/lib.rs b/bridges/relays/generic/utils/src/lib.rs
similarity index 100%
rename from bridges/relays/utils/src/lib.rs
rename to bridges/relays/generic/utils/src/lib.rs
diff --git a/bridges/relays/utils/src/metrics.rs b/bridges/relays/generic/utils/src/metrics.rs
similarity index 100%
rename from bridges/relays/utils/src/metrics.rs
rename to bridges/relays/generic/utils/src/metrics.rs
diff --git a/bridges/relays/utils/src/relay_loop.rs b/bridges/relays/generic/utils/src/relay_loop.rs
similarity index 100%
rename from bridges/relays/utils/src/relay_loop.rs
rename to bridges/relays/generic/utils/src/relay_loop.rs
diff --git a/bridges/relays/substrate/Cargo.toml b/bridges/relays/substrate/Cargo.toml
index fb5d48120c46b090d8d20b86c9247d4ac724c474..d71cfa2f1e10f674338023dc2e11605853c25ff6 100644
--- a/bridges/relays/substrate/Cargo.toml
+++ b/bridges/relays/substrate/Cargo.toml
@@ -6,6 +6,7 @@ edition = "2018"
 license = "GPL-3.0-or-later WITH Classpath-exception-2.0"
 
 [dependencies]
+anyhow = "1.0"
 async-std = "1.9.0"
 async-trait = "0.1.42"
 codec = { package = "parity-scale-codec", version = "2.0.0" }
@@ -26,19 +27,19 @@ bp-polkadot = { path = "../../primitives/chains/polkadot" }
 bp-runtime = { path = "../../primitives/runtime" }
 bp-rialto = { path = "../../primitives/chains/rialto" }
 bridge-runtime-common = { path = "../../bin/runtime-common" }
-finality-relay = { path = "../finality-relay" }
-headers-relay = { path = "../headers-relay" }
-messages-relay = { path = "../messages-relay" }
+finality-relay = { path = "../generic/finality" }
+headers-relay = { path = "../generic/headers" }
+messages-relay = { path = "../generic/messages" }
 millau-runtime = { path = "../../bin/millau/runtime" }
 pallet-bridge-call-dispatch = { path = "../../modules/call-dispatch" }
 pallet-finality-verifier = { path = "../../modules/finality-verifier" }
 pallet-message-lane = { path = "../../modules/message-lane" }
-relay-kusama-client = { path = "../kusama-client" }
-relay-millau-client = { path = "../millau-client" }
-relay-polkadot-client = { path = "../polkadot-client" }
-relay-rialto-client = { path = "../rialto-client" }
-relay-substrate-client = { path = "../substrate-client" }
-relay-utils = { path = "../utils" }
+relay-kusama-client = { path = "../clients/kusama" }
+relay-millau-client = { path = "../clients/millau" }
+relay-polkadot-client = { path = "../clients/polkadot" }
+relay-rialto-client = { path = "../clients/rialto" }
+relay-substrate-client = { path = "../clients/substrate" }
+relay-utils = { path = "../generic/utils" }
 rialto-runtime = { path = "../../bin/rialto/runtime" }
 
 # Substrate Dependencies
diff --git a/bridges/relays/substrate/src/cli.rs b/bridges/relays/substrate/src/cli.rs
index d9bec5774740e2296e9793ff64ec735380cd9c3b..bfe5f6f54922998a79bfb84b71e19dfff08bdc38 100644
--- a/bridges/relays/substrate/src/cli.rs
+++ b/bridges/relays/substrate/src/cli.rs
@@ -17,12 +17,12 @@
 //! Deal with CLI args of substrate-to-substrate relay.
 
 use bp_message_lane::LaneId;
-use frame_support::weights::Weight;
-use sp_core::Bytes;
-use sp_finality_grandpa::SetId as GrandpaAuthoritiesSetId;
+use codec::{Decode, Encode};
 use sp_runtime::app_crypto::Ss58Codec;
 use structopt::{clap::arg_enum, StructOpt};
 
+use crate::rialto_millau::cli as rialto_millau;
+
 /// Parse relay CLI args.
 pub fn parse_args() -> Command {
 	Command::from_args()
@@ -68,207 +68,140 @@ pub enum Command {
 	DeriveAccount(DeriveAccount),
 }
 
+impl Command {
+	/// Run the command.
+	pub async fn run(self) -> anyhow::Result<()> {
+		match self {
+			Self::InitBridge(arg) => arg.run().await?,
+			Self::RelayHeaders(arg) => arg.run().await?,
+			Self::RelayMessages(arg) => arg.run().await?,
+			Self::SendMessage(arg) => arg.run().await?,
+			Self::EncodeCall(arg) => arg.run().await?,
+			Self::EncodeMessagePayload(arg) => arg.run().await?,
+			Self::EstimateFee(arg) => arg.run().await?,
+			Self::DeriveAccount(arg) => arg.run().await?,
+		}
+		Ok(())
+	}
+}
+
 /// Start headers relayer process.
 #[derive(StructOpt)]
 pub enum RelayHeaders {
-	/// Relay Millau headers to Rialto.
-	MillauToRialto {
-		#[structopt(flatten)]
-		millau: MillauConnectionParams,
-		#[structopt(flatten)]
-		rialto: RialtoConnectionParams,
-		#[structopt(flatten)]
-		rialto_sign: RialtoSigningParams,
-		#[structopt(flatten)]
-		prometheus_params: PrometheusParams,
-	},
-	/// Relay Rialto headers to Millau.
-	RialtoToMillau {
-		#[structopt(flatten)]
-		rialto: RialtoConnectionParams,
-		#[structopt(flatten)]
-		millau: MillauConnectionParams,
-		#[structopt(flatten)]
-		millau_sign: MillauSigningParams,
-		#[structopt(flatten)]
-		prometheus_params: PrometheusParams,
-	},
+	#[structopt(flatten)]
+	RialtoMillau(rialto_millau::RelayHeaders),
+}
+
+impl RelayHeaders {
+	/// Run the command.
+	pub async fn run(self) -> anyhow::Result<()> {
+		match self {
+			Self::RialtoMillau(arg) => arg.run().await?,
+		}
+		Ok(())
+	}
 }
 
 /// Start message relayer process.
 #[derive(StructOpt)]
 pub enum RelayMessages {
-	/// Serve given lane of Millau -> Rialto messages.
-	MillauToRialto {
-		#[structopt(flatten)]
-		millau: MillauConnectionParams,
-		#[structopt(flatten)]
-		millau_sign: MillauSigningParams,
-		#[structopt(flatten)]
-		rialto: RialtoConnectionParams,
-		#[structopt(flatten)]
-		rialto_sign: RialtoSigningParams,
-		#[structopt(flatten)]
-		prometheus_params: PrometheusParams,
-		/// Hex-encoded lane id that should be served by the relay. Defaults to `00000000`.
-		#[structopt(long, default_value = "00000000")]
-		lane: HexLaneId,
-	},
-	/// Serve given lane of Rialto -> Millau messages.
-	RialtoToMillau {
-		#[structopt(flatten)]
-		rialto: RialtoConnectionParams,
-		#[structopt(flatten)]
-		rialto_sign: RialtoSigningParams,
-		#[structopt(flatten)]
-		millau: MillauConnectionParams,
-		#[structopt(flatten)]
-		millau_sign: MillauSigningParams,
-		#[structopt(flatten)]
-		prometheus_params: PrometheusParams,
-		/// Hex-encoded lane id that should be served by the relay. Defaults to `00000000`.
-		#[structopt(long, default_value = "00000000")]
-		lane: HexLaneId,
-	},
+	#[structopt(flatten)]
+	RialtoMillau(rialto_millau::RelayMessages),
+}
+
+impl RelayMessages {
+	/// Run the command.
+	pub async fn run(self) -> anyhow::Result<()> {
+		match self {
+			Self::RialtoMillau(arg) => arg.run().await?,
+		}
+		Ok(())
+	}
 }
 
 /// Initialize bridge pallet.
 #[derive(StructOpt)]
 pub enum InitBridge {
-	/// Initialize Millau headers bridge in Rialto.
-	MillauToRialto {
-		#[structopt(flatten)]
-		millau: MillauConnectionParams,
-		#[structopt(flatten)]
-		rialto: RialtoConnectionParams,
-		#[structopt(flatten)]
-		rialto_sign: RialtoSigningParams,
-		#[structopt(flatten)]
-		millau_bridge_params: MillauBridgeInitializationParams,
-	},
-	/// Initialize Rialto headers bridge in Millau.
-	RialtoToMillau {
-		#[structopt(flatten)]
-		rialto: RialtoConnectionParams,
-		#[structopt(flatten)]
-		millau: MillauConnectionParams,
-		#[structopt(flatten)]
-		millau_sign: MillauSigningParams,
-		#[structopt(flatten)]
-		rialto_bridge_params: RialtoBridgeInitializationParams,
-	},
+	#[structopt(flatten)]
+	RialtoMillau(rialto_millau::InitBridge),
+}
+
+impl InitBridge {
+	/// Run the command.
+	pub async fn run(self) -> anyhow::Result<()> {
+		match self {
+			Self::RialtoMillau(arg) => arg.run().await?,
+		}
+		Ok(())
+	}
 }
 
 /// Send bridge message.
 #[derive(StructOpt)]
 pub enum SendMessage {
-	/// Submit message to given Millau -> Rialto lane.
-	MillauToRialto {
-		#[structopt(flatten)]
-		millau: MillauConnectionParams,
-		#[structopt(flatten)]
-		millau_sign: MillauSigningParams,
-		#[structopt(flatten)]
-		rialto_sign: RialtoSigningParams,
-		/// Hex-encoded lane id. Defaults to `00000000`.
-		#[structopt(long, default_value = "00000000")]
-		lane: HexLaneId,
-		/// Dispatch weight of the message. If not passed, determined automatically.
-		#[structopt(long)]
-		dispatch_weight: Option<ExplicitOrMaximal<Weight>>,
-		/// Delivery and dispatch fee in source chain base currency units. If not passed, determined automatically.
-		#[structopt(long)]
-		fee: Option<bp_millau::Balance>,
-		/// Message type.
-		#[structopt(subcommand)]
-		message: ToRialtoMessage,
-		/// The origin to use when dispatching the message on the target chain. Defaults to
-		/// `SourceAccount`.
-		#[structopt(long, possible_values = &Origins::variants(), default_value = "Source")]
-		origin: Origins,
-	},
-	/// Submit message to given Rialto -> Millau lane.
-	RialtoToMillau {
-		#[structopt(flatten)]
-		rialto: RialtoConnectionParams,
-		#[structopt(flatten)]
-		rialto_sign: RialtoSigningParams,
-		#[structopt(flatten)]
-		millau_sign: MillauSigningParams,
-		/// Hex-encoded lane id. Defaults to `00000000`.
-		#[structopt(long, default_value = "00000000")]
-		lane: HexLaneId,
-		/// Dispatch weight of the message. If not passed, determined automatically.
-		#[structopt(long)]
-		dispatch_weight: Option<ExplicitOrMaximal<Weight>>,
-		/// Delivery and dispatch fee in source chain base currency units. If not passed, determined automatically.
-		#[structopt(long)]
-		fee: Option<bp_rialto::Balance>,
-		/// Message type.
-		#[structopt(subcommand)]
-		message: ToMillauMessage,
-		/// The origin to use when dispatching the message on the target chain. Defaults to
-		/// `SourceAccount`.
-		#[structopt(long, possible_values = &Origins::variants(), default_value = "Source")]
-		origin: Origins,
-	},
+	#[structopt(flatten)]
+	RialtoMillau(rialto_millau::SendMessage),
+}
+
+impl SendMessage {
+	/// Run the command.
+	pub async fn run(self) -> anyhow::Result<()> {
+		match self {
+			Self::RialtoMillau(arg) => arg.run().await?,
+		}
+		Ok(())
+	}
 }
 
 /// A call to encode.
 #[derive(StructOpt)]
 pub enum EncodeCall {
-	/// Encode Rialto's Call.
-	Rialto {
-		#[structopt(flatten)]
-		call: ToRialtoMessage,
-	},
-	/// Encode Millau's Call.
-	Millau {
-		#[structopt(flatten)]
-		call: ToMillauMessage,
-	},
+	#[structopt(flatten)]
+	RialtoMillau(rialto_millau::EncodeCall),
+}
+
+impl EncodeCall {
+	/// Run the command.
+	pub async fn run(self) -> anyhow::Result<()> {
+		match self {
+			Self::RialtoMillau(arg) => arg.run().await?,
+		}
+		Ok(())
+	}
 }
 
 /// A `MessagePayload` to encode.
 #[derive(StructOpt)]
 pub enum EncodeMessagePayload {
-	/// Message Payload of Rialto to Millau call.
-	RialtoToMillau {
-		#[structopt(flatten)]
-		payload: RialtoToMillauMessagePayload,
-	},
-	/// Message Payload of Millau to Rialto call.
-	MillauToRialto {
-		#[structopt(flatten)]
-		payload: MillauToRialtoMessagePayload,
-	},
+	#[structopt(flatten)]
+	RialtoMillau(rialto_millau::EncodeMessagePayload),
+}
+
+impl EncodeMessagePayload {
+	/// Run the command.
+	pub async fn run(self) -> anyhow::Result<()> {
+		match self {
+			Self::RialtoMillau(arg) => arg.run().await?,
+		}
+		Ok(())
+	}
 }
 
 /// Estimate Delivery & Dispatch Fee command.
 #[derive(StructOpt)]
 pub enum EstimateFee {
-	/// Estimate fee of Rialto to Millau message.
-	RialtoToMillau {
-		#[structopt(flatten)]
-		rialto: RialtoConnectionParams,
-		/// Hex-encoded id of lane that will be delivering the message.
-		#[structopt(long)]
-		lane: HexLaneId,
-		/// Payload to send over the bridge.
-		#[structopt(flatten)]
-		payload: RialtoToMillauMessagePayload,
-	},
-	/// Estimate fee of Rialto to Millau message.
-	MillauToRialto {
-		#[structopt(flatten)]
-		millau: MillauConnectionParams,
-		/// Hex-encoded id of lane that will be delivering the message.
-		#[structopt(long)]
-		lane: HexLaneId,
-		/// Payload to send over the bridge.
-		#[structopt(flatten)]
-		payload: MillauToRialtoMessagePayload,
-	},
+	#[structopt(flatten)]
+	RialtoMillau(rialto_millau::EstimateFee),
+}
+
+impl EstimateFee {
+	/// Run the command.
+	pub async fn run(self) -> anyhow::Result<()> {
+		match self {
+			Self::RialtoMillau(arg) => arg.run().await?,
+		}
+		Ok(())
+	}
 }
 
 /// Given a source chain `AccountId`, derive the corresponding `AccountId` for the target chain.
@@ -279,122 +212,18 @@ pub enum EstimateFee {
 /// since messages sent over the bridge will be able to spend these.
 #[derive(StructOpt)]
 pub enum DeriveAccount {
-	/// Given Rialto AccountId, display corresponding Millau AccountId.
-	RialtoToMillau { account: AccountId },
-	/// Given Millau AccountId, display corresponding Rialto AccountId.
-	MillauToRialto { account: AccountId },
-}
-
-/// MessagePayload that can be delivered to message lane pallet on Millau.
-#[derive(StructOpt, Debug)]
-pub enum MillauToRialtoMessagePayload {
-	/// Raw, SCALE-encoded `MessagePayload`.
-	Raw {
-		/// Hex-encoded SCALE data.
-		data: Bytes,
-	},
-	/// Construct message to send over the bridge.
-	Message {
-		/// Message details.
-		#[structopt(flatten)]
-		message: ToRialtoMessage,
-		/// SS58 encoded account that will send the payload (must have SS58Prefix = 42)
-		#[structopt(long)]
-		sender: AccountId,
-	},
-}
-
-/// MessagePayload that can be delivered to message lane pallet on Rialto.
-#[derive(StructOpt, Debug)]
-pub enum RialtoToMillauMessagePayload {
-	/// Raw, SCALE-encoded `MessagePayload`.
-	Raw {
-		/// Hex-encoded SCALE data.
-		data: Bytes,
-	},
-	/// Construct message to send over the bridge.
-	Message {
-		/// Message details.
-		#[structopt(flatten)]
-		message: ToMillauMessage,
-		/// SS58 encoded account that will send the payload (must have SS58Prefix = 42)
-		#[structopt(long)]
-		sender: AccountId,
-	},
-}
-
-/// All possible messages that may be delivered to the Rialto chain.
-#[derive(StructOpt, Debug)]
-pub enum ToRialtoMessage {
-	/// Raw bytes for the message
-	Raw {
-		/// Raw, SCALE-encoded message
-		data: Bytes,
-	},
-	/// Make an on-chain remark (comment).
-	Remark {
-		/// Remark size. If not passed, small UTF8-encoded string is generated by relay as remark.
-		#[structopt(long)]
-		remark_size: Option<ExplicitOrMaximal<usize>>,
-	},
-	/// Transfer the specified `amount` of native tokens to a particular `recipient`.
-	Transfer {
-		/// SS58 encoded account that will receive the transfer (must have SS58Prefix = 42)
-		#[structopt(long)]
-		recipient: AccountId,
-		/// Amount of target tokens to send in target chain base currency units.
-		#[structopt(long)]
-		amount: bp_rialto::Balance,
-	},
-	/// A call to the Millau Bridge Message Lane pallet to send a message over the bridge.
-	MillauSendMessage {
-		/// Hex-encoded lane id that should be served by the relay. Defaults to `00000000`.
-		#[structopt(long, default_value = "00000000")]
-		lane: HexLaneId,
-		/// Raw SCALE-encoded Message Payload to submit to the message lane pallet.
-		#[structopt(long)]
-		payload: Bytes,
-		/// Declared delivery and dispatch fee in base source-chain currency units.
-		#[structopt(long)]
-		fee: bp_rialto::Balance,
-	},
-}
-
-/// All possible messages that may be delivered to the Millau chain.
-#[derive(StructOpt, Debug)]
-pub enum ToMillauMessage {
-	/// Raw bytes for the message
-	Raw {
-		/// Raw, SCALE-encoded message
-		data: Bytes,
-	},
-	/// Make an on-chain remark (comment).
-	Remark {
-		/// Size of the remark. If not passed, small UTF8-encoded string is generated by relay as remark.
-		#[structopt(long)]
-		remark_size: Option<ExplicitOrMaximal<usize>>,
-	},
-	/// Transfer the specified `amount` of native tokens to a particular `recipient`.
-	Transfer {
-		/// SS58 encoded account that will receive the transfer (must have SS58Prefix = 42)
-		#[structopt(long)]
-		recipient: AccountId,
-		/// Amount of target tokens to send in target chain base currency units.
-		#[structopt(long)]
-		amount: bp_millau::Balance,
-	},
-	/// A call to the Rialto Bridge Message Lane pallet to send a message over the bridge.
-	RialtoSendMessage {
-		/// Hex-encoded lane id that should be served by the relay. Defaults to `00000000`.
-		#[structopt(long, default_value = "00000000")]
-		lane: HexLaneId,
-		/// Raw SCALE-encoded Message Payload to submit to the message lane pallet.
-		#[structopt(long)]
-		payload: Bytes,
-		/// Declared delivery and dispatch fee in base source-chain currency units.
-		#[structopt(long)]
-		fee: bp_millau::Balance,
-	},
+	#[structopt(flatten)]
+	RialtoMillau(rialto_millau::DeriveAccount),
+}
+
+impl DeriveAccount {
+	/// Run the command.
+	pub async fn run(self) -> anyhow::Result<()> {
+		match self {
+			Self::RialtoMillau(arg) => arg.run().await?,
+		}
+		Ok(())
+	}
 }
 
 arg_enum! {
@@ -456,7 +285,7 @@ impl AccountId {
 
 /// Lane id.
 #[derive(Debug)]
-pub struct HexLaneId(LaneId);
+pub struct HexLaneId(pub LaneId);
 
 impl From<HexLaneId> for LaneId {
 	fn from(lane_id: HexLaneId) -> LaneId {
@@ -474,6 +303,31 @@ impl std::str::FromStr for HexLaneId {
 	}
 }
 
+/// Nicer formatting for raw bytes vectors.
+#[derive(Encode, Decode)]
+pub struct HexBytes(pub Vec<u8>);
+
+impl std::str::FromStr for HexBytes {
+	type Err = hex::FromHexError;
+
+	fn from_str(s: &str) -> Result<Self, Self::Err> {
+		Ok(Self(hex::decode(s)?))
+	}
+}
+
+impl std::fmt::Debug for HexBytes {
+	fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
+		write!(fmt, "0x{}", hex::encode(&self.0))
+	}
+}
+
+impl HexBytes {
+	/// Encode given object and wrap into nicely formatted bytes.
+	pub fn encode<T: Encode>(t: &T) -> Self {
+		Self(t.encode())
+	}
+}
+
 /// Prometheus metrics params.
 #[derive(StructOpt)]
 pub struct PrometheusParams {
@@ -527,6 +381,9 @@ where
 	}
 }
 
+/// Create chain-specific set of configuration objects: connection parameters,
+/// signing parameters and bridge initialisation parameters.
+#[macro_export]
 macro_rules! declare_chain_options {
 	($chain:ident, $chain_prefix:ident) => {
 		paste::item! {
@@ -557,17 +414,14 @@ macro_rules! declare_chain_options {
 			pub struct [<$chain BridgeInitializationParams>] {
 				#[doc = "Hex-encoded " $chain " header to initialize bridge with. If not specified, genesis header is used."]
 				#[structopt(long)]
-				pub [<$chain_prefix _initial_header>]: Option<Bytes>,
+				pub [<$chain_prefix _initial_header>]: Option<sp_core::Bytes>,
 				#[doc = "Hex-encoded " $chain " GRANDPA authorities set to initialize bridge with. If not specified, set from genesis block is used."]
 				#[structopt(long)]
-				pub [<$chain_prefix _initial_authorities>]: Option<Bytes>,
+				pub [<$chain_prefix _initial_authorities>]: Option<sp_core::Bytes>,
 				#[doc = "Id of the " $chain " GRANDPA authorities set to initialize bridge with. If not specified, zero is used."]
 				#[structopt(long)]
-				pub [<$chain_prefix _initial_authorities_set_id>]: Option<GrandpaAuthoritiesSetId>,
+				pub [<$chain_prefix _initial_authorities_set_id>]: Option<sp_finality_grandpa::SetId>,
 			}
 		}
 	};
 }
-
-declare_chain_options!(Rialto, rialto);
-declare_chain_options!(Millau, millau);
diff --git a/bridges/relays/substrate/src/main.rs b/bridges/relays/substrate/src/main.rs
index 8cd437027c95cd2cf9acb680ada9b1a781d36422..eaaa9848836e9c9020919c2827a138dcdeb85f00 100644
--- a/bridges/relays/substrate/src/main.rs
+++ b/bridges/relays/substrate/src/main.rs
@@ -18,24 +18,7 @@
 
 #![warn(missing_docs)]
 
-use codec::{Decode, Encode};
-use frame_support::weights::{GetDispatchInfo, Weight};
-use pallet_bridge_call_dispatch::{CallOrigin, MessagePayload};
-use relay_kusama_client::Kusama;
-use relay_millau_client::{Millau, SigningParams as MillauSigningParams};
-use relay_rialto_client::{Rialto, SigningParams as RialtoSigningParams};
-use relay_substrate_client::{Chain, ConnectionParams, TransactionSignScheme};
 use relay_utils::initialize::initialize_relay;
-use sp_core::{Bytes, Pair};
-use sp_runtime::traits::IdentifyAccount;
-use std::fmt::Debug;
-
-/// Kusama node client.
-pub type KusamaClient = relay_substrate_client::Client<Kusama>;
-/// Millau node client.
-pub type MillauClient = relay_substrate_client::Client<Millau>;
-/// Rialto node client.
-pub type RialtoClient = relay_substrate_client::Client<Rialto>;
 
 mod cli;
 mod finality_pipeline;
@@ -44,929 +27,15 @@ mod headers_initialize;
 mod messages_lane;
 mod messages_source;
 mod messages_target;
-mod millau_headers_to_rialto;
-mod millau_messages_to_rialto;
-mod rialto_headers_to_millau;
-mod rialto_messages_to_millau;
+
+mod rialto_millau;
 
 fn main() {
 	initialize_relay();
-
-	let result = async_std::task::block_on(run_command(cli::parse_args()));
+	let command = cli::parse_args();
+	let run = command.run();
+	let result = async_std::task::block_on(run);
 	if let Err(error) = result {
 		log::error!(target: "bridge", "Failed to start relay: {}", error);
 	}
 }
-
-async fn run_command(command: cli::Command) -> Result<(), String> {
-	match command {
-		cli::Command::InitBridge(arg) => run_init_bridge(arg).await,
-		cli::Command::RelayHeaders(arg) => run_relay_headers(arg).await,
-		cli::Command::RelayMessages(arg) => run_relay_messages(arg).await,
-		cli::Command::SendMessage(arg) => run_send_message(arg).await,
-		cli::Command::EncodeCall(arg) => run_encode_call(arg).await,
-		cli::Command::EncodeMessagePayload(arg) => run_encode_message_payload(arg).await,
-		cli::Command::EstimateFee(arg) => run_estimate_fee(arg).await,
-		cli::Command::DeriveAccount(arg) => run_derive_account(arg).await,
-	}
-}
-
-async fn run_init_bridge(command: cli::InitBridge) -> Result<(), String> {
-	match command {
-		cli::InitBridge::MillauToRialto {
-			millau,
-			rialto,
-			rialto_sign,
-			millau_bridge_params,
-		} => {
-			let millau_client = millau.into_client().await?;
-			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?;
-
-			headers_initialize::initialize(
-				millau_client,
-				rialto_client.clone(),
-				millau_bridge_params.millau_initial_header,
-				millau_bridge_params.millau_initial_authorities,
-				millau_bridge_params.millau_initial_authorities_set_id,
-				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::FinalityBridgeMillauCall::initialize(initialization_data).into(),
-							))
-							.into(),
-						)
-						.encode(),
-					))
-				},
-			)
-			.await;
-		}
-		cli::InitBridge::RialtoToMillau {
-			rialto,
-			millau,
-			millau_sign,
-			rialto_bridge_params,
-		} => {
-			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?;
-
-			headers_initialize::initialize(
-				rialto_client,
-				millau_client.clone(),
-				rialto_bridge_params.rialto_initial_header,
-				rialto_bridge_params.rialto_initial_authorities,
-				rialto_bridge_params.rialto_initial_authorities_set_id,
-				move |initialization_data| {
-					Ok(Bytes(
-						Millau::sign_transaction(
-							*millau_client.genesis_hash(),
-							&millau_sign.signer,
-							millau_signer_next_index,
-							millau_runtime::SudoCall::sudo(Box::new(
-								millau_runtime::FinalityBridgeRialtoCall::initialize(initialization_data).into(),
-							))
-							.into(),
-						)
-						.encode(),
-					))
-				},
-			)
-			.await;
-		}
-	}
-	Ok(())
-}
-
-async fn run_relay_headers(command: cli::RelayHeaders) -> Result<(), String> {
-	match command {
-		cli::RelayHeaders::MillauToRialto {
-			millau,
-			rialto,
-			rialto_sign,
-			prometheus_params,
-		} => {
-			let millau_client = millau.into_client().await?;
-			let rialto_client = rialto.into_client().await?;
-			let rialto_sign = rialto_sign.parse()?;
-			millau_headers_to_rialto::run(millau_client, rialto_client, rialto_sign, prometheus_params.into()).await;
-		}
-		cli::RelayHeaders::RialtoToMillau {
-			rialto,
-			millau,
-			millau_sign,
-			prometheus_params,
-		} => {
-			let rialto_client = rialto.into_client().await?;
-			let millau_client = millau.into_client().await?;
-			let millau_sign = millau_sign.parse()?;
-			rialto_headers_to_millau::run(rialto_client, millau_client, millau_sign, prometheus_params.into()).await;
-		}
-	}
-	Ok(())
-}
-
-async fn run_relay_messages(command: cli::RelayMessages) -> Result<(), String> {
-	match command {
-		cli::RelayMessages::MillauToRialto {
-			millau,
-			millau_sign,
-			rialto,
-			rialto_sign,
-			prometheus_params,
-			lane,
-		} => {
-			let millau_client = millau.into_client().await?;
-			let millau_sign = millau_sign.parse()?;
-			let rialto_client = rialto.into_client().await?;
-			let rialto_sign = rialto_sign.parse()?;
-
-			millau_messages_to_rialto::run(
-				millau_client,
-				millau_sign,
-				rialto_client,
-				rialto_sign,
-				lane.into(),
-				prometheus_params.into(),
-			);
-		}
-		cli::RelayMessages::RialtoToMillau {
-			rialto,
-			rialto_sign,
-			millau,
-			millau_sign,
-			prometheus_params,
-			lane,
-		} => {
-			let rialto_client = rialto.into_client().await?;
-			let rialto_sign = rialto_sign.parse()?;
-			let millau_client = millau.into_client().await?;
-			let millau_sign = millau_sign.parse()?;
-
-			rialto_messages_to_millau::run(
-				rialto_client,
-				rialto_sign,
-				millau_client,
-				millau_sign,
-				lane.into(),
-				prometheus_params.into(),
-			);
-		}
-	}
-	Ok(())
-}
-
-async fn run_send_message(command: cli::SendMessage) -> Result<(), String> {
-	match command {
-		cli::SendMessage::MillauToRialto {
-			millau,
-			millau_sign,
-			rialto_sign,
-			lane,
-			message,
-			dispatch_weight,
-			fee,
-			origin,
-			..
-		} => {
-			let millau_client = millau.into_client().await?;
-			let millau_sign = millau_sign.parse()?;
-			let rialto_sign = rialto_sign.parse()?;
-			let rialto_call = message.into_call()?;
-
-			let payload =
-				millau_to_rialto_message_payload(&millau_sign, &rialto_sign, &rialto_call, origin, dispatch_weight);
-			let dispatch_weight = payload.weight;
-
-			let lane = lane.into();
-			let fee = get_fee(fee, || {
-				estimate_message_delivery_and_dispatch_fee(
-					&millau_client,
-					bp_rialto::TO_RIALTO_ESTIMATE_MESSAGE_FEE_METHOD,
-					lane,
-					payload.clone(),
-				)
-			})
-			.await?;
-
-			let millau_call = millau_runtime::Call::BridgeRialtoMessageLane(
-				millau_runtime::MessageLaneCall::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?;
-		}
-		cli::SendMessage::RialtoToMillau {
-			rialto,
-			rialto_sign,
-			millau_sign,
-			lane,
-			message,
-			dispatch_weight,
-			fee,
-			origin,
-			..
-		} => {
-			let rialto_client = rialto.into_client().await?;
-			let rialto_sign = rialto_sign.parse()?;
-			let millau_sign = millau_sign.parse()?;
-			let millau_call = message.into_call()?;
-
-			let payload =
-				rialto_to_millau_message_payload(&rialto_sign, &millau_sign, &millau_call, origin, dispatch_weight);
-			let dispatch_weight = payload.weight;
-
-			let lane = lane.into();
-			let fee = get_fee(fee, || {
-				estimate_message_delivery_and_dispatch_fee(
-					&rialto_client,
-					bp_millau::TO_MILLAU_ESTIMATE_MESSAGE_FEE_METHOD,
-					lane,
-					payload.clone(),
-				)
-			})
-			.await?;
-
-			let rialto_call = rialto_runtime::Call::BridgeMillauMessageLane(
-				rialto_runtime::MessageLaneCall::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?;
-		}
-	}
-	Ok(())
-}
-
-async fn run_encode_call(call: cli::EncodeCall) -> Result<(), String> {
-	match call {
-		cli::EncodeCall::Rialto { call } => {
-			let call = call.into_call()?;
-
-			println!("{:?}", HexBytes::encode(&call));
-		}
-		cli::EncodeCall::Millau { call } => {
-			let call = call.into_call()?;
-			println!("{:?}", HexBytes::encode(&call));
-		}
-	}
-	Ok(())
-}
-
-async fn run_encode_message_payload(call: cli::EncodeMessagePayload) -> Result<(), String> {
-	match call {
-		cli::EncodeMessagePayload::RialtoToMillau { payload } => {
-			let payload = payload.into_payload()?;
-
-			println!("{:?}", HexBytes::encode(&payload));
-		}
-		cli::EncodeMessagePayload::MillauToRialto { payload } => {
-			let payload = payload.into_payload()?;
-
-			println!("{:?}", HexBytes::encode(&payload));
-		}
-	}
-	Ok(())
-}
-
-async fn run_estimate_fee(cmd: cli::EstimateFee) -> Result<(), String> {
-	match cmd {
-		cli::EstimateFee::RialtoToMillau { rialto, lane, payload } => {
-			let client = rialto.into_client().await?;
-			let lane = lane.into();
-			let payload = payload.into_payload()?;
-
-			let fee: Option<bp_rialto::Balance> = estimate_message_delivery_and_dispatch_fee(
-				&client,
-				bp_millau::TO_MILLAU_ESTIMATE_MESSAGE_FEE_METHOD,
-				lane,
-				payload,
-			)
-			.await?;
-
-			println!("Fee: {:?}", fee);
-		}
-		cli::EstimateFee::MillauToRialto { millau, lane, payload } => {
-			let client = millau.into_client().await?;
-			let lane = lane.into();
-			let payload = payload.into_payload()?;
-
-			let fee: Option<bp_millau::Balance> = estimate_message_delivery_and_dispatch_fee(
-				&client,
-				bp_rialto::TO_RIALTO_ESTIMATE_MESSAGE_FEE_METHOD,
-				lane,
-				payload,
-			)
-			.await?;
-
-			println!("Fee: {:?}", fee);
-		}
-	}
-
-	Ok(())
-}
-
-async fn run_derive_account(cmd: cli::DeriveAccount) -> Result<(), String> {
-	match cmd {
-		cli::DeriveAccount::RialtoToMillau { account } => {
-			let account = account.into_rialto();
-			let acc = bp_runtime::SourceAccount::Account(account.clone());
-			let id = bp_millau::derive_account_from_rialto_id(acc);
-			println!(
-				"{} (Rialto)\n\nCorresponding (derived) account id:\n-> {} (Millau)",
-				account, id
-			)
-		}
-		cli::DeriveAccount::MillauToRialto { account } => {
-			let account = account.into_millau();
-			let acc = bp_runtime::SourceAccount::Account(account.clone());
-			let id = bp_rialto::derive_account_from_millau_id(acc);
-			println!(
-				"{} (Millau)\n\nCorresponding (derived) account id:\n-> {} (Rialto)",
-				account, id
-			)
-		}
-	}
-
-	Ok(())
-}
-
-async fn estimate_message_delivery_and_dispatch_fee<Fee: Decode, C: Chain, P: Encode>(
-	client: &relay_substrate_client::Client<C>,
-	estimate_fee_method: &str,
-	lane: bp_message_lane::LaneId,
-	payload: P,
-) -> Result<Option<Fee>, relay_substrate_client::Error> {
-	let encoded_response = client
-		.state_call(estimate_fee_method.into(), (lane, payload).encode().into(), None)
-		.await?;
-	let decoded_response: Option<Fee> =
-		Decode::decode(&mut &encoded_response.0[..]).map_err(relay_substrate_client::Error::ResponseParseFailed)?;
-	Ok(decoded_response)
-}
-
-fn remark_payload(remark_size: Option<cli::ExplicitOrMaximal<usize>>, maximal_allowed_size: u32) -> Vec<u8> {
-	match remark_size {
-		Some(cli::ExplicitOrMaximal::Explicit(remark_size)) => vec![0; remark_size],
-		Some(cli::ExplicitOrMaximal::Maximal) => vec![0; maximal_allowed_size as _],
-		None => format!(
-			"Unix time: {}",
-			std::time::SystemTime::now()
-				.duration_since(std::time::SystemTime::UNIX_EPOCH)
-				.unwrap_or_default()
-				.as_secs(),
-		)
-		.as_bytes()
-		.to_vec(),
-	}
-}
-
-fn message_payload<SAccountId, TPublic, TSignature>(
-	spec_version: u32,
-	weight: Weight,
-	origin: CallOrigin<SAccountId, TPublic, TSignature>,
-	call: &impl Encode,
-) -> MessagePayload<SAccountId, TPublic, TSignature, Vec<u8>>
-where
-	SAccountId: Encode + Debug,
-	TPublic: Encode + Debug,
-	TSignature: Encode + Debug,
-{
-	// Display nicely formatted call.
-	let payload = MessagePayload {
-		spec_version,
-		weight,
-		origin,
-		call: HexBytes::encode(call),
-	};
-
-	log::info!(target: "bridge", "Created Message Payload: {:#?}", payload);
-	log::info!(target: "bridge", "Encoded Message Payload: {:?}", HexBytes::encode(&payload));
-
-	// re-pack to return `Vec<u8>`
-	let MessagePayload {
-		spec_version,
-		weight,
-		origin,
-		call,
-	} = payload;
-	MessagePayload {
-		spec_version,
-		weight,
-		origin,
-		call: call.0,
-	}
-}
-
-fn rialto_to_millau_message_payload(
-	rialto_sign: &RialtoSigningParams,
-	millau_sign: &MillauSigningParams,
-	millau_call: &millau_runtime::Call,
-	origin: cli::Origins,
-	user_specified_dispatch_weight: Option<cli::ExplicitOrMaximal<Weight>>,
-) -> rialto_runtime::millau_messages::ToMillauMessagePayload {
-	let millau_call_weight = prepare_call_dispatch_weight(
-		user_specified_dispatch_weight,
-		cli::ExplicitOrMaximal::Explicit(millau_call.get_dispatch_info().weight),
-		compute_maximal_message_dispatch_weight(bp_millau::max_extrinsic_weight()),
-	);
-	let rialto_sender_public: bp_rialto::AccountSigner = rialto_sign.signer.public().clone().into();
-	let rialto_account_id: bp_rialto::AccountId = rialto_sender_public.into_account();
-	let millau_origin_public = millau_sign.signer.public();
-
-	message_payload(
-		millau_runtime::VERSION.spec_version,
-		millau_call_weight,
-		match origin {
-			cli::Origins::Source => CallOrigin::SourceAccount(rialto_account_id),
-			cli::Origins::Target => {
-				let digest = rialto_runtime::millau_account_ownership_digest(
-					&millau_call,
-					rialto_account_id.clone(),
-					millau_runtime::VERSION.spec_version,
-				);
-
-				let digest_signature = millau_sign.signer.sign(&digest);
-
-				CallOrigin::TargetAccount(rialto_account_id, millau_origin_public.into(), digest_signature.into())
-			}
-		},
-		&millau_call,
-	)
-}
-
-fn millau_to_rialto_message_payload(
-	millau_sign: &MillauSigningParams,
-	rialto_sign: &RialtoSigningParams,
-	rialto_call: &rialto_runtime::Call,
-	origin: cli::Origins,
-	user_specified_dispatch_weight: Option<cli::ExplicitOrMaximal<Weight>>,
-) -> millau_runtime::rialto_messages::ToRialtoMessagePayload {
-	let rialto_call_weight = prepare_call_dispatch_weight(
-		user_specified_dispatch_weight,
-		cli::ExplicitOrMaximal::Explicit(rialto_call.get_dispatch_info().weight),
-		compute_maximal_message_dispatch_weight(bp_rialto::max_extrinsic_weight()),
-	);
-	let millau_sender_public: bp_millau::AccountSigner = millau_sign.signer.public().clone().into();
-	let millau_account_id: bp_millau::AccountId = millau_sender_public.into_account();
-	let rialto_origin_public = rialto_sign.signer.public();
-
-	message_payload(
-		rialto_runtime::VERSION.spec_version,
-		rialto_call_weight,
-		match origin {
-			cli::Origins::Source => CallOrigin::SourceAccount(millau_account_id),
-			cli::Origins::Target => {
-				let digest = millau_runtime::rialto_account_ownership_digest(
-					&rialto_call,
-					millau_account_id.clone(),
-					rialto_runtime::VERSION.spec_version,
-				);
-
-				let digest_signature = rialto_sign.signer.sign(&digest);
-
-				CallOrigin::TargetAccount(millau_account_id, rialto_origin_public.into(), digest_signature.into())
-			}
-		},
-		&rialto_call,
-	)
-}
-
-fn prepare_call_dispatch_weight(
-	user_specified_dispatch_weight: Option<cli::ExplicitOrMaximal<Weight>>,
-	weight_from_pre_dispatch_call: cli::ExplicitOrMaximal<Weight>,
-	maximal_allowed_weight: Weight,
-) -> Weight {
-	match user_specified_dispatch_weight.unwrap_or(weight_from_pre_dispatch_call) {
-		cli::ExplicitOrMaximal::Explicit(weight) => weight,
-		cli::ExplicitOrMaximal::Maximal => maximal_allowed_weight,
-	}
-}
-
-async fn get_fee<Fee, F, R, E>(fee: Option<Fee>, f: F) -> Result<Fee, String>
-where
-	Fee: Decode,
-	F: FnOnce() -> R,
-	R: std::future::Future<Output = Result<Option<Fee>, E>>,
-	E: Debug,
-{
-	match fee {
-		Some(fee) => Ok(fee),
-		None => match f().await {
-			Ok(Some(fee)) => Ok(fee),
-			Ok(None) => Err("Failed to estimate message fee. Message is too heavy?".into()),
-			Err(error) => Err(format!("Failed to estimate message fee: {:?}", error)),
-		},
-	}
-}
-
-fn compute_maximal_message_dispatch_weight(maximal_extrinsic_weight: Weight) -> Weight {
-	bridge_runtime_common::messages::target::maximal_incoming_message_dispatch_weight(maximal_extrinsic_weight)
-}
-
-fn compute_maximal_message_arguments_size(
-	maximal_source_extrinsic_size: u32,
-	maximal_target_extrinsic_size: u32,
-) -> u32 {
-	// assume that both signed extensions and other arguments fit 1KB
-	let service_tx_bytes_on_source_chain = 1024;
-	let maximal_source_extrinsic_size = maximal_source_extrinsic_size - service_tx_bytes_on_source_chain;
-	let maximal_call_size =
-		bridge_runtime_common::messages::target::maximal_incoming_message_size(maximal_target_extrinsic_size);
-	let maximal_call_size = if maximal_call_size > maximal_source_extrinsic_size {
-		maximal_source_extrinsic_size
-	} else {
-		maximal_call_size
-	};
-
-	// bytes in Call encoding that are used to encode everything except arguments
-	let service_bytes = 1 + 1 + 4;
-	maximal_call_size - service_bytes
-}
-
-impl crate::cli::MillauToRialtoMessagePayload {
-	/// Parse the CLI parameters and construct message payload.
-	pub fn into_payload(
-		self,
-	) -> Result<MessagePayload<bp_rialto::AccountId, bp_rialto::AccountSigner, bp_rialto::Signature, Vec<u8>>, String> {
-		match self {
-			Self::Raw { data } => MessagePayload::decode(&mut &*data.0)
-				.map_err(|e| format!("Failed to decode Millau's MessagePayload: {:?}", e)),
-			Self::Message { message, sender } => {
-				let spec_version = rialto_runtime::VERSION.spec_version;
-				let origin = CallOrigin::SourceAccount(sender.into_millau());
-				let call = message.into_call()?;
-				let weight = call.get_dispatch_info().weight;
-
-				Ok(message_payload(spec_version, weight, origin, &call))
-			}
-		}
-	}
-}
-
-impl crate::cli::RialtoToMillauMessagePayload {
-	/// Parse the CLI parameters and construct message payload.
-	pub fn into_payload(
-		self,
-	) -> Result<MessagePayload<bp_millau::AccountId, bp_millau::AccountSigner, bp_millau::Signature, Vec<u8>>, String> {
-		match self {
-			Self::Raw { data } => MessagePayload::decode(&mut &*data.0)
-				.map_err(|e| format!("Failed to decode Rialto's MessagePayload: {:?}", e)),
-			Self::Message { message, sender } => {
-				let spec_version = millau_runtime::VERSION.spec_version;
-				let origin = CallOrigin::SourceAccount(sender.into_rialto());
-				let call = message.into_call()?;
-				let weight = call.get_dispatch_info().weight;
-
-				Ok(message_payload(spec_version, weight, origin, &call))
-			}
-		}
-	}
-}
-
-impl crate::cli::RialtoSigningParams {
-	/// Parse CLI parameters into typed signing params.
-	pub fn parse(self) -> Result<RialtoSigningParams, String> {
-		RialtoSigningParams::from_suri(&self.rialto_signer, self.rialto_signer_password.as_deref())
-			.map_err(|e| format!("Failed to parse rialto-signer: {:?}", e))
-	}
-}
-
-impl crate::cli::MillauSigningParams {
-	/// Parse CLI parameters into typed signing params.
-	pub fn parse(self) -> Result<MillauSigningParams, String> {
-		MillauSigningParams::from_suri(&self.millau_signer, self.millau_signer_password.as_deref())
-			.map_err(|e| format!("Failed to parse millau-signer: {:?}", e))
-	}
-}
-
-impl crate::cli::MillauConnectionParams {
-	/// Convert CLI connection parameters into Millau RPC Client.
-	pub async fn into_client(self) -> relay_substrate_client::Result<MillauClient> {
-		MillauClient::new(ConnectionParams {
-			host: self.millau_host,
-			port: self.millau_port,
-		})
-		.await
-	}
-}
-impl crate::cli::RialtoConnectionParams {
-	/// Convert CLI connection parameters into Rialto RPC Client.
-	pub async fn into_client(self) -> relay_substrate_client::Result<RialtoClient> {
-		RialtoClient::new(ConnectionParams {
-			host: self.rialto_host,
-			port: self.rialto_port,
-		})
-		.await
-	}
-}
-
-impl crate::cli::ToRialtoMessage {
-	/// Convert CLI call request into runtime `Call` instance.
-	pub fn into_call(self) -> Result<rialto_runtime::Call, String> {
-		let call = match self {
-			cli::ToRialtoMessage::Raw { data } => {
-				Decode::decode(&mut &*data.0).map_err(|e| format!("Unable to decode message: {:#?}", e))?
-			}
-			cli::ToRialtoMessage::Remark { remark_size } => {
-				rialto_runtime::Call::System(rialto_runtime::SystemCall::remark(remark_payload(
-					remark_size,
-					compute_maximal_message_arguments_size(
-						bp_millau::max_extrinsic_size(),
-						bp_rialto::max_extrinsic_size(),
-					),
-				)))
-			}
-			cli::ToRialtoMessage::Transfer { recipient, amount } => {
-				let recipient = recipient.into_rialto();
-				rialto_runtime::Call::Balances(rialto_runtime::BalancesCall::transfer(recipient, amount))
-			}
-			cli::ToRialtoMessage::MillauSendMessage { lane, payload, fee } => {
-				let payload = cli::RialtoToMillauMessagePayload::Raw { data: payload }.into_payload()?;
-				let lane = lane.into();
-				rialto_runtime::Call::BridgeMillauMessageLane(rialto_runtime::MessageLaneCall::send_message(
-					lane, payload, fee,
-				))
-			}
-		};
-
-		log::info!(target: "bridge", "Generated Rialto call: {:#?}", call);
-		log::info!(target: "bridge", "Weight of Rialto call: {}", call.get_dispatch_info().weight);
-		log::info!(target: "bridge", "Encoded Rialto call: {:?}", HexBytes::encode(&call));
-
-		Ok(call)
-	}
-}
-
-impl crate::cli::ToMillauMessage {
-	/// Convert CLI call request into runtime `Call` instance.
-	pub fn into_call(self) -> Result<millau_runtime::Call, String> {
-		let call = match self {
-			cli::ToMillauMessage::Raw { data } => {
-				Decode::decode(&mut &*data.0).map_err(|e| format!("Unable to decode message: {:#?}", e))?
-			}
-			cli::ToMillauMessage::Remark { remark_size } => {
-				millau_runtime::Call::System(millau_runtime::SystemCall::remark(remark_payload(
-					remark_size,
-					compute_maximal_message_arguments_size(
-						bp_rialto::max_extrinsic_size(),
-						bp_millau::max_extrinsic_size(),
-					),
-				)))
-			}
-			cli::ToMillauMessage::Transfer { recipient, amount } => {
-				let recipient = recipient.into_millau();
-				millau_runtime::Call::Balances(millau_runtime::BalancesCall::transfer(recipient, amount))
-			}
-			cli::ToMillauMessage::RialtoSendMessage { lane, payload, fee } => {
-				let payload = cli::MillauToRialtoMessagePayload::Raw { data: payload }.into_payload()?;
-				let lane = lane.into();
-				millau_runtime::Call::BridgeRialtoMessageLane(millau_runtime::MessageLaneCall::send_message(
-					lane, payload, fee,
-				))
-			}
-		};
-
-		log::info!(target: "bridge", "Generated Millau call: {:#?}", call);
-		log::info!(target: "bridge", "Weight of Millau call: {}", call.get_dispatch_info().weight);
-		log::info!(target: "bridge", "Encoded Millau call: {:?}", HexBytes::encode(&call));
-
-		Ok(call)
-	}
-}
-
-/// Nicer formatting for raw bytes vectors.
-#[derive(Encode, Decode)]
-struct HexBytes(Vec<u8>);
-
-impl Debug for HexBytes {
-	fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
-		write!(fmt, "0x{}", hex::encode(&self.0))
-	}
-}
-
-impl HexBytes {
-	/// Encode given object and wrap into nicely formatted bytes.
-	pub fn encode<T: Encode>(t: &T) -> Self {
-		Self(t.encode())
-	}
-}
-
-#[cfg(test)]
-mod tests {
-	use super::*;
-	use bp_message_lane::source_chain::TargetHeaderChain;
-	use sp_core::Pair;
-	use sp_runtime::traits::{IdentifyAccount, Verify};
-
-	#[test]
-	fn millau_signature_is_valid_on_rialto() {
-		let millau_sign = relay_millau_client::SigningParams::from_suri("//Dave", None).unwrap();
-
-		let call = rialto_runtime::Call::System(rialto_runtime::SystemCall::remark(vec![]));
-
-		let millau_public: bp_millau::AccountSigner = millau_sign.signer.public().clone().into();
-		let millau_account_id: bp_millau::AccountId = millau_public.into_account();
-
-		let digest = millau_runtime::rialto_account_ownership_digest(
-			&call,
-			millau_account_id,
-			rialto_runtime::VERSION.spec_version,
-		);
-
-		let rialto_signer = relay_rialto_client::SigningParams::from_suri("//Dave", None).unwrap();
-		let signature = rialto_signer.signer.sign(&digest);
-
-		assert!(signature.verify(&digest[..], &rialto_signer.signer.public()));
-	}
-
-	#[test]
-	fn rialto_signature_is_valid_on_millau() {
-		let rialto_sign = relay_rialto_client::SigningParams::from_suri("//Dave", None).unwrap();
-
-		let call = millau_runtime::Call::System(millau_runtime::SystemCall::remark(vec![]));
-
-		let rialto_public: bp_rialto::AccountSigner = rialto_sign.signer.public().clone().into();
-		let rialto_account_id: bp_rialto::AccountId = rialto_public.into_account();
-
-		let digest = rialto_runtime::millau_account_ownership_digest(
-			&call,
-			rialto_account_id,
-			millau_runtime::VERSION.spec_version,
-		);
-
-		let millau_signer = relay_millau_client::SigningParams::from_suri("//Dave", None).unwrap();
-		let signature = millau_signer.signer.sign(&digest);
-
-		assert!(signature.verify(&digest[..], &millau_signer.signer.public()));
-	}
-
-	#[test]
-	fn maximal_rialto_to_millau_message_arguments_size_is_computed_correctly() {
-		use rialto_runtime::millau_messages::Millau;
-
-		let maximal_remark_size =
-			compute_maximal_message_arguments_size(bp_rialto::max_extrinsic_size(), bp_millau::max_extrinsic_size());
-
-		let call: millau_runtime::Call = millau_runtime::SystemCall::remark(vec![42; maximal_remark_size as _]).into();
-		let payload = message_payload(
-			Default::default(),
-			call.get_dispatch_info().weight,
-			pallet_bridge_call_dispatch::CallOrigin::SourceRoot,
-			&call,
-		);
-		assert_eq!(Millau::verify_message(&payload), Ok(()));
-
-		let call: millau_runtime::Call =
-			millau_runtime::SystemCall::remark(vec![42; (maximal_remark_size + 1) as _]).into();
-		let payload = message_payload(
-			Default::default(),
-			call.get_dispatch_info().weight,
-			pallet_bridge_call_dispatch::CallOrigin::SourceRoot,
-			&call,
-		);
-		assert!(Millau::verify_message(&payload).is_err());
-	}
-
-	#[test]
-	fn maximal_size_remark_to_rialto_is_generated_correctly() {
-		assert!(
-			bridge_runtime_common::messages::target::maximal_incoming_message_size(
-				bp_rialto::max_extrinsic_size()
-			) > bp_millau::max_extrinsic_size(),
-			"We can't actually send maximal messages to Rialto from Millau, because Millau extrinsics can't be that large",
-		)
-	}
-
-	#[test]
-	fn maximal_rialto_to_millau_message_dispatch_weight_is_computed_correctly() {
-		use rialto_runtime::millau_messages::Millau;
-
-		let maximal_dispatch_weight = compute_maximal_message_dispatch_weight(bp_millau::max_extrinsic_weight());
-		let call: millau_runtime::Call = rialto_runtime::SystemCall::remark(vec![]).into();
-
-		let payload = message_payload(
-			Default::default(),
-			maximal_dispatch_weight,
-			pallet_bridge_call_dispatch::CallOrigin::SourceRoot,
-			&call,
-		);
-		assert_eq!(Millau::verify_message(&payload), Ok(()));
-
-		let payload = message_payload(
-			Default::default(),
-			maximal_dispatch_weight + 1,
-			pallet_bridge_call_dispatch::CallOrigin::SourceRoot,
-			&call,
-		);
-		assert!(Millau::verify_message(&payload).is_err());
-	}
-
-	#[test]
-	fn maximal_weight_fill_block_to_rialto_is_generated_correctly() {
-		use millau_runtime::rialto_messages::Rialto;
-
-		let maximal_dispatch_weight = compute_maximal_message_dispatch_weight(bp_rialto::max_extrinsic_weight());
-		let call: rialto_runtime::Call = millau_runtime::SystemCall::remark(vec![]).into();
-
-		let payload = message_payload(
-			Default::default(),
-			maximal_dispatch_weight,
-			pallet_bridge_call_dispatch::CallOrigin::SourceRoot,
-			&call,
-		);
-		assert_eq!(Rialto::verify_message(&payload), Ok(()));
-
-		let payload = message_payload(
-			Default::default(),
-			maximal_dispatch_weight + 1,
-			pallet_bridge_call_dispatch::CallOrigin::SourceRoot,
-			&call,
-		);
-		assert!(Rialto::verify_message(&payload).is_err());
-	}
-
-	#[test]
-	fn rialto_tx_extra_bytes_constant_is_correct() {
-		let rialto_call = rialto_runtime::Call::System(rialto_runtime::SystemCall::remark(vec![]));
-		let rialto_tx = Rialto::sign_transaction(
-			Default::default(),
-			&sp_keyring::AccountKeyring::Alice.pair(),
-			0,
-			rialto_call.clone(),
-		);
-		let extra_bytes_in_transaction = rialto_tx.encode().len() - rialto_call.encode().len();
-		assert!(
-			bp_rialto::TX_EXTRA_BYTES as usize >= extra_bytes_in_transaction,
-			"Hardcoded number of extra bytes in Rialto transaction {} is lower than actual value: {}",
-			bp_rialto::TX_EXTRA_BYTES,
-			extra_bytes_in_transaction,
-		);
-	}
-
-	#[test]
-	fn millau_tx_extra_bytes_constant_is_correct() {
-		let millau_call = millau_runtime::Call::System(millau_runtime::SystemCall::remark(vec![]));
-		let millau_tx = Millau::sign_transaction(
-			Default::default(),
-			&sp_keyring::AccountKeyring::Alice.pair(),
-			0,
-			millau_call.clone(),
-		);
-		let extra_bytes_in_transaction = millau_tx.encode().len() - millau_call.encode().len();
-		assert!(
-			bp_millau::TX_EXTRA_BYTES as usize >= extra_bytes_in_transaction,
-			"Hardcoded number of extra bytes in Millau transaction {} is lower than actual value: {}",
-			bp_millau::TX_EXTRA_BYTES,
-			extra_bytes_in_transaction,
-		);
-	}
-}
diff --git a/bridges/relays/substrate/src/rialto_millau/cli.rs b/bridges/relays/substrate/src/rialto_millau/cli.rs
new file mode 100644
index 0000000000000000000000000000000000000000..6ef5625921e32e1cd2bbbbf38337eabe3b19e0d3
--- /dev/null
+++ b/bridges/relays/substrate/src/rialto_millau/cli.rs
@@ -0,0 +1,423 @@
+// Copyright 2019-2020 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/>.
+
+//! Deal with CLI args of Rialto <> Millau relay.
+
+use frame_support::weights::Weight;
+use structopt::StructOpt;
+
+use crate::cli::{AccountId, ExplicitOrMaximal, HexBytes, HexLaneId, Origins, PrometheusParams};
+use crate::declare_chain_options;
+
+/// Start headers relayer process.
+#[derive(StructOpt)]
+pub enum RelayHeaders {
+	/// Relay Millau headers to Rialto.
+	MillauToRialto {
+		#[structopt(flatten)]
+		millau: MillauConnectionParams,
+		#[structopt(flatten)]
+		rialto: RialtoConnectionParams,
+		#[structopt(flatten)]
+		rialto_sign: RialtoSigningParams,
+		#[structopt(flatten)]
+		prometheus_params: PrometheusParams,
+	},
+	/// Relay Rialto headers to Millau.
+	RialtoToMillau {
+		#[structopt(flatten)]
+		rialto: RialtoConnectionParams,
+		#[structopt(flatten)]
+		millau: MillauConnectionParams,
+		#[structopt(flatten)]
+		millau_sign: MillauSigningParams,
+		#[structopt(flatten)]
+		prometheus_params: PrometheusParams,
+	},
+}
+
+impl RelayHeaders {
+	/// Run the command.
+	pub async fn run(self) -> anyhow::Result<()> {
+		super::run_relay_headers(self).await.map_err(format_err)?;
+		Ok(())
+	}
+}
+
+/// Start message relayer process.
+#[derive(StructOpt)]
+pub enum RelayMessages {
+	/// Serve given lane of Millau -> Rialto messages.
+	MillauToRialto {
+		#[structopt(flatten)]
+		millau: MillauConnectionParams,
+		#[structopt(flatten)]
+		millau_sign: MillauSigningParams,
+		#[structopt(flatten)]
+		rialto: RialtoConnectionParams,
+		#[structopt(flatten)]
+		rialto_sign: RialtoSigningParams,
+		#[structopt(flatten)]
+		prometheus_params: PrometheusParams,
+		/// Hex-encoded lane id that should be served by the relay. Defaults to `00000000`.
+		#[structopt(long, default_value = "00000000")]
+		lane: HexLaneId,
+	},
+	/// Serve given lane of Rialto -> Millau messages.
+	RialtoToMillau {
+		#[structopt(flatten)]
+		rialto: RialtoConnectionParams,
+		#[structopt(flatten)]
+		rialto_sign: RialtoSigningParams,
+		#[structopt(flatten)]
+		millau: MillauConnectionParams,
+		#[structopt(flatten)]
+		millau_sign: MillauSigningParams,
+		#[structopt(flatten)]
+		prometheus_params: PrometheusParams,
+		/// Hex-encoded lane id that should be served by the relay. Defaults to `00000000`.
+		#[structopt(long, default_value = "00000000")]
+		lane: HexLaneId,
+	},
+}
+
+impl RelayMessages {
+	/// Run the command.
+	pub async fn run(self) -> anyhow::Result<()> {
+		super::run_relay_messages(self).await.map_err(format_err)?;
+		Ok(())
+	}
+}
+
+/// Initialize bridge pallet.
+#[derive(StructOpt)]
+pub enum InitBridge {
+	/// Initialize Millau headers bridge in Rialto.
+	MillauToRialto {
+		#[structopt(flatten)]
+		millau: MillauConnectionParams,
+		#[structopt(flatten)]
+		rialto: RialtoConnectionParams,
+		#[structopt(flatten)]
+		rialto_sign: RialtoSigningParams,
+		#[structopt(flatten)]
+		millau_bridge_params: MillauBridgeInitializationParams,
+	},
+	/// Initialize Rialto headers bridge in Millau.
+	RialtoToMillau {
+		#[structopt(flatten)]
+		rialto: RialtoConnectionParams,
+		#[structopt(flatten)]
+		millau: MillauConnectionParams,
+		#[structopt(flatten)]
+		millau_sign: MillauSigningParams,
+		#[structopt(flatten)]
+		rialto_bridge_params: RialtoBridgeInitializationParams,
+	},
+}
+
+impl InitBridge {
+	/// Run the command.
+	pub async fn run(self) -> anyhow::Result<()> {
+		super::run_init_bridge(self).await.map_err(format_err)?;
+		Ok(())
+	}
+}
+
+/// Send bridge message.
+#[derive(StructOpt)]
+pub enum SendMessage {
+	/// Submit message to given Millau -> Rialto lane.
+	MillauToRialto {
+		#[structopt(flatten)]
+		millau: MillauConnectionParams,
+		#[structopt(flatten)]
+		millau_sign: MillauSigningParams,
+		#[structopt(flatten)]
+		rialto_sign: RialtoSigningParams,
+		/// Hex-encoded lane id. Defaults to `00000000`.
+		#[structopt(long, default_value = "00000000")]
+		lane: HexLaneId,
+		/// Dispatch weight of the message. If not passed, determined automatically.
+		#[structopt(long)]
+		dispatch_weight: Option<ExplicitOrMaximal<Weight>>,
+		/// Delivery and dispatch fee in source chain base currency units. If not passed, determined automatically.
+		#[structopt(long)]
+		fee: Option<bp_millau::Balance>,
+		/// Message type.
+		#[structopt(subcommand)]
+		message: ToRialtoMessage,
+		/// The origin to use when dispatching the message on the target chain. Defaults to
+		/// `SourceAccount`.
+		#[structopt(long, possible_values = &Origins::variants(), default_value = "Source")]
+		origin: Origins,
+	},
+	/// Submit message to given Rialto -> Millau lane.
+	RialtoToMillau {
+		#[structopt(flatten)]
+		rialto: RialtoConnectionParams,
+		#[structopt(flatten)]
+		rialto_sign: RialtoSigningParams,
+		#[structopt(flatten)]
+		millau_sign: MillauSigningParams,
+		/// Hex-encoded lane id. Defaults to `00000000`.
+		#[structopt(long, default_value = "00000000")]
+		lane: HexLaneId,
+		/// Dispatch weight of the message. If not passed, determined automatically.
+		#[structopt(long)]
+		dispatch_weight: Option<ExplicitOrMaximal<Weight>>,
+		/// Delivery and dispatch fee in source chain base currency units. If not passed, determined automatically.
+		#[structopt(long)]
+		fee: Option<bp_rialto::Balance>,
+		/// Message type.
+		#[structopt(subcommand)]
+		message: ToMillauMessage,
+		/// The origin to use when dispatching the message on the target chain. Defaults to
+		/// `SourceAccount`.
+		#[structopt(long, possible_values = &Origins::variants(), default_value = "Source")]
+		origin: Origins,
+	},
+}
+
+impl SendMessage {
+	/// Run the command.
+	pub async fn run(self) -> anyhow::Result<()> {
+		super::run_send_message(self).await.map_err(format_err)?;
+		Ok(())
+	}
+}
+
+/// A call to encode.
+#[derive(StructOpt)]
+pub enum EncodeCall {
+	/// Encode Rialto's Call.
+	Rialto {
+		#[structopt(flatten)]
+		call: ToRialtoMessage,
+	},
+	/// Encode Millau's Call.
+	Millau {
+		#[structopt(flatten)]
+		call: ToMillauMessage,
+	},
+}
+
+impl EncodeCall {
+	/// Run the command.
+	pub async fn run(self) -> anyhow::Result<()> {
+		super::run_encode_call(self).await.map_err(format_err)?;
+		Ok(())
+	}
+}
+
+/// A `MessagePayload` to encode.
+#[derive(StructOpt)]
+pub enum EncodeMessagePayload {
+	/// Message Payload of Rialto to Millau call.
+	RialtoToMillau {
+		#[structopt(flatten)]
+		payload: RialtoToMillauMessagePayload,
+	},
+	/// Message Payload of Millau to Rialto call.
+	MillauToRialto {
+		#[structopt(flatten)]
+		payload: MillauToRialtoMessagePayload,
+	},
+}
+
+impl EncodeMessagePayload {
+	/// Run the command.
+	pub async fn run(self) -> anyhow::Result<()> {
+		super::run_encode_message_payload(self).await.map_err(format_err)?;
+		Ok(())
+	}
+}
+
+/// Estimate Delivery & Dispatch Fee command.
+#[derive(StructOpt)]
+pub enum EstimateFee {
+	/// Estimate fee of Rialto to Millau message.
+	RialtoToMillau {
+		#[structopt(flatten)]
+		rialto: RialtoConnectionParams,
+		/// Hex-encoded id of lane that will be delivering the message.
+		#[structopt(long)]
+		lane: HexLaneId,
+		/// Payload to send over the bridge.
+		#[structopt(flatten)]
+		payload: RialtoToMillauMessagePayload,
+	},
+	/// Estimate fee of Rialto to Millau message.
+	MillauToRialto {
+		#[structopt(flatten)]
+		millau: MillauConnectionParams,
+		/// Hex-encoded id of lane that will be delivering the message.
+		#[structopt(long)]
+		lane: HexLaneId,
+		/// Payload to send over the bridge.
+		#[structopt(flatten)]
+		payload: MillauToRialtoMessagePayload,
+	},
+}
+
+impl EstimateFee {
+	/// Run the command.
+	pub async fn run(self) -> anyhow::Result<()> {
+		super::run_estimate_fee(self).await.map_err(format_err)?;
+		Ok(())
+	}
+}
+
+/// Given a source chain `AccountId`, derive the corresponding `AccountId` for the target chain.
+///
+/// The (derived) target chain `AccountId` is going to be used as dispatch origin of the call
+/// that has been sent over the bridge.
+/// This account can also be used to receive target-chain funds (or other form of ownership),
+/// since messages sent over the bridge will be able to spend these.
+#[derive(StructOpt)]
+pub enum DeriveAccount {
+	/// Given Rialto AccountId, display corresponding Millau AccountId.
+	RialtoToMillau { account: AccountId },
+	/// Given Millau AccountId, display corresponding Rialto AccountId.
+	MillauToRialto { account: AccountId },
+}
+
+impl DeriveAccount {
+	/// Run the command.
+	pub async fn run(self) -> anyhow::Result<()> {
+		super::run_derive_account(self).await.map_err(format_err)?;
+		Ok(())
+	}
+}
+
+fn format_err(err: String) -> anyhow::Error {
+	anyhow::anyhow!(err)
+}
+
+/// MessagePayload that can be delivered to message lane pallet on Millau.
+#[derive(StructOpt, Debug)]
+pub enum MillauToRialtoMessagePayload {
+	/// Raw, SCALE-encoded `MessagePayload`.
+	Raw {
+		/// Hex-encoded SCALE data.
+		data: HexBytes,
+	},
+	/// Construct message to send over the bridge.
+	Message {
+		/// Message details.
+		#[structopt(flatten)]
+		message: ToRialtoMessage,
+		/// SS58 encoded account that will send the payload (must have SS58Prefix = 42)
+		#[structopt(long)]
+		sender: AccountId,
+	},
+}
+
+/// MessagePayload that can be delivered to message lane pallet on Rialto.
+#[derive(StructOpt, Debug)]
+pub enum RialtoToMillauMessagePayload {
+	/// Raw, SCALE-encoded `MessagePayload`.
+	Raw {
+		/// Hex-encoded SCALE data.
+		data: HexBytes,
+	},
+	/// Construct message to send over the bridge.
+	Message {
+		/// Message details.
+		#[structopt(flatten)]
+		message: ToMillauMessage,
+		/// SS58 encoded account that will send the payload (must have SS58Prefix = 42)
+		#[structopt(long)]
+		sender: AccountId,
+	},
+}
+
+/// All possible messages that may be delivered to the Rialto chain.
+#[derive(StructOpt, Debug)]
+pub enum ToRialtoMessage {
+	/// Raw bytes for the message
+	Raw {
+		/// Raw, SCALE-encoded message
+		data: HexBytes,
+	},
+	/// Make an on-chain remark (comment).
+	Remark {
+		/// Remark size. If not passed, small UTF8-encoded string is generated by relay as remark.
+		#[structopt(long)]
+		remark_size: Option<ExplicitOrMaximal<usize>>,
+	},
+	/// Transfer the specified `amount` of native tokens to a particular `recipient`.
+	Transfer {
+		/// SS58 encoded account that will receive the transfer (must have SS58Prefix = 42)
+		#[structopt(long)]
+		recipient: AccountId,
+		/// Amount of target tokens to send in target chain base currency units.
+		#[structopt(long)]
+		amount: bp_rialto::Balance,
+	},
+	/// A call to the Millau Bridge Message Lane pallet to send a message over the bridge.
+	MillauSendMessage {
+		/// Hex-encoded lane id that should be served by the relay. Defaults to `00000000`.
+		#[structopt(long, default_value = "00000000")]
+		lane: HexLaneId,
+		/// Raw SCALE-encoded Message Payload to submit to the message lane pallet.
+		#[structopt(long)]
+		payload: HexBytes,
+		/// Declared delivery and dispatch fee in base source-chain currency units.
+		#[structopt(long)]
+		fee: bp_rialto::Balance,
+	},
+}
+
+/// All possible messages that may be delivered to the Millau chain.
+#[derive(StructOpt, Debug)]
+pub enum ToMillauMessage {
+	/// Raw bytes for the message
+	Raw {
+		/// Raw, SCALE-encoded message
+		data: HexBytes,
+	},
+	/// Make an on-chain remark (comment).
+	Remark {
+		/// Size of the remark. If not passed, small UTF8-encoded string is generated by relay as remark.
+		#[structopt(long)]
+		remark_size: Option<ExplicitOrMaximal<usize>>,
+	},
+	/// Transfer the specified `amount` of native tokens to a particular `recipient`.
+	Transfer {
+		/// SS58 encoded account that will receive the transfer (must have SS58Prefix = 42)
+		#[structopt(long)]
+		recipient: AccountId,
+		/// Amount of target tokens to send in target chain base currency units.
+		#[structopt(long)]
+		amount: bp_millau::Balance,
+	},
+	/// A call to the Rialto Bridge Message Lane pallet to send a message over the bridge.
+	RialtoSendMessage {
+		/// Hex-encoded lane id that should be served by the relay. Defaults to `00000000`.
+		#[structopt(long, default_value = "00000000")]
+		lane: HexLaneId,
+		/// Raw SCALE-encoded Message Payload to submit to the message lane pallet.
+		#[structopt(long)]
+		payload: HexBytes,
+		/// Declared delivery and dispatch fee in base source-chain currency units.
+		#[structopt(long)]
+		fee: bp_millau::Balance,
+	},
+}
+
+declare_chain_options!(Rialto, rialto);
+declare_chain_options!(Millau, millau);
diff --git a/bridges/relays/substrate/src/millau_headers_to_rialto.rs b/bridges/relays/substrate/src/rialto_millau/millau_headers_to_rialto.rs
similarity index 95%
rename from bridges/relays/substrate/src/millau_headers_to_rialto.rs
rename to bridges/relays/substrate/src/rialto_millau/millau_headers_to_rialto.rs
index f84eee03a9fd080a4ccb5603dd300c3ae90d6f41..de03fead20144d2a5a7b0987d825eb0e15cf2383 100644
--- a/bridges/relays/substrate/src/millau_headers_to_rialto.rs
+++ b/bridges/relays/substrate/src/rialto_millau/millau_headers_to_rialto.rs
@@ -16,10 +16,8 @@
 
 //! Millau-to-Rialto headers sync entrypoint.
 
-use crate::{
-	finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate},
-	MillauClient, RialtoClient,
-};
+use super::{MillauClient, RialtoClient};
+use crate::finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate};
 
 use async_trait::async_trait;
 use relay_millau_client::{Millau, SyncHeader as MillauSyncHeader};
diff --git a/bridges/relays/substrate/src/millau_messages_to_rialto.rs b/bridges/relays/substrate/src/rialto_millau/millau_messages_to_rialto.rs
similarity index 99%
rename from bridges/relays/substrate/src/millau_messages_to_rialto.rs
rename to bridges/relays/substrate/src/rialto_millau/millau_messages_to_rialto.rs
index f3dfdadf9a38de82f8dedd26fdd18bf4260c9cdc..84664e4572bba02b9b30b7b499c7a4fce0d7a18b 100644
--- a/bridges/relays/substrate/src/millau_messages_to_rialto.rs
+++ b/bridges/relays/substrate/src/rialto_millau/millau_messages_to_rialto.rs
@@ -16,10 +16,10 @@
 
 //! Millau-to-Rialto messages sync entrypoint.
 
+use super::{MillauClient, RialtoClient};
 use crate::messages_lane::{select_delivery_transaction_limits, SubstrateMessageLane, SubstrateMessageLaneToSubstrate};
 use crate::messages_source::SubstrateMessagesSource;
 use crate::messages_target::SubstrateMessagesTarget;
-use crate::{MillauClient, RialtoClient};
 
 use async_trait::async_trait;
 use bp_message_lane::{LaneId, MessageNonce};
diff --git a/bridges/relays/substrate/src/rialto_millau/mod.rs b/bridges/relays/substrate/src/rialto_millau/mod.rs
new file mode 100644
index 0000000000000000000000000000000000000000..756a0349745651a5c792b8821c2f65090c358ea7
--- /dev/null
+++ b/bridges/relays/substrate/src/rialto_millau/mod.rs
@@ -0,0 +1,922 @@
+// Copyright 2019-2020 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/>.
+
+//! Rialto <> Millau Bridge commands.
+
+pub mod cli;
+pub mod millau_headers_to_rialto;
+pub mod millau_messages_to_rialto;
+pub mod rialto_headers_to_millau;
+pub mod rialto_messages_to_millau;
+
+/// Millau node client.
+pub type MillauClient = relay_substrate_client::Client<Millau>;
+/// Rialto node client.
+pub type RialtoClient = relay_substrate_client::Client<Rialto>;
+
+use crate::cli::{ExplicitOrMaximal, HexBytes, Origins};
+use codec::{Decode, Encode};
+use frame_support::weights::{GetDispatchInfo, Weight};
+use pallet_bridge_call_dispatch::{CallOrigin, MessagePayload};
+use relay_millau_client::{Millau, SigningParams as MillauSigningParams};
+use relay_rialto_client::{Rialto, SigningParams as RialtoSigningParams};
+use relay_substrate_client::{Chain, ConnectionParams, TransactionSignScheme};
+use sp_core::{Bytes, Pair};
+use sp_runtime::traits::IdentifyAccount;
+use std::fmt::Debug;
+
+async fn run_init_bridge(command: cli::InitBridge) -> Result<(), String> {
+	match command {
+		cli::InitBridge::MillauToRialto {
+			millau,
+			rialto,
+			rialto_sign,
+			millau_bridge_params,
+		} => {
+			let millau_client = millau.into_client().await?;
+			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(),
+				millau_bridge_params.millau_initial_header,
+				millau_bridge_params.millau_initial_authorities,
+				millau_bridge_params.millau_initial_authorities_set_id,
+				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::FinalityBridgeMillauCall::initialize(initialization_data).into(),
+							))
+							.into(),
+						)
+						.encode(),
+					))
+				},
+			)
+			.await;
+		}
+		cli::InitBridge::RialtoToMillau {
+			rialto,
+			millau,
+			millau_sign,
+			rialto_bridge_params,
+		} => {
+			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(),
+				rialto_bridge_params.rialto_initial_header,
+				rialto_bridge_params.rialto_initial_authorities,
+				rialto_bridge_params.rialto_initial_authorities_set_id,
+				move |initialization_data| {
+					Ok(Bytes(
+						Millau::sign_transaction(
+							*millau_client.genesis_hash(),
+							&millau_sign.signer,
+							millau_signer_next_index,
+							millau_runtime::SudoCall::sudo(Box::new(
+								millau_runtime::FinalityBridgeRialtoCall::initialize(initialization_data).into(),
+							))
+							.into(),
+						)
+						.encode(),
+					))
+				},
+			)
+			.await;
+		}
+	}
+	Ok(())
+}
+
+async fn run_relay_headers(command: cli::RelayHeaders) -> Result<(), String> {
+	match command {
+		cli::RelayHeaders::MillauToRialto {
+			millau,
+			rialto,
+			rialto_sign,
+			prometheus_params,
+		} => {
+			let millau_client = millau.into_client().await?;
+			let rialto_client = rialto.into_client().await?;
+			let rialto_sign = rialto_sign.parse()?;
+			millau_headers_to_rialto::run(millau_client, rialto_client, rialto_sign, prometheus_params.into()).await;
+		}
+		cli::RelayHeaders::RialtoToMillau {
+			rialto,
+			millau,
+			millau_sign,
+			prometheus_params,
+		} => {
+			let rialto_client = rialto.into_client().await?;
+			let millau_client = millau.into_client().await?;
+			let millau_sign = millau_sign.parse()?;
+			rialto_headers_to_millau::run(rialto_client, millau_client, millau_sign, prometheus_params.into()).await;
+		}
+	}
+	Ok(())
+}
+
+async fn run_relay_messages(command: cli::RelayMessages) -> Result<(), String> {
+	match command {
+		cli::RelayMessages::MillauToRialto {
+			millau,
+			millau_sign,
+			rialto,
+			rialto_sign,
+			prometheus_params,
+			lane,
+		} => {
+			let millau_client = millau.into_client().await?;
+			let millau_sign = millau_sign.parse()?;
+			let rialto_client = rialto.into_client().await?;
+			let rialto_sign = rialto_sign.parse()?;
+
+			millau_messages_to_rialto::run(
+				millau_client,
+				millau_sign,
+				rialto_client,
+				rialto_sign,
+				lane.into(),
+				prometheus_params.into(),
+			);
+		}
+		cli::RelayMessages::RialtoToMillau {
+			rialto,
+			rialto_sign,
+			millau,
+			millau_sign,
+			prometheus_params,
+			lane,
+		} => {
+			let rialto_client = rialto.into_client().await?;
+			let rialto_sign = rialto_sign.parse()?;
+			let millau_client = millau.into_client().await?;
+			let millau_sign = millau_sign.parse()?;
+
+			rialto_messages_to_millau::run(
+				rialto_client,
+				rialto_sign,
+				millau_client,
+				millau_sign,
+				lane.into(),
+				prometheus_params.into(),
+			);
+		}
+	}
+	Ok(())
+}
+
+async fn run_send_message(command: cli::SendMessage) -> Result<(), String> {
+	match command {
+		cli::SendMessage::MillauToRialto {
+			millau,
+			millau_sign,
+			rialto_sign,
+			lane,
+			message,
+			dispatch_weight,
+			fee,
+			origin,
+			..
+		} => {
+			let millau_client = millau.into_client().await?;
+			let millau_sign = millau_sign.parse()?;
+			let rialto_sign = rialto_sign.parse()?;
+			let rialto_call = message.into_call()?;
+
+			let payload =
+				millau_to_rialto_message_payload(&millau_sign, &rialto_sign, &rialto_call, origin, dispatch_weight);
+			let dispatch_weight = payload.weight;
+
+			let lane = lane.into();
+			let fee = get_fee(fee, || {
+				estimate_message_delivery_and_dispatch_fee(
+					&millau_client,
+					bp_rialto::TO_RIALTO_ESTIMATE_MESSAGE_FEE_METHOD,
+					lane,
+					payload.clone(),
+				)
+			})
+			.await?;
+
+			let millau_call = millau_runtime::Call::BridgeRialtoMessageLane(
+				millau_runtime::MessageLaneCall::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?;
+		}
+		cli::SendMessage::RialtoToMillau {
+			rialto,
+			rialto_sign,
+			millau_sign,
+			lane,
+			message,
+			dispatch_weight,
+			fee,
+			origin,
+			..
+		} => {
+			let rialto_client = rialto.into_client().await?;
+			let rialto_sign = rialto_sign.parse()?;
+			let millau_sign = millau_sign.parse()?;
+			let millau_call = message.into_call()?;
+
+			let payload =
+				rialto_to_millau_message_payload(&rialto_sign, &millau_sign, &millau_call, origin, dispatch_weight);
+			let dispatch_weight = payload.weight;
+
+			let lane = lane.into();
+			let fee = get_fee(fee, || {
+				estimate_message_delivery_and_dispatch_fee(
+					&rialto_client,
+					bp_millau::TO_MILLAU_ESTIMATE_MESSAGE_FEE_METHOD,
+					lane,
+					payload.clone(),
+				)
+			})
+			.await?;
+
+			let rialto_call = rialto_runtime::Call::BridgeMillauMessageLane(
+				rialto_runtime::MessageLaneCall::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?;
+		}
+	}
+	Ok(())
+}
+
+async fn run_encode_call(call: cli::EncodeCall) -> Result<(), String> {
+	match call {
+		cli::EncodeCall::Rialto { call } => {
+			let call = call.into_call()?;
+
+			println!("{:?}", HexBytes::encode(&call));
+		}
+		cli::EncodeCall::Millau { call } => {
+			let call = call.into_call()?;
+			println!("{:?}", HexBytes::encode(&call));
+		}
+	}
+	Ok(())
+}
+
+async fn run_encode_message_payload(call: cli::EncodeMessagePayload) -> Result<(), String> {
+	match call {
+		cli::EncodeMessagePayload::RialtoToMillau { payload } => {
+			let payload = payload.into_payload()?;
+
+			println!("{:?}", HexBytes::encode(&payload));
+		}
+		cli::EncodeMessagePayload::MillauToRialto { payload } => {
+			let payload = payload.into_payload()?;
+
+			println!("{:?}", HexBytes::encode(&payload));
+		}
+	}
+	Ok(())
+}
+
+async fn run_estimate_fee(cmd: cli::EstimateFee) -> Result<(), String> {
+	match cmd {
+		cli::EstimateFee::RialtoToMillau { rialto, lane, payload } => {
+			let client = rialto.into_client().await?;
+			let lane = lane.into();
+			let payload = payload.into_payload()?;
+
+			let fee: Option<bp_rialto::Balance> = estimate_message_delivery_and_dispatch_fee(
+				&client,
+				bp_millau::TO_MILLAU_ESTIMATE_MESSAGE_FEE_METHOD,
+				lane,
+				payload,
+			)
+			.await?;
+
+			println!("Fee: {:?}", fee);
+		}
+		cli::EstimateFee::MillauToRialto { millau, lane, payload } => {
+			let client = millau.into_client().await?;
+			let lane = lane.into();
+			let payload = payload.into_payload()?;
+
+			let fee: Option<bp_millau::Balance> = estimate_message_delivery_and_dispatch_fee(
+				&client,
+				bp_rialto::TO_RIALTO_ESTIMATE_MESSAGE_FEE_METHOD,
+				lane,
+				payload,
+			)
+			.await?;
+
+			println!("Fee: {:?}", fee);
+		}
+	}
+
+	Ok(())
+}
+
+async fn run_derive_account(cmd: cli::DeriveAccount) -> Result<(), String> {
+	match cmd {
+		cli::DeriveAccount::RialtoToMillau { account } => {
+			let account = account.into_rialto();
+			let acc = bp_runtime::SourceAccount::Account(account.clone());
+			let id = bp_millau::derive_account_from_rialto_id(acc);
+			println!(
+				"{} (Rialto)\n\nCorresponding (derived) account id:\n-> {} (Millau)",
+				account, id
+			)
+		}
+		cli::DeriveAccount::MillauToRialto { account } => {
+			let account = account.into_millau();
+			let acc = bp_runtime::SourceAccount::Account(account.clone());
+			let id = bp_rialto::derive_account_from_millau_id(acc);
+			println!(
+				"{} (Millau)\n\nCorresponding (derived) account id:\n-> {} (Rialto)",
+				account, id
+			)
+		}
+	}
+
+	Ok(())
+}
+
+async fn estimate_message_delivery_and_dispatch_fee<Fee: Decode, C: Chain, P: Encode>(
+	client: &relay_substrate_client::Client<C>,
+	estimate_fee_method: &str,
+	lane: bp_message_lane::LaneId,
+	payload: P,
+) -> Result<Option<Fee>, relay_substrate_client::Error> {
+	let encoded_response = client
+		.state_call(estimate_fee_method.into(), (lane, payload).encode().into(), None)
+		.await?;
+	let decoded_response: Option<Fee> =
+		Decode::decode(&mut &encoded_response.0[..]).map_err(relay_substrate_client::Error::ResponseParseFailed)?;
+	Ok(decoded_response)
+}
+
+fn remark_payload(remark_size: Option<ExplicitOrMaximal<usize>>, maximal_allowed_size: u32) -> Vec<u8> {
+	match remark_size {
+		Some(ExplicitOrMaximal::Explicit(remark_size)) => vec![0; remark_size],
+		Some(ExplicitOrMaximal::Maximal) => vec![0; maximal_allowed_size as _],
+		None => format!(
+			"Unix time: {}",
+			std::time::SystemTime::now()
+				.duration_since(std::time::SystemTime::UNIX_EPOCH)
+				.unwrap_or_default()
+				.as_secs(),
+		)
+		.as_bytes()
+		.to_vec(),
+	}
+}
+
+fn message_payload<SAccountId, TPublic, TSignature>(
+	spec_version: u32,
+	weight: Weight,
+	origin: CallOrigin<SAccountId, TPublic, TSignature>,
+	call: &impl Encode,
+) -> MessagePayload<SAccountId, TPublic, TSignature, Vec<u8>>
+where
+	SAccountId: Encode + Debug,
+	TPublic: Encode + Debug,
+	TSignature: Encode + Debug,
+{
+	// Display nicely formatted call.
+	let payload = MessagePayload {
+		spec_version,
+		weight,
+		origin,
+		call: HexBytes::encode(call),
+	};
+
+	log::info!(target: "bridge", "Created Message Payload: {:#?}", payload);
+	log::info!(target: "bridge", "Encoded Message Payload: {:?}", HexBytes::encode(&payload));
+
+	// re-pack to return `Vec<u8>`
+	let MessagePayload {
+		spec_version,
+		weight,
+		origin,
+		call,
+	} = payload;
+	MessagePayload {
+		spec_version,
+		weight,
+		origin,
+		call: call.0,
+	}
+}
+
+fn rialto_to_millau_message_payload(
+	rialto_sign: &RialtoSigningParams,
+	millau_sign: &MillauSigningParams,
+	millau_call: &millau_runtime::Call,
+	origin: Origins,
+	user_specified_dispatch_weight: Option<ExplicitOrMaximal<Weight>>,
+) -> rialto_runtime::millau_messages::ToMillauMessagePayload {
+	let millau_call_weight = prepare_call_dispatch_weight(
+		user_specified_dispatch_weight,
+		ExplicitOrMaximal::Explicit(millau_call.get_dispatch_info().weight),
+		compute_maximal_message_dispatch_weight(bp_millau::max_extrinsic_weight()),
+	);
+	let rialto_sender_public: bp_rialto::AccountSigner = rialto_sign.signer.public().clone().into();
+	let rialto_account_id: bp_rialto::AccountId = rialto_sender_public.into_account();
+	let millau_origin_public = millau_sign.signer.public();
+
+	message_payload(
+		millau_runtime::VERSION.spec_version,
+		millau_call_weight,
+		match origin {
+			Origins::Source => CallOrigin::SourceAccount(rialto_account_id),
+			Origins::Target => {
+				let digest = rialto_runtime::millau_account_ownership_digest(
+					&millau_call,
+					rialto_account_id.clone(),
+					millau_runtime::VERSION.spec_version,
+				);
+
+				let digest_signature = millau_sign.signer.sign(&digest);
+
+				CallOrigin::TargetAccount(rialto_account_id, millau_origin_public.into(), digest_signature.into())
+			}
+		},
+		&millau_call,
+	)
+}
+
+fn millau_to_rialto_message_payload(
+	millau_sign: &MillauSigningParams,
+	rialto_sign: &RialtoSigningParams,
+	rialto_call: &rialto_runtime::Call,
+	origin: Origins,
+	user_specified_dispatch_weight: Option<ExplicitOrMaximal<Weight>>,
+) -> millau_runtime::rialto_messages::ToRialtoMessagePayload {
+	let rialto_call_weight = prepare_call_dispatch_weight(
+		user_specified_dispatch_weight,
+		ExplicitOrMaximal::Explicit(rialto_call.get_dispatch_info().weight),
+		compute_maximal_message_dispatch_weight(bp_rialto::max_extrinsic_weight()),
+	);
+	let millau_sender_public: bp_millau::AccountSigner = millau_sign.signer.public().clone().into();
+	let millau_account_id: bp_millau::AccountId = millau_sender_public.into_account();
+	let rialto_origin_public = rialto_sign.signer.public();
+
+	message_payload(
+		rialto_runtime::VERSION.spec_version,
+		rialto_call_weight,
+		match origin {
+			Origins::Source => CallOrigin::SourceAccount(millau_account_id),
+			Origins::Target => {
+				let digest = millau_runtime::rialto_account_ownership_digest(
+					&rialto_call,
+					millau_account_id.clone(),
+					rialto_runtime::VERSION.spec_version,
+				);
+
+				let digest_signature = rialto_sign.signer.sign(&digest);
+
+				CallOrigin::TargetAccount(millau_account_id, rialto_origin_public.into(), digest_signature.into())
+			}
+		},
+		&rialto_call,
+	)
+}
+
+fn prepare_call_dispatch_weight(
+	user_specified_dispatch_weight: Option<ExplicitOrMaximal<Weight>>,
+	weight_from_pre_dispatch_call: ExplicitOrMaximal<Weight>,
+	maximal_allowed_weight: Weight,
+) -> Weight {
+	match user_specified_dispatch_weight.unwrap_or(weight_from_pre_dispatch_call) {
+		ExplicitOrMaximal::Explicit(weight) => weight,
+		ExplicitOrMaximal::Maximal => maximal_allowed_weight,
+	}
+}
+
+async fn get_fee<Fee, F, R, E>(fee: Option<Fee>, f: F) -> Result<Fee, String>
+where
+	Fee: Decode,
+	F: FnOnce() -> R,
+	R: std::future::Future<Output = Result<Option<Fee>, E>>,
+	E: Debug,
+{
+	match fee {
+		Some(fee) => Ok(fee),
+		None => match f().await {
+			Ok(Some(fee)) => Ok(fee),
+			Ok(None) => Err("Failed to estimate message fee. Message is too heavy?".into()),
+			Err(error) => Err(format!("Failed to estimate message fee: {:?}", error)),
+		},
+	}
+}
+
+fn compute_maximal_message_dispatch_weight(maximal_extrinsic_weight: Weight) -> Weight {
+	bridge_runtime_common::messages::target::maximal_incoming_message_dispatch_weight(maximal_extrinsic_weight)
+}
+
+fn compute_maximal_message_arguments_size(
+	maximal_source_extrinsic_size: u32,
+	maximal_target_extrinsic_size: u32,
+) -> u32 {
+	// assume that both signed extensions and other arguments fit 1KB
+	let service_tx_bytes_on_source_chain = 1024;
+	let maximal_source_extrinsic_size = maximal_source_extrinsic_size - service_tx_bytes_on_source_chain;
+	let maximal_call_size =
+		bridge_runtime_common::messages::target::maximal_incoming_message_size(maximal_target_extrinsic_size);
+	let maximal_call_size = if maximal_call_size > maximal_source_extrinsic_size {
+		maximal_source_extrinsic_size
+	} else {
+		maximal_call_size
+	};
+
+	// bytes in Call encoding that are used to encode everything except arguments
+	let service_bytes = 1 + 1 + 4;
+	maximal_call_size - service_bytes
+}
+
+impl cli::MillauToRialtoMessagePayload {
+	/// Parse the CLI parameters and construct message payload.
+	pub fn into_payload(
+		self,
+	) -> Result<MessagePayload<bp_rialto::AccountId, bp_rialto::AccountSigner, bp_rialto::Signature, Vec<u8>>, String> {
+		match self {
+			Self::Raw { data } => MessagePayload::decode(&mut &*data.0)
+				.map_err(|e| format!("Failed to decode Millau's MessagePayload: {:?}", e)),
+			Self::Message { message, sender } => {
+				let spec_version = rialto_runtime::VERSION.spec_version;
+				let origin = CallOrigin::SourceAccount(sender.into_millau());
+				let call = message.into_call()?;
+				let weight = call.get_dispatch_info().weight;
+
+				Ok(message_payload(spec_version, weight, origin, &call))
+			}
+		}
+	}
+}
+
+impl cli::RialtoToMillauMessagePayload {
+	/// Parse the CLI parameters and construct message payload.
+	pub fn into_payload(
+		self,
+	) -> Result<MessagePayload<bp_millau::AccountId, bp_millau::AccountSigner, bp_millau::Signature, Vec<u8>>, String> {
+		match self {
+			Self::Raw { data } => MessagePayload::decode(&mut &*data.0)
+				.map_err(|e| format!("Failed to decode Rialto's MessagePayload: {:?}", e)),
+			Self::Message { message, sender } => {
+				let spec_version = millau_runtime::VERSION.spec_version;
+				let origin = CallOrigin::SourceAccount(sender.into_rialto());
+				let call = message.into_call()?;
+				let weight = call.get_dispatch_info().weight;
+
+				Ok(message_payload(spec_version, weight, origin, &call))
+			}
+		}
+	}
+}
+
+impl cli::RialtoSigningParams {
+	/// Parse CLI parameters into typed signing params.
+	pub fn parse(self) -> Result<RialtoSigningParams, String> {
+		RialtoSigningParams::from_suri(&self.rialto_signer, self.rialto_signer_password.as_deref())
+			.map_err(|e| format!("Failed to parse rialto-signer: {:?}", e))
+	}
+}
+
+impl cli::MillauSigningParams {
+	/// Parse CLI parameters into typed signing params.
+	pub fn parse(self) -> Result<MillauSigningParams, String> {
+		MillauSigningParams::from_suri(&self.millau_signer, self.millau_signer_password.as_deref())
+			.map_err(|e| format!("Failed to parse millau-signer: {:?}", e))
+	}
+}
+
+impl cli::MillauConnectionParams {
+	/// Convert CLI connection parameters into Millau RPC Client.
+	pub async fn into_client(self) -> relay_substrate_client::Result<MillauClient> {
+		MillauClient::new(ConnectionParams {
+			host: self.millau_host,
+			port: self.millau_port,
+		})
+		.await
+	}
+}
+impl cli::RialtoConnectionParams {
+	/// Convert CLI connection parameters into Rialto RPC Client.
+	pub async fn into_client(self) -> relay_substrate_client::Result<RialtoClient> {
+		RialtoClient::new(ConnectionParams {
+			host: self.rialto_host,
+			port: self.rialto_port,
+		})
+		.await
+	}
+}
+
+impl cli::ToRialtoMessage {
+	/// Convert CLI call request into runtime `Call` instance.
+	pub fn into_call(self) -> Result<rialto_runtime::Call, String> {
+		let call = match self {
+			cli::ToRialtoMessage::Raw { data } => {
+				Decode::decode(&mut &*data.0).map_err(|e| format!("Unable to decode message: {:#?}", e))?
+			}
+			cli::ToRialtoMessage::Remark { remark_size } => {
+				rialto_runtime::Call::System(rialto_runtime::SystemCall::remark(remark_payload(
+					remark_size,
+					compute_maximal_message_arguments_size(
+						bp_millau::max_extrinsic_size(),
+						bp_rialto::max_extrinsic_size(),
+					),
+				)))
+			}
+			cli::ToRialtoMessage::Transfer { recipient, amount } => {
+				let recipient = recipient.into_rialto();
+				rialto_runtime::Call::Balances(rialto_runtime::BalancesCall::transfer(recipient, amount))
+			}
+			cli::ToRialtoMessage::MillauSendMessage { lane, payload, fee } => {
+				let payload = cli::RialtoToMillauMessagePayload::Raw { data: payload }.into_payload()?;
+				let lane = lane.into();
+				rialto_runtime::Call::BridgeMillauMessageLane(rialto_runtime::MessageLaneCall::send_message(
+					lane, payload, fee,
+				))
+			}
+		};
+
+		log::info!(target: "bridge", "Generated Rialto call: {:#?}", call);
+		log::info!(target: "bridge", "Weight of Rialto call: {}", call.get_dispatch_info().weight);
+		log::info!(target: "bridge", "Encoded Rialto call: {:?}", HexBytes::encode(&call));
+
+		Ok(call)
+	}
+}
+
+impl cli::ToMillauMessage {
+	/// Convert CLI call request into runtime `Call` instance.
+	pub fn into_call(self) -> Result<millau_runtime::Call, String> {
+		let call = match self {
+			cli::ToMillauMessage::Raw { data } => {
+				Decode::decode(&mut &*data.0).map_err(|e| format!("Unable to decode message: {:#?}", e))?
+			}
+			cli::ToMillauMessage::Remark { remark_size } => {
+				millau_runtime::Call::System(millau_runtime::SystemCall::remark(remark_payload(
+					remark_size,
+					compute_maximal_message_arguments_size(
+						bp_rialto::max_extrinsic_size(),
+						bp_millau::max_extrinsic_size(),
+					),
+				)))
+			}
+			cli::ToMillauMessage::Transfer { recipient, amount } => {
+				let recipient = recipient.into_millau();
+				millau_runtime::Call::Balances(millau_runtime::BalancesCall::transfer(recipient, amount))
+			}
+			cli::ToMillauMessage::RialtoSendMessage { lane, payload, fee } => {
+				let payload = cli::MillauToRialtoMessagePayload::Raw { data: payload }.into_payload()?;
+				let lane = lane.into();
+				millau_runtime::Call::BridgeRialtoMessageLane(millau_runtime::MessageLaneCall::send_message(
+					lane, payload, fee,
+				))
+			}
+		};
+
+		log::info!(target: "bridge", "Generated Millau call: {:#?}", call);
+		log::info!(target: "bridge", "Weight of Millau call: {}", call.get_dispatch_info().weight);
+		log::info!(target: "bridge", "Encoded Millau call: {:?}", HexBytes::encode(&call));
+
+		Ok(call)
+	}
+}
+
+#[cfg(test)]
+mod tests {
+	use super::*;
+	use bp_message_lane::source_chain::TargetHeaderChain;
+	use sp_core::Pair;
+	use sp_runtime::traits::{IdentifyAccount, Verify};
+
+	#[test]
+	fn millau_signature_is_valid_on_rialto() {
+		let millau_sign = relay_millau_client::SigningParams::from_suri("//Dave", None).unwrap();
+
+		let call = rialto_runtime::Call::System(rialto_runtime::SystemCall::remark(vec![]));
+
+		let millau_public: bp_millau::AccountSigner = millau_sign.signer.public().clone().into();
+		let millau_account_id: bp_millau::AccountId = millau_public.into_account();
+
+		let digest = millau_runtime::rialto_account_ownership_digest(
+			&call,
+			millau_account_id,
+			rialto_runtime::VERSION.spec_version,
+		);
+
+		let rialto_signer = relay_rialto_client::SigningParams::from_suri("//Dave", None).unwrap();
+		let signature = rialto_signer.signer.sign(&digest);
+
+		assert!(signature.verify(&digest[..], &rialto_signer.signer.public()));
+	}
+
+	#[test]
+	fn rialto_signature_is_valid_on_millau() {
+		let rialto_sign = relay_rialto_client::SigningParams::from_suri("//Dave", None).unwrap();
+
+		let call = millau_runtime::Call::System(millau_runtime::SystemCall::remark(vec![]));
+
+		let rialto_public: bp_rialto::AccountSigner = rialto_sign.signer.public().clone().into();
+		let rialto_account_id: bp_rialto::AccountId = rialto_public.into_account();
+
+		let digest = rialto_runtime::millau_account_ownership_digest(
+			&call,
+			rialto_account_id,
+			millau_runtime::VERSION.spec_version,
+		);
+
+		let millau_signer = relay_millau_client::SigningParams::from_suri("//Dave", None).unwrap();
+		let signature = millau_signer.signer.sign(&digest);
+
+		assert!(signature.verify(&digest[..], &millau_signer.signer.public()));
+	}
+
+	#[test]
+	fn maximal_rialto_to_millau_message_arguments_size_is_computed_correctly() {
+		use rialto_runtime::millau_messages::Millau;
+
+		let maximal_remark_size =
+			compute_maximal_message_arguments_size(bp_rialto::max_extrinsic_size(), bp_millau::max_extrinsic_size());
+
+		let call: millau_runtime::Call = millau_runtime::SystemCall::remark(vec![42; maximal_remark_size as _]).into();
+		let payload = message_payload(
+			Default::default(),
+			call.get_dispatch_info().weight,
+			pallet_bridge_call_dispatch::CallOrigin::SourceRoot,
+			&call,
+		);
+		assert_eq!(Millau::verify_message(&payload), Ok(()));
+
+		let call: millau_runtime::Call =
+			millau_runtime::SystemCall::remark(vec![42; (maximal_remark_size + 1) as _]).into();
+		let payload = message_payload(
+			Default::default(),
+			call.get_dispatch_info().weight,
+			pallet_bridge_call_dispatch::CallOrigin::SourceRoot,
+			&call,
+		);
+		assert!(Millau::verify_message(&payload).is_err());
+	}
+
+	#[test]
+	fn maximal_size_remark_to_rialto_is_generated_correctly() {
+		assert!(
+			bridge_runtime_common::messages::target::maximal_incoming_message_size(
+				bp_rialto::max_extrinsic_size()
+			) > bp_millau::max_extrinsic_size(),
+			"We can't actually send maximal messages to Rialto from Millau, because Millau extrinsics can't be that large",
+		)
+	}
+
+	#[test]
+	fn maximal_rialto_to_millau_message_dispatch_weight_is_computed_correctly() {
+		use rialto_runtime::millau_messages::Millau;
+
+		let maximal_dispatch_weight = compute_maximal_message_dispatch_weight(bp_millau::max_extrinsic_weight());
+		let call: millau_runtime::Call = rialto_runtime::SystemCall::remark(vec![]).into();
+
+		let payload = message_payload(
+			Default::default(),
+			maximal_dispatch_weight,
+			pallet_bridge_call_dispatch::CallOrigin::SourceRoot,
+			&call,
+		);
+		assert_eq!(Millau::verify_message(&payload), Ok(()));
+
+		let payload = message_payload(
+			Default::default(),
+			maximal_dispatch_weight + 1,
+			pallet_bridge_call_dispatch::CallOrigin::SourceRoot,
+			&call,
+		);
+		assert!(Millau::verify_message(&payload).is_err());
+	}
+
+	#[test]
+	fn maximal_weight_fill_block_to_rialto_is_generated_correctly() {
+		use millau_runtime::rialto_messages::Rialto;
+
+		let maximal_dispatch_weight = compute_maximal_message_dispatch_weight(bp_rialto::max_extrinsic_weight());
+		let call: rialto_runtime::Call = millau_runtime::SystemCall::remark(vec![]).into();
+
+		let payload = message_payload(
+			Default::default(),
+			maximal_dispatch_weight,
+			pallet_bridge_call_dispatch::CallOrigin::SourceRoot,
+			&call,
+		);
+		assert_eq!(Rialto::verify_message(&payload), Ok(()));
+
+		let payload = message_payload(
+			Default::default(),
+			maximal_dispatch_weight + 1,
+			pallet_bridge_call_dispatch::CallOrigin::SourceRoot,
+			&call,
+		);
+		assert!(Rialto::verify_message(&payload).is_err());
+	}
+
+	#[test]
+	fn rialto_tx_extra_bytes_constant_is_correct() {
+		let rialto_call = rialto_runtime::Call::System(rialto_runtime::SystemCall::remark(vec![]));
+		let rialto_tx = Rialto::sign_transaction(
+			Default::default(),
+			&sp_keyring::AccountKeyring::Alice.pair(),
+			0,
+			rialto_call.clone(),
+		);
+		let extra_bytes_in_transaction = rialto_tx.encode().len() - rialto_call.encode().len();
+		assert!(
+			bp_rialto::TX_EXTRA_BYTES as usize >= extra_bytes_in_transaction,
+			"Hardcoded number of extra bytes in Rialto transaction {} is lower than actual value: {}",
+			bp_rialto::TX_EXTRA_BYTES,
+			extra_bytes_in_transaction,
+		);
+	}
+
+	#[test]
+	fn millau_tx_extra_bytes_constant_is_correct() {
+		let millau_call = millau_runtime::Call::System(millau_runtime::SystemCall::remark(vec![]));
+		let millau_tx = Millau::sign_transaction(
+			Default::default(),
+			&sp_keyring::AccountKeyring::Alice.pair(),
+			0,
+			millau_call.clone(),
+		);
+		let extra_bytes_in_transaction = millau_tx.encode().len() - millau_call.encode().len();
+		assert!(
+			bp_millau::TX_EXTRA_BYTES as usize >= extra_bytes_in_transaction,
+			"Hardcoded number of extra bytes in Millau transaction {} is lower than actual value: {}",
+			bp_millau::TX_EXTRA_BYTES,
+			extra_bytes_in_transaction,
+		);
+	}
+}
diff --git a/bridges/relays/substrate/src/rialto_headers_to_millau.rs b/bridges/relays/substrate/src/rialto_millau/rialto_headers_to_millau.rs
similarity index 95%
rename from bridges/relays/substrate/src/rialto_headers_to_millau.rs
rename to bridges/relays/substrate/src/rialto_millau/rialto_headers_to_millau.rs
index 5a9bbb12133a21025a21b96a60ff2b48b1bc091c..7e9b855afd18330e16df6a5786046f3abbed35a4 100644
--- a/bridges/relays/substrate/src/rialto_headers_to_millau.rs
+++ b/bridges/relays/substrate/src/rialto_millau/rialto_headers_to_millau.rs
@@ -16,10 +16,8 @@
 
 //! Rialto-to-Millau headers sync entrypoint.
 
-use crate::{
-	finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate},
-	MillauClient, RialtoClient,
-};
+use super::{MillauClient, RialtoClient};
+use crate::finality_pipeline::{SubstrateFinalitySyncPipeline, SubstrateFinalityToSubstrate};
 
 use async_trait::async_trait;
 use relay_millau_client::{Millau, SigningParams as MillauSigningParams};
diff --git a/bridges/relays/substrate/src/rialto_messages_to_millau.rs b/bridges/relays/substrate/src/rialto_millau/rialto_messages_to_millau.rs
similarity index 99%
rename from bridges/relays/substrate/src/rialto_messages_to_millau.rs
rename to bridges/relays/substrate/src/rialto_millau/rialto_messages_to_millau.rs
index 96211bd944f64d50fc96d4c55ebf12434d3b4ca4..3083ce3cbf5d479718a53935417938b8d36b3a89 100644
--- a/bridges/relays/substrate/src/rialto_messages_to_millau.rs
+++ b/bridges/relays/substrate/src/rialto_millau/rialto_messages_to_millau.rs
@@ -16,10 +16,10 @@
 
 //! Rialto-to-Millau messages sync entrypoint.
 
+use super::{MillauClient, RialtoClient};
 use crate::messages_lane::{select_delivery_transaction_limits, SubstrateMessageLane, SubstrateMessageLaneToSubstrate};
 use crate::messages_source::SubstrateMessagesSource;
 use crate::messages_target::SubstrateMessagesTarget;
-use crate::{MillauClient, RialtoClient};
 
 use async_trait::async_trait;
 use bp_message_lane::{LaneId, MessageNonce};