From efdc1e9b1615c5502ed63ffc9683d99af6397263 Mon Sep 17 00:00:00 2001
From: Ron <yrong1997@gmail.com>
Date: Wed, 7 Aug 2024 17:49:21 +0800
Subject: [PATCH] Snowbridge on Westend (#5074)

### Context

Since Rococo is now deprecated, we need another testnet to detect
bleeding-edge changes to Substrate, Polkadot, & BEEFY consensus
protocols that could brick the bridge.

It's the mirror PR of https://github.com/Snowfork/polkadot-sdk/pull/157
which has reviewed by Snowbridge team internally.

Synced with @acatangiu about that in channel
https://matrix.to/#/!gxqZwOyvhLstCgPJHO:matrix.parity.io/$N0CvTfDSl3cOQLEJeZBh-wlKJUXx7EDHAuNN5HuYHY4?via=matrix.parity.io&via=parity.io&via=matrix.org

---------

Co-authored-by: Clara van Staden <claravanstaden64@gmail.com>
---
 Cargo.lock                                    |  28 ++
 .../bridges/bridge-hub-westend/src/genesis.rs |   6 +
 .../bridges/bridge-hub-westend/Cargo.toml     |  15 +
 .../bridge-hub-westend/src/tests/mod.rs       |   6 +-
 .../src/tests/snowbridge.rs                   | 307 ++++++++++++++++++
 .../assets/asset-hub-westend/Cargo.toml       |   5 +
 .../assets/asset-hub-westend/src/lib.rs       |  14 +-
 .../asset-hub-westend/src/xcm_config.rs       |  88 ++++-
 .../bridge-hubs/bridge-hub-rococo/Cargo.toml  |   2 +
 .../bridge-hubs/bridge-hub-rococo/src/lib.rs  |   6 +-
 .../bridge-hub-rococo/src/xcm_config.rs       |   4 +-
 .../bridge-hub-rococo/tests/snowbridge.rs     |   1 +
 .../bridge-hub-rococo/tests/tests.rs          |  13 +-
 .../bridge-hubs/bridge-hub-westend/Cargo.toml |  40 +++
 .../src/bridge_to_ethereum_config.rs          | 219 +++++++++++++
 .../bridge-hubs/bridge-hub-westend/src/lib.rs |  51 ++-
 .../bridge-hub-westend/src/weights/mod.rs     |   5 +
 .../snowbridge_pallet_ethereum_client.rs      | 120 +++++++
 .../snowbridge_pallet_inbound_queue.rs        |  69 ++++
 .../snowbridge_pallet_outbound_queue.rs       |  87 +++++
 .../src/weights/snowbridge_pallet_system.rs   | 256 +++++++++++++++
 .../bridge-hub-westend/src/xcm_config.rs      |  51 ++-
 .../bridge-hub-westend/tests/snowbridge.rs    | 202 ++++++++++++
 .../bridge-hub-westend/tests/tests.rs         |   9 +-
 .../runtimes/constants/src/westend.rs         |  16 +
 cumulus/polkadot-parachain/Cargo.toml         |   1 +
 .../src/chain_spec/bridge_hubs.rs             |   4 +
 cumulus/polkadot-parachain/src/command.rs     |   1 +
 polkadot/node/service/src/chain_spec.rs       |   3 +-
 polkadot/runtime/westend/build.rs             |   9 +
 polkadot/runtime/westend/src/lib.rs           |   5 +
 prdoc/pr_5074.prdoc                           |  33 ++
 32 files changed, 1636 insertions(+), 40 deletions(-)
 create mode 100644 cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs
 create mode 100644 cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs
 create mode 100644 cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_ethereum_client.rs
 create mode 100644 cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_inbound_queue.rs
 create mode 100644 cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_outbound_queue.rs
 create mode 100644 cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_system.rs
 create mode 100644 cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs
 create mode 100644 prdoc/pr_5074.prdoc

diff --git a/Cargo.lock b/Cargo.lock
index 4f41f3ca1eb..826bb9398ae 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1017,6 +1017,7 @@ dependencies = [
  "polkadot-runtime-common",
  "primitive-types",
  "scale-info",
+ "snowbridge-router-primitives",
  "sp-api",
  "sp-block-builder",
  "sp-consensus-aura",
@@ -1026,6 +1027,7 @@ dependencies = [
  "sp-offchain",
  "sp-runtime",
  "sp-session",
+ "sp-std 14.0.0",
  "sp-storage 19.0.0",
  "sp-transaction-pool",
  "sp-version",
@@ -2110,6 +2112,7 @@ dependencies = [
  "cumulus-primitives-utility",
  "frame-benchmarking",
  "frame-executive",
+ "frame-metadata-hash-extension",
  "frame-support",
  "frame-system",
  "frame-system-benchmarking",
@@ -2232,10 +2235,13 @@ dependencies = [
 name = "bridge-hub-westend-integration-tests"
 version = "1.0.0"
 dependencies = [
+ "asset-hub-westend-runtime",
+ "bridge-hub-westend-runtime",
  "cumulus-pallet-xcmp-queue",
  "emulated-integration-tests-common",
  "frame-support",
  "hex-literal",
+ "log",
  "pallet-asset-conversion",
  "pallet-assets",
  "pallet-balances",
@@ -2243,10 +2249,20 @@ dependencies = [
  "pallet-message-queue",
  "pallet-xcm",
  "parachains-common",
+ "parity-scale-codec",
  "rococo-westend-system-emulated-network",
+ "scale-info",
+ "snowbridge-core",
+ "snowbridge-pallet-inbound-queue",
+ "snowbridge-pallet-inbound-queue-fixtures",
+ "snowbridge-pallet-outbound-queue",
+ "snowbridge-pallet-system",
+ "snowbridge-router-primitives",
+ "sp-core",
  "sp-runtime",
  "staging-xcm",
  "staging-xcm-executor",
+ "testnet-parachains-constants",
 ]
 
 [[package]]
@@ -2279,6 +2295,7 @@ dependencies = [
  "cumulus-primitives-utility",
  "frame-benchmarking",
  "frame-executive",
+ "frame-metadata-hash-extension",
  "frame-support",
  "frame-system",
  "frame-system-benchmarking",
@@ -2310,6 +2327,17 @@ dependencies = [
  "polkadot-runtime-common",
  "scale-info",
  "serde",
+ "snowbridge-beacon-primitives",
+ "snowbridge-core",
+ "snowbridge-outbound-queue-runtime-api",
+ "snowbridge-pallet-ethereum-client",
+ "snowbridge-pallet-inbound-queue",
+ "snowbridge-pallet-outbound-queue",
+ "snowbridge-pallet-system",
+ "snowbridge-router-primitives",
+ "snowbridge-runtime-common",
+ "snowbridge-runtime-test-common",
+ "snowbridge-system-runtime-api",
  "sp-api",
  "sp-block-builder",
  "sp-consensus-aura",
diff --git a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs
index a160d18d4cf..f38f385db65 100644
--- a/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs
+++ b/cumulus/parachains/integration-tests/emulated/chains/parachains/bridges/bridge-hub-westend/src/genesis.rs
@@ -23,6 +23,7 @@ use emulated_integration_tests_common::{
 use parachains_common::Balance;
 
 pub const PARA_ID: u32 = 1002;
+pub const ASSETHUB_PARA_ID: u32 = 1000;
 pub const ED: Balance = testnet_parachains_constants::westend::currency::EXISTENTIAL_DEPOSIT;
 
 pub fn genesis() -> Storage {
@@ -65,6 +66,11 @@ pub fn genesis() -> Storage {
 			owner: Some(get_account_id_from_seed::<sr25519::Public>(accounts::BOB)),
 			..Default::default()
 		},
+		ethereum_system: bridge_hub_westend_runtime::EthereumSystemConfig {
+			para_id: PARA_ID.into(),
+			asset_hub_para_id: ASSETHUB_PARA_ID.into(),
+			..Default::default()
+		},
 		..Default::default()
 	};
 
diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml
index 6b83479eaf8..1f2d2c8ece2 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml
+++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/Cargo.toml
@@ -12,6 +12,9 @@ workspace = true
 
 [dependencies]
 hex-literal = { workspace = true, default-features = true }
+codec = { workspace = true }
+log = { workspace = true }
+scale-info = { workspace = true }
 
 # Substrate
 frame-support = { workspace = true }
@@ -19,6 +22,7 @@ pallet-assets = { workspace = true }
 pallet-asset-conversion = { workspace = true }
 pallet-balances = { workspace = true }
 pallet-message-queue = { workspace = true, default-features = true }
+sp-core = { workspace = true }
 sp-runtime = { workspace = true }
 
 # Polkadot
@@ -34,3 +38,14 @@ cumulus-pallet-xcmp-queue = { workspace = true }
 emulated-integration-tests-common = { workspace = true }
 parachains-common = { workspace = true, default-features = true }
 rococo-westend-system-emulated-network = { workspace = true }
+testnet-parachains-constants = { workspace = true, features = ["westend"] }
+asset-hub-westend-runtime = { workspace = true }
+bridge-hub-westend-runtime = { workspace = true }
+
+# Snowbridge
+snowbridge-core = { workspace = true }
+snowbridge-router-primitives = { workspace = true }
+snowbridge-pallet-system = { workspace = true }
+snowbridge-pallet-outbound-queue = { workspace = true }
+snowbridge-pallet-inbound-queue = { workspace = true }
+snowbridge-pallet-inbound-queue-fixtures = { workspace = true }
diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs
index 768b647a13f..87ae9aedd6f 100644
--- a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs
+++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/mod.rs
@@ -18,13 +18,9 @@ use crate::imports::*;
 mod asset_transfers;
 mod claim_assets;
 mod send_xcm;
+mod snowbridge;
 mod teleport;
 
-mod snowbridge {
-	pub const CHAIN_ID: u64 = 11155111;
-	pub const WETH: [u8; 20] = hex_literal::hex!("87d1f7fdfEe7f651FaBc8bFCB6E086C278b77A7d");
-}
-
 pub(crate) fn asset_hub_rococo_location() -> Location {
 	Location::new(2, [GlobalConsensus(Rococo), Parachain(AssetHubRococo::para_id().into())])
 }
diff --git a/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs
new file mode 100644
index 00000000000..b4db9b365f3
--- /dev/null
+++ b/cumulus/parachains/integration-tests/emulated/tests/bridges/bridge-hub-westend/src/tests/snowbridge.rs
@@ -0,0 +1,307 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+use crate::imports::*;
+use asset_hub_westend_runtime::xcm_config::bridging::to_ethereum::DefaultBridgeHubEthereumBaseFee;
+use bridge_hub_westend_runtime::EthereumInboundQueue;
+use codec::{Decode, Encode};
+use frame_support::pallet_prelude::TypeInfo;
+use hex_literal::hex;
+use snowbridge_core::outbound::OperatingMode;
+use snowbridge_router_primitives::inbound::{
+	Command, ConvertMessage, Destination, MessageV1, VersionedMessage,
+};
+use testnet_parachains_constants::westend::snowbridge::EthereumNetwork;
+
+const INITIAL_FUND: u128 = 5_000_000_000_000_000_000;
+pub const CHAIN_ID: u64 = 11155111;
+pub const WETH: [u8; 20] = hex!("87d1f7fdfEe7f651FaBc8bFCB6E086C278b77A7d");
+const ETHEREUM_DESTINATION_ADDRESS: [u8; 20] = hex!("44a57ee2f2FCcb85FDa2B0B18EBD0D8D2333700e");
+const XCM_FEE: u128 = 100_000_000_000;
+const WETH_AMOUNT: u128 = 1_000_000_000;
+
+#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
+pub enum ControlCall {
+	#[codec(index = 3)]
+	CreateAgent,
+	#[codec(index = 4)]
+	CreateChannel { mode: OperatingMode },
+}
+
+#[allow(clippy::large_enum_variant)]
+#[derive(Encode, Decode, Debug, PartialEq, Eq, Clone, TypeInfo)]
+pub enum SnowbridgeControl {
+	#[codec(index = 83)]
+	Control(ControlCall),
+}
+
+/// Tests the registering of a token as an asset on AssetHub.
+#[test]
+fn register_weth_token_from_ethereum_to_asset_hub() {
+	// Fund AssetHub sovereign account so that it can pay execution fees.
+	BridgeHubWestend::fund_para_sovereign(AssetHubWestend::para_id().into(), INITIAL_FUND);
+
+	BridgeHubWestend::execute_with(|| {
+		type RuntimeEvent = <BridgeHubWestend as Chain>::RuntimeEvent;
+
+		type Converter = <bridge_hub_westend_runtime::Runtime as snowbridge_pallet_inbound_queue::Config>::MessageConverter;
+
+		let message = VersionedMessage::V1(MessageV1 {
+			chain_id: CHAIN_ID,
+			command: Command::RegisterToken { token: WETH.into(), fee: XCM_FEE },
+		});
+		let (xcm, _) = Converter::convert(message).unwrap();
+		let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubRococo::para_id().into()).unwrap();
+
+		assert_expected_events!(
+			BridgeHubWestend,
+			vec![
+				RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},
+			]
+		);
+	});
+
+	AssetHubWestend::execute_with(|| {
+		type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
+
+		assert_expected_events!(
+			AssetHubWestend,
+			vec![
+				RuntimeEvent::ForeignAssets(pallet_assets::Event::Created { .. }) => {},
+			]
+		);
+	});
+}
+
+/// Tests the registering of a token as an asset on AssetHub, and then subsequently sending
+/// a token from Ethereum to AssetHub.
+#[test]
+fn send_token_from_ethereum_to_asset_hub() {
+	let asset_hub_sovereign = BridgeHubWestend::sovereign_account_id_of(Location::new(
+		1,
+		[Parachain(AssetHubWestend::para_id().into())],
+	));
+	// Fund AssetHub sovereign account so it can pay execution fees for the asset transfer
+	BridgeHubWestend::fund_accounts(vec![(asset_hub_sovereign.clone(), INITIAL_FUND)]);
+
+	// Fund ethereum sovereign on AssetHub
+	AssetHubWestend::fund_accounts(vec![(AssetHubWestendReceiver::get(), INITIAL_FUND)]);
+
+	let weth_asset_location: Location =
+		(Parent, Parent, EthereumNetwork::get(), AccountKey20 { network: None, key: WETH }).into();
+
+	AssetHubWestend::execute_with(|| {
+		type RuntimeOrigin = <AssetHubWestend as Chain>::RuntimeOrigin;
+
+		assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::ForeignAssets::force_create(
+			RuntimeOrigin::root(),
+			weth_asset_location.clone().try_into().unwrap(),
+			asset_hub_sovereign.into(),
+			false,
+			1,
+		));
+
+		assert!(<AssetHubWestend as AssetHubWestendPallet>::ForeignAssets::asset_exists(
+			weth_asset_location.clone().try_into().unwrap(),
+		));
+	});
+
+	BridgeHubWestend::execute_with(|| {
+		type RuntimeEvent = <BridgeHubWestend as Chain>::RuntimeEvent;
+
+		type Converter = <bridge_hub_westend_runtime::Runtime as snowbridge_pallet_inbound_queue::Config>::MessageConverter;
+
+		let message = VersionedMessage::V1(MessageV1 {
+			chain_id: CHAIN_ID,
+			command: Command::SendToken {
+				token: WETH.into(),
+				destination: Destination::AccountId32 { id: AssetHubWestendReceiver::get().into() },
+				amount: WETH_AMOUNT,
+				fee: XCM_FEE,
+			},
+		});
+		let (xcm, _) = Converter::convert(message).unwrap();
+		let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap();
+
+		// Check that the message was sent
+		assert_expected_events!(
+			BridgeHubWestend,
+			vec![
+				RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) => {},
+			]
+		);
+	});
+
+	AssetHubWestend::execute_with(|| {
+		type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
+
+		// Check that the token was received and issued as a foreign asset on AssetHub
+		assert_expected_events!(
+			AssetHubWestend,
+			vec![
+				RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {},
+			]
+		);
+	});
+}
+
+/// Tests the full cycle of token transfers:
+/// - registering a token on AssetHub
+/// - sending a token to AssetHub
+/// - returning the token to Ethereum
+#[test]
+fn send_weth_asset_from_asset_hub_to_ethereum() {
+	let assethub_location = BridgeHubWestend::sibling_location_of(AssetHubWestend::para_id());
+	let assethub_sovereign = BridgeHubWestend::sovereign_account_id_of(assethub_location);
+	let weth_asset_location: Location =
+		(Parent, Parent, EthereumNetwork::get(), AccountKey20 { network: None, key: WETH }).into();
+
+	AssetHubWestend::force_default_xcm_version(Some(XCM_VERSION));
+	BridgeHubWestend::force_default_xcm_version(Some(XCM_VERSION));
+	AssetHubWestend::force_xcm_version(
+		Location::new(2, [GlobalConsensus(Ethereum { chain_id: CHAIN_ID })]),
+		XCM_VERSION,
+	);
+
+	BridgeHubWestend::fund_accounts(vec![(assethub_sovereign.clone(), INITIAL_FUND)]);
+
+	AssetHubWestend::execute_with(|| {
+		type RuntimeOrigin = <AssetHubWestend as Chain>::RuntimeOrigin;
+
+		assert_ok!(<AssetHubWestend as AssetHubWestendPallet>::ForeignAssets::force_create(
+			RuntimeOrigin::root(),
+			weth_asset_location.clone().try_into().unwrap(),
+			assethub_sovereign.clone().into(),
+			false,
+			1,
+		));
+
+		assert!(<AssetHubWestend as AssetHubWestendPallet>::ForeignAssets::asset_exists(
+			weth_asset_location.clone().try_into().unwrap(),
+		));
+	});
+
+	BridgeHubWestend::execute_with(|| {
+		type RuntimeEvent = <BridgeHubWestend as Chain>::RuntimeEvent;
+		type Converter = <bridge_hub_westend_runtime::Runtime as
+	snowbridge_pallet_inbound_queue::Config>::MessageConverter;
+
+		let message = VersionedMessage::V1(MessageV1 {
+			chain_id: CHAIN_ID,
+			command: Command::SendToken {
+				token: WETH.into(),
+				destination: Destination::AccountId32 { id: AssetHubWestendReceiver::get().into() },
+				amount: WETH_AMOUNT,
+				fee: XCM_FEE,
+			},
+		});
+		let (xcm, _) = Converter::convert(message).unwrap();
+		let _ = EthereumInboundQueue::send_xcm(xcm, AssetHubWestend::para_id().into()).unwrap();
+
+		// Check that the send token message was sent using xcm
+		assert_expected_events!(
+			BridgeHubWestend,
+			vec![
+	         RuntimeEvent::XcmpQueue(cumulus_pallet_xcmp_queue::Event::XcmpMessageSent { .. }) =>{},]
+		);
+	});
+
+	AssetHubWestend::execute_with(|| {
+		type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
+		type RuntimeOrigin = <AssetHubWestend as Chain>::RuntimeOrigin;
+
+		// Check that AssetHub has issued the foreign asset
+		assert_expected_events!(
+			AssetHubWestend,
+			vec![
+				RuntimeEvent::ForeignAssets(pallet_assets::Event::Issued { .. }) => {},
+			]
+		);
+		let assets = vec![Asset {
+			id: AssetId(Location::new(
+				2,
+				[
+					GlobalConsensus(Ethereum { chain_id: CHAIN_ID }),
+					AccountKey20 { network: None, key: WETH },
+				],
+			)),
+			fun: Fungible(WETH_AMOUNT),
+		}];
+		let multi_assets = VersionedAssets::V4(Assets::from(assets));
+
+		let destination = VersionedLocation::V4(Location::new(
+			2,
+			[GlobalConsensus(Ethereum { chain_id: CHAIN_ID })],
+		));
+
+		let beneficiary = VersionedLocation::V4(Location::new(
+			0,
+			[AccountKey20 { network: None, key: ETHEREUM_DESTINATION_ADDRESS.into() }],
+		));
+
+		let free_balance_before =
+			<AssetHubWestend as AssetHubWestendPallet>::Balances::free_balance(
+				AssetHubWestendReceiver::get(),
+			);
+		// Send the Weth back to Ethereum
+		<AssetHubWestend as AssetHubWestendPallet>::PolkadotXcm::limited_reserve_transfer_assets(
+			RuntimeOrigin::signed(AssetHubWestendReceiver::get()),
+			Box::new(destination),
+			Box::new(beneficiary),
+			Box::new(multi_assets),
+			0,
+			Unlimited,
+		)
+		.unwrap();
+		let free_balance_after = <AssetHubWestend as AssetHubWestendPallet>::Balances::free_balance(
+			AssetHubWestendReceiver::get(),
+		);
+		// Assert at least DefaultBridgeHubEthereumBaseFee charged from the sender
+		let free_balance_diff = free_balance_before - free_balance_after;
+		assert!(free_balance_diff > DefaultBridgeHubEthereumBaseFee::get());
+	});
+
+	BridgeHubWestend::execute_with(|| {
+		use bridge_hub_westend_runtime::xcm_config::TreasuryAccount;
+		type RuntimeEvent = <BridgeHubWestend as Chain>::RuntimeEvent;
+		// Check that the transfer token back to Ethereum message was queue in the Ethereum
+		// Outbound Queue
+		assert_expected_events!(
+			BridgeHubWestend,
+			vec![
+
+	RuntimeEvent::EthereumOutboundQueue(snowbridge_pallet_outbound_queue::Event::MessageQueued
+	{..}) => {},             ]
+		);
+		let events = BridgeHubWestend::events();
+		// Check that the local fee was credited to the Snowbridge sovereign account
+		assert!(
+			events.iter().any(|event| matches!(
+				event,
+				RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount })
+					if *who == TreasuryAccount::get().into() && *amount == 5071000000
+			)),
+			"Snowbridge sovereign takes local fee."
+		);
+		// Check that the remote fee was credited to the AssetHub sovereign account
+		assert!(
+			events.iter().any(|event| matches!(
+				event,
+				RuntimeEvent::Balances(pallet_balances::Event::Minted { who, amount })
+					if *who == assethub_sovereign && *amount == 2680000000000,
+			)),
+			"AssetHub sovereign takes remote fee."
+		);
+	});
+}
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml
index 6b1bf769ace..2f244a07e8f 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml
+++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/Cargo.toml
@@ -53,6 +53,7 @@ sp-inherents = { workspace = true }
 sp-offchain = { workspace = true }
 sp-runtime = { workspace = true }
 sp-session = { workspace = true }
+sp-std = { workspace = true }
 sp-storage = { workspace = true }
 sp-transaction-pool = { workspace = true }
 sp-version = { workspace = true }
@@ -93,6 +94,7 @@ bp-asset-hub-rococo = { workspace = true }
 bp-asset-hub-westend = { workspace = true }
 bp-bridge-hub-rococo = { workspace = true }
 bp-bridge-hub-westend = { workspace = true }
+snowbridge-router-primitives = { workspace = true }
 
 [dev-dependencies]
 asset-test-utils = { workspace = true, default-features = true }
@@ -134,6 +136,7 @@ runtime-benchmarks = [
 	"parachains-common/runtime-benchmarks",
 	"polkadot-parachain-primitives/runtime-benchmarks",
 	"polkadot-runtime-common/runtime-benchmarks",
+	"snowbridge-router-primitives/runtime-benchmarks",
 	"sp-runtime/runtime-benchmarks",
 	"xcm-builder/runtime-benchmarks",
 	"xcm-executor/runtime-benchmarks",
@@ -230,6 +233,7 @@ std = [
 	"polkadot-runtime-common/std",
 	"primitive-types/std",
 	"scale-info/std",
+	"snowbridge-router-primitives/std",
 	"sp-api/std",
 	"sp-block-builder/std",
 	"sp-consensus-aura/std",
@@ -239,6 +243,7 @@ std = [
 	"sp-offchain/std",
 	"sp-runtime/std",
 	"sp-session/std",
+	"sp-std/std",
 	"sp-storage/std",
 	"sp-transaction-pool/std",
 	"sp-version/std",
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs
index 62d3462038c..201698ecb7f 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/lib.rs
@@ -74,7 +74,9 @@ use sp_runtime::{
 #[cfg(feature = "std")]
 use sp_version::NativeVersion;
 use sp_version::RuntimeVersion;
-use testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee, time::*};
+use testnet_parachains_constants::westend::{
+	consensus::*, currency::*, fee::WeightToFee, snowbridge::EthereumNetwork, time::*,
+};
 use xcm_config::{
 	ForeignAssetsConvertedConcreteId, ForeignCreatorsSovereignAccountOf,
 	PoolAssetsConvertedConcreteId, TrustBackedAssetsConvertedConcreteId,
@@ -85,7 +87,10 @@ use xcm_config::{
 #[cfg(any(feature = "std", test))]
 pub use sp_runtime::BuildStorage;
 
-use assets_common::{foreign_creators::ForeignCreators, matching::FromSiblingParachain};
+use assets_common::{
+	foreign_creators::ForeignCreators,
+	matching::{FromNetwork, FromSiblingParachain},
+};
 use polkadot_runtime_common::{BlockHashCount, SlowAdjustingFeeUpdate};
 use xcm::{
 	latest::prelude::AssetId,
@@ -410,7 +415,10 @@ impl pallet_assets::Config<ForeignAssetsInstance> for Runtime {
 	type AssetIdParameter = xcm::v3::Location;
 	type Currency = Balances;
 	type CreateOrigin = ForeignCreators<
-		FromSiblingParachain<parachain_info::Pallet<Runtime>, xcm::v3::Location>,
+		(
+			FromSiblingParachain<parachain_info::Pallet<Runtime>, xcm::v3::Location>,
+			FromNetwork<xcm_config::UniversalLocation, EthereumNetwork, xcm::v3::Location>,
+		),
 		ForeignCreatorsSovereignAccountOf,
 		AccountId,
 		xcm::v3::Location,
diff --git a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs
index 5ecfce18b6d..bf45e146e33 100644
--- a/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs
+++ b/cumulus/parachains/runtimes/assets/asset-hub-westend/src/xcm_config.rs
@@ -42,6 +42,7 @@ use parachains_common::{
 };
 use polkadot_parachain_primitives::primitives::Sibling;
 use polkadot_runtime_common::xcm_sender::ExponentialPrice;
+use snowbridge_router_primitives::inbound::GlobalConsensusEthereumConvertsFor;
 use sp_runtime::traits::{AccountIdConversion, ConvertInto};
 use xcm::latest::prelude::*;
 use xcm_builder::{
@@ -52,9 +53,10 @@ use xcm_builder::{
 	GlobalConsensusParachainConvertsFor, HashedDescription, IsConcrete, LocalMint,
 	NetworkExportTableItem, NoChecking, NonFungiblesAdapter, ParentAsSuperuser, ParentIsPreset,
 	RelayChainAsNative, SendXcmFeeToAccount, SiblingParachainAsNative, SiblingParachainConvertsVia,
-	SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, StartsWith,
-	StartsWithExplicitGlobalConsensus, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents,
-	WeightInfoBounds, WithComputedOrigin, WithUniqueTopic, XcmFeeManagerFromComponents,
+	SignedAccountId32AsNative, SignedToAccountId32, SovereignPaidRemoteExporter,
+	SovereignSignedViaLocation, StartsWith, StartsWithExplicitGlobalConsensus, TakeWeightCredit,
+	TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic,
+	XcmFeeManagerFromComponents,
 };
 use xcm_executor::XcmExecutor;
 
@@ -99,6 +101,9 @@ pub type LocationToAccountId = (
 	// Different global consensus parachain sovereign account.
 	// (Used for over-bridge transfers and reserve processing)
 	GlobalConsensusParachainConvertsFor<UniversalLocation, AccountId>,
+	// Ethereum contract sovereign account.
+	// (Used to get convert ethereum contract locations to sovereign account)
+	GlobalConsensusEthereumConvertsFor<AccountId>,
 );
 
 /// Means for transacting the native currency on this chain.
@@ -358,7 +363,10 @@ impl xcm_executor::Config for XcmConfig {
 	// as reserve locations (we trust the Bridge Hub to relay the message that a reserve is being
 	// held). On Westend Asset Hub, we allow Rococo Asset Hub to act as reserve for any asset native
 	// to the Rococo or Ethereum ecosystems.
-	type IsReserve = (bridging::to_rococo::RococoOrEthereumAssetFromAssetHubRococo,);
+	type IsReserve = (
+		bridging::to_rococo::RococoOrEthereumAssetFromAssetHubRococo,
+		bridging::to_ethereum::IsTrustedBridgedReserveLocationForForeignAsset,
+	);
 	type IsTeleporter = TrustedTeleporters;
 	type UniversalLocation = UniversalLocation;
 	type Barrier = Barrier;
@@ -431,7 +439,8 @@ impl xcm_executor::Config for XcmConfig {
 		SendXcmFeeToAccount<Self::AssetTransactor, TreasuryAccount>,
 	>;
 	type MessageExporter = ();
-	type UniversalAliases = (bridging::to_rococo::UniversalAliases,);
+	type UniversalAliases =
+		(bridging::to_rococo::UniversalAliases, bridging::to_ethereum::UniversalAliases);
 	type CallDispatcher = RuntimeCall;
 	type SafeCallFilter = Everything;
 	type Aliasers = Nothing;
@@ -463,6 +472,13 @@ pub type XcmRouter = WithUniqueTopic<(
 	// Router which wraps and sends xcm to BridgeHub to be delivered to the Rococo
 	// GlobalConsensus
 	ToRococoXcmRouter,
+	// Router which wraps and sends xcm to BridgeHub to be delivered to the Ethereum
+	// GlobalConsensus
+	SovereignPaidRemoteExporter<
+		bridging::to_ethereum::EthereumNetworkExportTable,
+		XcmpQueue,
+		UniversalLocation,
+	>,
 )>;
 
 impl pallet_xcm::Config for Runtime {
@@ -504,6 +520,7 @@ pub type ForeignCreatorsSovereignAccountOf = (
 	SiblingParachainConvertsVia<Sibling, AccountId>,
 	AccountId32Aliases<RelayNetwork, AccountId>,
 	ParentIsPreset<AccountId>,
+	GlobalConsensusEthereumConvertsFor<AccountId>,
 );
 
 /// Simple conversion of `u32` into an `AssetId` for use in benchmarking.
@@ -627,6 +644,67 @@ pub mod bridging {
 		}
 	}
 
+	pub mod to_ethereum {
+		use super::*;
+		use assets_common::matching::FromNetwork;
+		use sp_std::collections::btree_set::BTreeSet;
+		use testnet_parachains_constants::westend::snowbridge::{
+			EthereumNetwork, INBOUND_QUEUE_PALLET_INDEX,
+		};
+
+		parameter_types! {
+			/// User fee for ERC20 token transfer back to Ethereum.
+			/// (initially was calculated by test `OutboundQueue::calculate_fees` - ETH/WND 1/400 and fee_per_gas 20 GWEI = 2200698000000 + *25%)
+			/// Needs to be more than fee calculated from DefaultFeeConfig FeeConfigRecord in snowbridge:parachain/pallets/outbound-queue/src/lib.rs
+			/// Polkadot uses 10 decimals, Kusama,Rococo,Westend 12 decimals.
+			pub const DefaultBridgeHubEthereumBaseFee: Balance = 2_750_872_500_000;
+			pub storage BridgeHubEthereumBaseFee: Balance = DefaultBridgeHubEthereumBaseFee::get();
+			pub SiblingBridgeHubWithEthereumInboundQueueInstance: Location = Location::new(
+				1,
+				[
+					Parachain(SiblingBridgeHubParaId::get()),
+					PalletInstance(INBOUND_QUEUE_PALLET_INDEX)
+				]
+			);
+
+			/// Set up exporters configuration.
+			/// `Option<Asset>` represents static "base fee" which is used for total delivery fee calculation.
+			pub BridgeTable: sp_std::vec::Vec<NetworkExportTableItem> = sp_std::vec![
+				NetworkExportTableItem::new(
+					EthereumNetwork::get(),
+					Some(sp_std::vec![Junctions::Here]),
+					SiblingBridgeHub::get(),
+					Some((
+						XcmBridgeHubRouterFeeAssetId::get(),
+						BridgeHubEthereumBaseFee::get(),
+					).into())
+				),
+			];
+
+			/// Universal aliases
+			pub UniversalAliases: BTreeSet<(Location, Junction)> = BTreeSet::from_iter(
+				sp_std::vec![
+					(SiblingBridgeHubWithEthereumInboundQueueInstance::get(), GlobalConsensus(EthereumNetwork::get())),
+				]
+			);
+
+			pub EthereumBridgeTable: sp_std::vec::Vec<NetworkExportTableItem> = sp_std::vec::Vec::new().into_iter()
+				.chain(BridgeTable::get())
+				.collect();
+		}
+
+		pub type EthereumNetworkExportTable = xcm_builder::NetworkExportTable<EthereumBridgeTable>;
+
+		pub type IsTrustedBridgedReserveLocationForForeignAsset =
+			IsForeignConcreteAsset<FromNetwork<UniversalLocation, EthereumNetwork>>;
+
+		impl Contains<(Location, Junction)> for UniversalAliases {
+			fn contains(alias: &(Location, Junction)) -> bool {
+				UniversalAliases::get().contains(alias)
+			}
+		}
+	}
+
 	/// Benchmarks helper for bridging configuration.
 	#[cfg(feature = "runtime-benchmarks")]
 	pub struct BridgingBenchmarksHelper;
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml
index 7a60a3f6484..f3e03aa7430 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/Cargo.toml
@@ -26,6 +26,7 @@ serde = { optional = true, features = ["derive"], workspace = true, default-feat
 # Substrate
 frame-benchmarking = { optional = true, workspace = true }
 frame-executive = { workspace = true }
+frame-metadata-hash-extension = { workspace = true }
 frame-support = { workspace = true }
 frame-system = { workspace = true }
 frame-system-benchmarking = { optional = true, workspace = true }
@@ -159,6 +160,7 @@ std = [
 	"cumulus-primitives-utility/std",
 	"frame-benchmarking/std",
 	"frame-executive/std",
+	"frame-metadata-hash-extension/std",
 	"frame-support/std",
 	"frame-system-benchmarking?/std",
 	"frame-system-rpc-runtime-api/std",
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs
index 02ef0b9c0c7..c8face15627 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/lib.rs
@@ -145,6 +145,7 @@ pub type SignedExtra = (
 		bridge_to_bulletin_config::OnBridgeHubRococoRefundRococoBulletinMessages,
 	),
 	cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim<Runtime>,
+	frame_metadata_hash_extension::CheckMetadataHash<Runtime>,
 );
 
 /// Unchecked extrinsic type as expected by this runtime.
@@ -1612,6 +1613,7 @@ mod tests {
 					bridge_to_bulletin_config::OnBridgeHubRococoRefundRococoBulletinMessages::default(),
 				),
 				cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::new(),
+				frame_metadata_hash_extension::CheckMetadataHash::new(false),
 			);
 
 			// for BridgeHubRococo
@@ -1625,9 +1627,9 @@ mod tests {
 					10,
 					(((), ()), ((), ())),
 				);
-				assert_eq!(payload.encode(), bhr_indirect_payload.encode());
+				assert_eq!(payload.encode().split_last().unwrap().1, bhr_indirect_payload.encode());
 				assert_eq!(
-					payload.additional_signed().unwrap().encode(),
+					payload.additional_signed().unwrap().encode().split_last().unwrap().1,
 					bhr_indirect_payload.additional_signed().unwrap().encode()
 				)
 			}
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs
index 8c077470ba1..92368b29212 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/xcm_config.rs
@@ -397,7 +397,9 @@ impl<WaivedLocations: Contains<Location>, FeeHandler: HandleFee> FeeManager
 	fn is_waived(origin: Option<&Location>, fee_reason: FeeReason) -> bool {
 		let Some(loc) = origin else { return false };
 		if let Export { network, destination: Here } = fee_reason {
-			return !(network == EthereumNetwork::get())
+			if network == EthereumNetwork::get() {
+				return false
+			}
 		}
 		WaivedLocations::contains(loc)
 	}
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs
index 5960ab7b550..c7b5850f9ff 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/snowbridge.rs
@@ -188,6 +188,7 @@ fn construct_extrinsic(
 			OnBridgeHubRococoRefundRococoBulletinMessages::default(),
 		),
 		cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::new(),
+		frame_metadata_hash_extension::CheckMetadataHash::<Runtime>::new(false),
 	);
 	let payload = SignedPayload::new(call.clone(), extra.clone()).unwrap();
 	let signature = payload.using_encoded(|e| sender.sign(e));
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs
index ca0d89f038c..e91837af0b2 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/tests/tests.rs
@@ -65,6 +65,7 @@ fn construct_extrinsic(
 			bridge_to_bulletin_config::OnBridgeHubRococoRefundRococoBulletinMessages::default(),
 		),
 		cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::new(),
+		frame_metadata_hash_extension::CheckMetadataHash::new(false),
 	);
 	let payload = SignedPayload::new(call.clone(), extra.clone()).unwrap();
 	let signature = payload.using_encoded(|e| sender.sign(e));
@@ -399,8 +400,8 @@ mod bridge_hub_westend_tests {
 					WeightToFee,
 				>()
 			},
-			Perbill::from_percent(33),
-			Some(-33),
+			Perbill::from_percent(25),
+			Some(-25),
 			&format!(
 				"Estimate fee for `ExportMessage` for runtime: {:?}",
 				<Runtime as frame_system::Config>::Version::get()
@@ -418,8 +419,8 @@ mod bridge_hub_westend_tests {
 					RuntimeTestsAdapter,
 				>(collator_session_keys(), construct_and_estimate_extrinsic_fee)
 			},
-			Perbill::from_percent(33),
-			Some(-33),
+			Perbill::from_percent(25),
+			Some(-25),
 			&format!(
 				"Estimate fee for `single message delivery` for runtime: {:?}",
 				<Runtime as frame_system::Config>::Version::get()
@@ -437,8 +438,8 @@ mod bridge_hub_westend_tests {
 					RuntimeTestsAdapter,
 				>(collator_session_keys(), construct_and_estimate_extrinsic_fee)
 			},
-			Perbill::from_percent(33),
-			Some(-33),
+			Perbill::from_percent(25),
+			Some(-25),
 			&format!(
 				"Estimate fee for `single message confirmation` for runtime: {:?}",
 				<Runtime as frame_system::Config>::Version::get()
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml
index 4326cbf926d..a9381501359 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/Cargo.toml
@@ -22,6 +22,7 @@ serde = { optional = true, features = ["derive"], workspace = true, default-feat
 # Substrate
 frame-benchmarking = { optional = true, workspace = true }
 frame-executive = { workspace = true }
+frame-metadata-hash-extension = { workspace = true }
 frame-support = { workspace = true }
 frame-system = { workspace = true }
 frame-system-benchmarking = { optional = true, workspace = true }
@@ -100,10 +101,24 @@ pallet-xcm-bridge-hub = { workspace = true }
 bridge-runtime-common = { workspace = true }
 bridge-hub-common = { workspace = true }
 
+# Ethereum Bridge (Snowbridge)
+snowbridge-beacon-primitives = { workspace = true }
+snowbridge-pallet-system = { workspace = true }
+snowbridge-system-runtime-api = { workspace = true }
+snowbridge-core = { workspace = true }
+snowbridge-pallet-ethereum-client = { workspace = true }
+snowbridge-pallet-inbound-queue = { workspace = true }
+snowbridge-pallet-outbound-queue = { workspace = true }
+snowbridge-outbound-queue-runtime-api = { workspace = true }
+snowbridge-router-primitives = { workspace = true }
+snowbridge-runtime-common = { workspace = true }
+
+
 [dev-dependencies]
 bridge-hub-test-utils = { workspace = true, default-features = true }
 bridge-runtime-common = { features = ["integrity-test"], workspace = true, default-features = true }
 sp-keyring = { workspace = true, default-features = true }
+snowbridge-runtime-test-common = { workspace = true, default-features = true }
 
 [features]
 default = ["std"]
@@ -134,6 +149,7 @@ std = [
 	"cumulus-primitives-utility/std",
 	"frame-benchmarking/std",
 	"frame-executive/std",
+	"frame-metadata-hash-extension/std",
 	"frame-support/std",
 	"frame-system-benchmarking?/std",
 	"frame-system-rpc-runtime-api/std",
@@ -164,6 +180,16 @@ std = [
 	"polkadot-runtime-common/std",
 	"scale-info/std",
 	"serde",
+	"snowbridge-beacon-primitives/std",
+	"snowbridge-core/std",
+	"snowbridge-outbound-queue-runtime-api/std",
+	"snowbridge-pallet-ethereum-client/std",
+	"snowbridge-pallet-inbound-queue/std",
+	"snowbridge-pallet-outbound-queue/std",
+	"snowbridge-pallet-system/std",
+	"snowbridge-router-primitives/std",
+	"snowbridge-runtime-common/std",
+	"snowbridge-system-runtime-api/std",
 	"sp-api/std",
 	"sp-block-builder/std",
 	"sp-consensus-aura/std",
@@ -215,6 +241,14 @@ runtime-benchmarks = [
 	"parachains-common/runtime-benchmarks",
 	"polkadot-parachain-primitives/runtime-benchmarks",
 	"polkadot-runtime-common/runtime-benchmarks",
+	"snowbridge-core/runtime-benchmarks",
+	"snowbridge-pallet-ethereum-client/runtime-benchmarks",
+	"snowbridge-pallet-inbound-queue/runtime-benchmarks",
+	"snowbridge-pallet-outbound-queue/runtime-benchmarks",
+	"snowbridge-pallet-system/runtime-benchmarks",
+	"snowbridge-router-primitives/runtime-benchmarks",
+	"snowbridge-runtime-common/runtime-benchmarks",
+	"snowbridge-runtime-test-common/runtime-benchmarks",
 	"sp-runtime/runtime-benchmarks",
 	"xcm-builder/runtime-benchmarks",
 	"xcm-executor/runtime-benchmarks",
@@ -248,6 +282,10 @@ try-runtime = [
 	"pallet-xcm/try-runtime",
 	"parachain-info/try-runtime",
 	"polkadot-runtime-common/try-runtime",
+	"snowbridge-pallet-ethereum-client/try-runtime",
+	"snowbridge-pallet-inbound-queue/try-runtime",
+	"snowbridge-pallet-outbound-queue/try-runtime",
+	"snowbridge-pallet-system/try-runtime",
 	"sp-runtime/try-runtime",
 ]
 
@@ -255,3 +293,5 @@ try-runtime = [
 # deployment. This will disable stuff that shouldn't be part of the on-chain wasm
 # to make it smaller, like logging for example.
 on-chain-release-build = ["sp-api/disable-logging"]
+
+fast-runtime = []
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs
new file mode 100644
index 00000000000..7922d3ed02b
--- /dev/null
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/bridge_to_ethereum_config.rs
@@ -0,0 +1,219 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// This file is part of Cumulus.
+
+// Cumulus 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.
+
+// Cumulus 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 Cumulus.  If not, see <http://www.gnu.org/licenses/>.
+
+#[cfg(not(feature = "runtime-benchmarks"))]
+use crate::XcmRouter;
+use crate::{
+	xcm_config,
+	xcm_config::{TreasuryAccount, UniversalLocation},
+	Balances, EthereumInboundQueue, EthereumOutboundQueue, EthereumSystem, MessageQueue, Runtime,
+	RuntimeEvent, TransactionByteFee,
+};
+use parachains_common::{AccountId, Balance};
+use snowbridge_beacon_primitives::{Fork, ForkVersions};
+use snowbridge_core::{gwei, meth, AllowSiblingsOnly, PricingParameters, Rewards};
+use snowbridge_router_primitives::{inbound::MessageToXcm, outbound::EthereumBlobExporter};
+use sp_core::H160;
+use testnet_parachains_constants::westend::{
+	currency::*,
+	fee::WeightToFee,
+	snowbridge::{EthereumNetwork, INBOUND_QUEUE_PALLET_INDEX},
+};
+
+#[cfg(feature = "runtime-benchmarks")]
+use benchmark_helpers::DoNothingRouter;
+use frame_support::{parameter_types, weights::ConstantMultiplier};
+use pallet_xcm::EnsureXcm;
+use sp_runtime::{
+	traits::{ConstU32, ConstU8, Keccak256},
+	FixedU128,
+};
+
+/// Exports message to the Ethereum Gateway contract.
+pub type SnowbridgeExporter = EthereumBlobExporter<
+	UniversalLocation,
+	EthereumNetwork,
+	snowbridge_pallet_outbound_queue::Pallet<Runtime>,
+	snowbridge_core::AgentIdOf,
+>;
+
+// Ethereum Bridge
+parameter_types! {
+	pub storage EthereumGatewayAddress: H160 = H160(hex_literal::hex!("EDa338E4dC46038493b885327842fD3E301CaB39"));
+}
+
+parameter_types! {
+	pub const CreateAssetCall: [u8;2] = [53, 0];
+	pub const CreateAssetDeposit: u128 = (UNITS / 10) + EXISTENTIAL_DEPOSIT;
+	pub Parameters: PricingParameters<u128> = PricingParameters {
+		exchange_rate: FixedU128::from_rational(1, 400),
+		fee_per_gas: gwei(20),
+		rewards: Rewards { local: 1 * UNITS, remote: meth(1) },
+		multiplier: FixedU128::from_rational(1, 1),
+	};
+}
+
+impl snowbridge_pallet_inbound_queue::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type Verifier = snowbridge_pallet_ethereum_client::Pallet<Runtime>;
+	type Token = Balances;
+	#[cfg(not(feature = "runtime-benchmarks"))]
+	type XcmSender = XcmRouter;
+	#[cfg(feature = "runtime-benchmarks")]
+	type XcmSender = DoNothingRouter;
+	type ChannelLookup = EthereumSystem;
+	type GatewayAddress = EthereumGatewayAddress;
+	#[cfg(feature = "runtime-benchmarks")]
+	type Helper = Runtime;
+	type MessageConverter = MessageToXcm<
+		CreateAssetCall,
+		CreateAssetDeposit,
+		ConstU8<INBOUND_QUEUE_PALLET_INDEX>,
+		AccountId,
+		Balance,
+	>;
+	type WeightToFee = WeightToFee;
+	type LengthToFee = ConstantMultiplier<Balance, TransactionByteFee>;
+	type MaxMessageSize = ConstU32<2048>;
+	type WeightInfo = crate::weights::snowbridge_pallet_inbound_queue::WeightInfo<Runtime>;
+	type PricingParameters = EthereumSystem;
+	type AssetTransactor = <xcm_config::XcmConfig as xcm_executor::Config>::AssetTransactor;
+}
+
+impl snowbridge_pallet_outbound_queue::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type Hashing = Keccak256;
+	type MessageQueue = MessageQueue;
+	type Decimals = ConstU8<12>;
+	type MaxMessagePayloadSize = ConstU32<2048>;
+	type MaxMessagesPerBlock = ConstU32<32>;
+	type GasMeter = snowbridge_core::outbound::ConstantGasMeter;
+	type Balance = Balance;
+	type WeightToFee = WeightToFee;
+	type WeightInfo = crate::weights::snowbridge_pallet_outbound_queue::WeightInfo<Runtime>;
+	type PricingParameters = EthereumSystem;
+	type Channels = EthereumSystem;
+}
+
+#[cfg(any(feature = "std", feature = "fast-runtime", feature = "runtime-benchmarks", test))]
+parameter_types! {
+	pub const ChainForkVersions: ForkVersions = ForkVersions {
+		genesis: Fork {
+			version: [0, 0, 0, 0], // 0x00000000
+			epoch: 0,
+		},
+		altair: Fork {
+			version: [1, 0, 0, 0], // 0x01000000
+			epoch: 0,
+		},
+		bellatrix: Fork {
+			version: [2, 0, 0, 0], // 0x02000000
+			epoch: 0,
+		},
+		capella: Fork {
+			version: [3, 0, 0, 0], // 0x03000000
+			epoch: 0,
+		},
+		deneb: Fork {
+			version: [4, 0, 0, 0], // 0x04000000
+			epoch: 0,
+		}
+	};
+}
+
+#[cfg(not(any(feature = "std", feature = "fast-runtime", feature = "runtime-benchmarks", test)))]
+parameter_types! {
+	pub const ChainForkVersions: ForkVersions = ForkVersions {
+		genesis: Fork {
+			version: [144, 0, 0, 111], // 0x90000069
+			epoch: 0,
+		},
+		altair: Fork {
+			version: [144, 0, 0, 112], // 0x90000070
+			epoch: 50,
+		},
+		bellatrix: Fork {
+			version: [144, 0, 0, 113], // 0x90000071
+			epoch: 100,
+		},
+		capella: Fork {
+			version: [144, 0, 0, 114], // 0x90000072
+			epoch: 56832,
+		},
+		deneb: Fork {
+			version: [144, 0, 0, 115], // 0x90000073
+			epoch: 132608,
+		},
+	};
+}
+
+impl snowbridge_pallet_ethereum_client::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type ForkVersions = ChainForkVersions;
+	type WeightInfo = crate::weights::snowbridge_pallet_ethereum_client::WeightInfo<Runtime>;
+}
+
+impl snowbridge_pallet_system::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type OutboundQueue = EthereumOutboundQueue;
+	type SiblingOrigin = EnsureXcm<AllowSiblingsOnly>;
+	type AgentIdOf = snowbridge_core::AgentIdOf;
+	type TreasuryAccount = TreasuryAccount;
+	type Token = Balances;
+	type WeightInfo = crate::weights::snowbridge_pallet_system::WeightInfo<Runtime>;
+	#[cfg(feature = "runtime-benchmarks")]
+	type Helper = ();
+	type DefaultPricingParameters = Parameters;
+	type InboundDeliveryCost = EthereumInboundQueue;
+}
+
+#[cfg(feature = "runtime-benchmarks")]
+pub mod benchmark_helpers {
+	use crate::{EthereumBeaconClient, Runtime, RuntimeOrigin};
+	use codec::Encode;
+	use snowbridge_beacon_primitives::BeaconHeader;
+	use snowbridge_pallet_inbound_queue::BenchmarkHelper;
+	use sp_core::H256;
+	use xcm::latest::{Assets, Location, SendError, SendResult, SendXcm, Xcm, XcmHash};
+
+	impl<T: snowbridge_pallet_ethereum_client::Config> BenchmarkHelper<T> for Runtime {
+		fn initialize_storage(beacon_header: BeaconHeader, block_roots_root: H256) {
+			EthereumBeaconClient::store_finalized_header(beacon_header, block_roots_root).unwrap();
+		}
+	}
+
+	pub struct DoNothingRouter;
+	impl SendXcm for DoNothingRouter {
+		type Ticket = Xcm<()>;
+
+		fn validate(
+			_dest: &mut Option<Location>,
+			xcm: &mut Option<Xcm<()>>,
+		) -> SendResult<Self::Ticket> {
+			Ok((xcm.clone().unwrap(), Assets::new()))
+		}
+		fn deliver(xcm: Xcm<()>) -> Result<XcmHash, SendError> {
+			let hash = xcm.using_encoded(sp_io::hashing::blake2_256);
+			Ok(hash)
+		}
+	}
+
+	impl snowbridge_pallet_system::BenchmarkHelper<RuntimeOrigin> for () {
+		fn make_xcm_origin(location: Location) -> RuntimeOrigin {
+			RuntimeOrigin::from(pallet_xcm::Origin::Xcm(location))
+		}
+	}
+}
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs
index d9f19afc629..7384bf2850f 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/lib.rs
@@ -28,6 +28,7 @@
 include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
 
 pub mod bridge_common_config;
+pub mod bridge_to_ethereum_config;
 pub mod bridge_to_rococo_config;
 mod weights;
 pub mod xcm_config;
@@ -96,7 +97,12 @@ use parachains_common::{
 	impls::DealWithFees, AccountId, Balance, BlockNumber, Hash, Header, Nonce, Signature,
 	AVERAGE_ON_INITIALIZE_RATIO, NORMAL_DISPATCH_RATIO,
 };
+use snowbridge_core::{
+	outbound::{Command, Fee},
+	AgentId, PricingParameters,
+};
 use testnet_parachains_constants::westend::{consensus::*, currency::*, fee::WeightToFee, time::*};
+use xcm::VersionedLocation;
 
 /// The address format for describing accounts.
 pub type Address = MultiAddress<AccountId, ()>;
@@ -123,6 +129,7 @@ pub type SignedExtra = (
 	BridgeRejectObsoleteHeadersAndMessages,
 	(bridge_to_rococo_config::OnBridgeHubWestendRefundBridgeHubRococoMessages,),
 	cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim<Runtime>,
+	frame_metadata_hash_extension::CheckMetadataHash<Runtime>,
 );
 
 /// Unchecked extrinsic type as expected by this runtime.
@@ -351,10 +358,13 @@ impl pallet_message_queue::Config for Runtime {
 	type MessageProcessor =
 		pallet_message_queue::mock_helpers::NoopMessageProcessor<AggregateMessageOrigin>;
 	#[cfg(not(feature = "runtime-benchmarks"))]
-	type MessageProcessor = xcm_builder::ProcessXcmMessage<
-		AggregateMessageOrigin,
-		xcm_executor::XcmExecutor<xcm_config::XcmConfig>,
-		RuntimeCall,
+	type MessageProcessor = bridge_hub_common::BridgeHubMessageRouter<
+		xcm_builder::ProcessXcmMessage<
+			AggregateMessageOrigin,
+			xcm_executor::XcmExecutor<xcm_config::XcmConfig>,
+			RuntimeCall,
+		>,
+		EthereumOutboundQueue,
 	>;
 	type Size = u32;
 	// The XCMP queue pallet is only ever able to handle the `Sibling(ParaId)` origin:
@@ -516,6 +526,11 @@ construct_runtime!(
 		BridgeRococoMessages: pallet_bridge_messages::<Instance1> = 44,
 		XcmOverBridgeHubRococo: pallet_xcm_bridge_hub::<Instance1> = 45,
 
+		EthereumInboundQueue: snowbridge_pallet_inbound_queue = 80,
+		EthereumOutboundQueue: snowbridge_pallet_outbound_queue = 81,
+		EthereumBeaconClient: snowbridge_pallet_ethereum_client = 82,
+		EthereumSystem: snowbridge_pallet_system = 83,
+
 		// Message Queue. Importantly, is registered last so that messages are processed after
 		// the `on_initialize` hooks of bridging pallets.
 		MessageQueue: pallet_message_queue = 250,
@@ -568,6 +583,11 @@ mod benches {
 		[pallet_bridge_grandpa, RococoFinality]
 		[pallet_bridge_parachains, WithinRococo]
 		[pallet_bridge_messages, WestendToRococo]
+		// Ethereum Bridge
+		[snowbridge_pallet_inbound_queue, EthereumInboundQueue]
+		[snowbridge_pallet_outbound_queue, EthereumOutboundQueue]
+		[snowbridge_pallet_system, EthereumSystem]
+		[snowbridge_pallet_ethereum_client, EthereumBeaconClient]
 	);
 }
 
@@ -830,6 +850,22 @@ impl_runtime_apis! {
 		}
 	}
 
+	impl snowbridge_outbound_queue_runtime_api::OutboundQueueApi<Block, Balance> for Runtime {
+		fn prove_message(leaf_index: u64) -> Option<snowbridge_pallet_outbound_queue::MerkleProof> {
+			snowbridge_pallet_outbound_queue::api::prove_message::<Runtime>(leaf_index)
+		}
+
+		fn calculate_fee(command: Command, parameters: Option<PricingParameters<Balance>>) -> Fee<Balance> {
+			snowbridge_pallet_outbound_queue::api::calculate_fee::<Runtime>(command, parameters)
+		}
+	}
+
+	impl snowbridge_system_runtime_api::ControlApi<Block> for Runtime {
+		fn agent_id(location: VersionedLocation) -> Option<AgentId> {
+			snowbridge_pallet_system::api::agent_id::<Runtime>(location)
+		}
+	}
+
 	#[cfg(feature = "try-runtime")]
 	impl frame_try_runtime::TryRuntime<Block> for Runtime {
 		fn on_runtime_upgrade(checks: frame_try_runtime::UpgradeCheckSelect) -> (Weight, Weight) {
@@ -1259,7 +1295,8 @@ mod tests {
 				(
 					bridge_to_rococo_config::OnBridgeHubWestendRefundBridgeHubRococoMessages::default(),
 				),
-				cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::new()
+				cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::new(),
+				frame_metadata_hash_extension::CheckMetadataHash::new(false),
 			);
 
 			{
@@ -1272,9 +1309,9 @@ mod tests {
 					10,
 					(((), ()), ((), ())),
 				);
-				assert_eq!(payload.encode(), bh_indirect_payload.encode());
+				assert_eq!(payload.encode().split_last().unwrap().1, bh_indirect_payload.encode());
 				assert_eq!(
-					payload.additional_signed().unwrap().encode(),
+					payload.additional_signed().unwrap().encode().split_last().unwrap().1,
 					bh_indirect_payload.additional_signed().unwrap().encode()
 				)
 			}
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs
index 245daaf8ed9..9b7f7188782 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/mod.rs
@@ -43,6 +43,11 @@ pub mod paritydb_weights;
 pub mod rocksdb_weights;
 pub mod xcm;
 
+pub mod snowbridge_pallet_ethereum_client;
+pub mod snowbridge_pallet_inbound_queue;
+pub mod snowbridge_pallet_outbound_queue;
+pub mod snowbridge_pallet_system;
+
 pub use block_weights::constants::BlockExecutionWeight;
 pub use extrinsic_weights::constants::ExtrinsicBaseWeight;
 pub use rocksdb_weights::constants::RocksDbWeight;
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_ethereum_client.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_ethereum_client.rs
new file mode 100644
index 00000000000..23e2a9cffb0
--- /dev/null
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_ethereum_client.rs
@@ -0,0 +1,120 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Autogenerated weights for `snowbridge_pallet_ethereum_client`
+//!
+//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 32.0.0
+//! DATE: 2024-06-06, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! WORST CASE MAP SIZE: `1000000`
+//! HOSTNAME: `Claras-MacBook-Pro-2.local`, CPU: `<UNKNOWN>`
+//! WASM-EXECUTION: `Compiled`, CHAIN: `Some("bridge-hub-rococo-dev")`, DB CACHE: 1024
+
+// Executed Command:
+// target/release/polkadot-parachain
+// benchmark
+// pallet
+// --chain=bridge-hub-rococo-dev
+// --pallet=snowbridge_pallet_ethereum_client
+// --extrinsic
+// *
+// --wasm-execution=compiled
+// --steps
+// 50
+// --repeat
+// 20
+// --output
+// cumulus/parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_pallet_ethereum_client.rs
+
+#![cfg_attr(rustfmt, rustfmt_skip)]
+#![allow(unused_parens)]
+#![allow(unused_imports)]
+#![allow(missing_docs)]
+
+use frame_support::{traits::Get, weights::Weight};
+use core::marker::PhantomData;
+
+/// Weight functions for `snowbridge_pallet_ethereum_client`.
+pub struct WeightInfo<T>(PhantomData<T>);
+impl<T: frame_system::Config> snowbridge_pallet_ethereum_client::WeightInfo for WeightInfo<T> {
+	/// Storage: `EthereumBeaconClient::FinalizedBeaconStateIndex` (r:1 w:1)
+	/// Proof: `EthereumBeaconClient::FinalizedBeaconStateIndex` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
+	/// Storage: `EthereumBeaconClient::FinalizedBeaconStateMapping` (r:1 w:1)
+	/// Proof: `EthereumBeaconClient::FinalizedBeaconStateMapping` (`max_values`: None, `max_size`: Some(36), added: 2511, mode: `MaxEncodedLen`)
+	/// Storage: `EthereumBeaconClient::NextSyncCommittee` (r:0 w:1)
+	/// Proof: `EthereumBeaconClient::NextSyncCommittee` (`max_values`: Some(1), `max_size`: Some(92372), added: 92867, mode: `MaxEncodedLen`)
+	/// Storage: `EthereumBeaconClient::InitialCheckpointRoot` (r:0 w:1)
+	/// Proof: `EthereumBeaconClient::InitialCheckpointRoot` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
+	/// Storage: `EthereumBeaconClient::ValidatorsRoot` (r:0 w:1)
+	/// Proof: `EthereumBeaconClient::ValidatorsRoot` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
+	/// Storage: `EthereumBeaconClient::LatestFinalizedBlockRoot` (r:0 w:1)
+	/// Proof: `EthereumBeaconClient::LatestFinalizedBlockRoot` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
+	/// Storage: `EthereumBeaconClient::CurrentSyncCommittee` (r:0 w:1)
+	/// Proof: `EthereumBeaconClient::CurrentSyncCommittee` (`max_values`: Some(1), `max_size`: Some(92372), added: 92867, mode: `MaxEncodedLen`)
+	/// Storage: `EthereumBeaconClient::FinalizedBeaconState` (r:0 w:1)
+	/// Proof: `EthereumBeaconClient::FinalizedBeaconState` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
+	fn force_checkpoint() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `76`
+		//  Estimated: `3501`
+		// Minimum execution time: 67_553_000_000 picoseconds.
+		Weight::from_parts(68_677_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 3501))
+			.saturating_add(T::DbWeight::get().reads(2))
+			.saturating_add(T::DbWeight::get().writes(8))
+	}
+	/// Storage: `EthereumBeaconClient::OperatingMode` (r:1 w:0)
+	/// Proof: `EthereumBeaconClient::OperatingMode` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`)
+	/// Storage: `EthereumBeaconClient::LatestFinalizedBlockRoot` (r:1 w:0)
+	/// Proof: `EthereumBeaconClient::LatestFinalizedBlockRoot` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
+	/// Storage: `EthereumBeaconClient::FinalizedBeaconState` (r:1 w:0)
+	/// Proof: `EthereumBeaconClient::FinalizedBeaconState` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
+	/// Storage: `EthereumBeaconClient::NextSyncCommittee` (r:1 w:0)
+	/// Proof: `EthereumBeaconClient::NextSyncCommittee` (`max_values`: Some(1), `max_size`: Some(92372), added: 92867, mode: `MaxEncodedLen`)
+	/// Storage: `EthereumBeaconClient::CurrentSyncCommittee` (r:1 w:0)
+	/// Proof: `EthereumBeaconClient::CurrentSyncCommittee` (`max_values`: Some(1), `max_size`: Some(92372), added: 92867, mode: `MaxEncodedLen`)
+	/// Storage: `EthereumBeaconClient::ValidatorsRoot` (r:1 w:0)
+	/// Proof: `EthereumBeaconClient::ValidatorsRoot` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
+	fn submit() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `92749`
+		//  Estimated: `93857`
+		// Minimum execution time: 16_988_000_000 picoseconds.
+		Weight::from_parts(17_125_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 93857))
+			.saturating_add(T::DbWeight::get().reads(6))
+	}
+	/// Storage: `EthereumBeaconClient::OperatingMode` (r:1 w:0)
+	/// Proof: `EthereumBeaconClient::OperatingMode` (`max_values`: Some(1), `max_size`: Some(1), added: 496, mode: `MaxEncodedLen`)
+	/// Storage: `EthereumBeaconClient::LatestFinalizedBlockRoot` (r:1 w:0)
+	/// Proof: `EthereumBeaconClient::LatestFinalizedBlockRoot` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
+	/// Storage: `EthereumBeaconClient::FinalizedBeaconState` (r:1 w:0)
+	/// Proof: `EthereumBeaconClient::FinalizedBeaconState` (`max_values`: None, `max_size`: Some(72), added: 2547, mode: `MaxEncodedLen`)
+	/// Storage: `EthereumBeaconClient::NextSyncCommittee` (r:1 w:1)
+	/// Proof: `EthereumBeaconClient::NextSyncCommittee` (`max_values`: Some(1), `max_size`: Some(92372), added: 92867, mode: `MaxEncodedLen`)
+	/// Storage: `EthereumBeaconClient::CurrentSyncCommittee` (r:1 w:0)
+	/// Proof: `EthereumBeaconClient::CurrentSyncCommittee` (`max_values`: Some(1), `max_size`: Some(92372), added: 92867, mode: `MaxEncodedLen`)
+	/// Storage: `EthereumBeaconClient::ValidatorsRoot` (r:1 w:0)
+	/// Proof: `EthereumBeaconClient::ValidatorsRoot` (`max_values`: Some(1), `max_size`: Some(32), added: 527, mode: `MaxEncodedLen`)
+	fn submit_with_sync_committee() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `92749`
+		//  Estimated: `93857`
+		// Minimum execution time: 84_553_000_000 picoseconds.
+		Weight::from_parts(87_459_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 93857))
+			.saturating_add(T::DbWeight::get().reads(6))
+			.saturating_add(T::DbWeight::get().writes(1))
+	}
+}
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_inbound_queue.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_inbound_queue.rs
new file mode 100644
index 00000000000..153c1d363be
--- /dev/null
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_inbound_queue.rs
@@ -0,0 +1,69 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Autogenerated weights for `snowbridge_pallet_inbound_queue`
+//!
+//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
+//! DATE: 2023-09-06, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! WORST CASE MAP SIZE: `1000000`
+//! HOSTNAME: `macbook pro 14 m2`, CPU: `m2-arm64`
+//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("bridge-hub-rococo-dev"), DB CACHE: 1024
+
+// Executed Command:
+// target/release/polkadot-parachain
+// benchmark
+// pallet
+// --chain=bridge-hub-rococo-dev
+// --pallet=snowbridge_inbound_queue
+// --extrinsic=*
+// --execution=wasm
+// --wasm-execution=compiled
+// --steps
+// 50
+// --repeat
+// 20
+// --output
+// ./parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_inbound_queue.rs
+
+#![cfg_attr(rustfmt, rustfmt_skip)]
+#![allow(unused_parens)]
+#![allow(unused_imports)]
+#![allow(missing_docs)]
+
+use frame_support::{traits::Get, weights::Weight};
+use core::marker::PhantomData;
+
+/// Weight functions for `snowbridge_pallet_inbound_queue`.
+pub struct WeightInfo<T>(PhantomData<T>);
+impl<T: frame_system::Config> snowbridge_pallet_inbound_queue::WeightInfo for WeightInfo<T> {
+	/// Storage: EthereumInboundQueue PalletOperatingMode (r:1 w:0)
+	/// Proof: EthereumInboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen)
+	/// Storage: EthereumBeaconClient ExecutionHeaders (r:1 w:0)
+	/// Proof: EthereumBeaconClient ExecutionHeaders (max_values: None, max_size: Some(136), added: 2611, mode: MaxEncodedLen)
+	/// Storage: EthereumInboundQueue Nonce (r:1 w:1)
+	/// Proof: EthereumInboundQueue Nonce (max_values: None, max_size: Some(20), added: 2495, mode: MaxEncodedLen)
+	/// Storage: System Account (r:1 w:1)
+	/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
+	fn submit() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `800`
+		//  Estimated: `7200`
+		// Minimum execution time: 200_000_000 picoseconds.
+		Weight::from_parts(200_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 7200))
+			.saturating_add(T::DbWeight::get().reads(9))
+			.saturating_add(T::DbWeight::get().writes(6))
+	}
+}
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_outbound_queue.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_outbound_queue.rs
new file mode 100644
index 00000000000..8adcef076e0
--- /dev/null
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_outbound_queue.rs
@@ -0,0 +1,87 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Autogenerated weights for `snowbridge_outbound_queue`
+//!
+//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
+//! DATE: 2023-10-20, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! WORST CASE MAP SIZE: `1000000`
+//! HOSTNAME: `192.168.1.13`, CPU: `<UNKNOWN>`
+//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("bridge-hub-rococo-dev"), DB CACHE: 1024
+
+// Executed Command:
+// ../target/release/polkadot-parachain
+// benchmark
+// pallet
+// --chain=bridge-hub-rococo-dev
+// --pallet=snowbridge_outbound_queue
+// --extrinsic=*
+// --execution=wasm
+// --wasm-execution=compiled
+// --output
+// ../parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_outbound_queue.rs
+
+#![cfg_attr(rustfmt, rustfmt_skip)]
+#![allow(unused_parens)]
+#![allow(unused_imports)]
+#![allow(missing_docs)]
+
+use frame_support::{traits::Get, weights::Weight};
+use core::marker::PhantomData;
+
+/// Weight functions for `snowbridge_outbound_queue`.
+pub struct WeightInfo<T>(PhantomData<T>);
+impl<T: frame_system::Config> snowbridge_pallet_outbound_queue::WeightInfo for WeightInfo<T> {
+	/// Storage: EthereumOutboundQueue MessageLeaves (r:1 w:1)
+	/// Proof Skipped: EthereumOutboundQueue MessageLeaves (max_values: Some(1), max_size: None, mode: Measured)
+	/// Storage: EthereumOutboundQueue PendingHighPriorityMessageCount (r:1 w:1)
+	/// Proof: EthereumOutboundQueue PendingHighPriorityMessageCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
+	/// Storage: EthereumOutboundQueue Nonce (r:1 w:1)
+	/// Proof: EthereumOutboundQueue Nonce (max_values: None, max_size: Some(20), added: 2495, mode: MaxEncodedLen)
+	/// Storage: EthereumOutboundQueue Messages (r:1 w:1)
+	/// Proof Skipped: EthereumOutboundQueue Messages (max_values: Some(1), max_size: None, mode: Measured)
+	fn do_process_message() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `42`
+		//  Estimated: `3485`
+		// Minimum execution time: 39_000_000 picoseconds.
+		Weight::from_parts(39_000_000, 3485)
+			.saturating_add(T::DbWeight::get().reads(4_u64))
+			.saturating_add(T::DbWeight::get().writes(4_u64))
+	}
+	/// Storage: EthereumOutboundQueue MessageLeaves (r:1 w:0)
+	/// Proof Skipped: EthereumOutboundQueue MessageLeaves (max_values: Some(1), max_size: None, mode: Measured)
+	/// Storage: System Digest (r:1 w:1)
+	/// Proof Skipped: System Digest (max_values: Some(1), max_size: None, mode: Measured)
+	fn commit() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `1094`
+		//  Estimated: `2579`
+		// Minimum execution time: 28_000_000 picoseconds.
+		Weight::from_parts(28_000_000, 2579)
+			.saturating_add(T::DbWeight::get().reads(2_u64))
+			.saturating_add(T::DbWeight::get().writes(1_u64))
+	}
+
+	fn commit_single() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `1094`
+		//  Estimated: `2579`
+		// Minimum execution time: 9_000_000 picoseconds.
+		Weight::from_parts(9_000_000, 1586)
+			.saturating_add(T::DbWeight::get().reads(2_u64))
+			.saturating_add(T::DbWeight::get().writes(1_u64))
+	}
+}
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_system.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_system.rs
new file mode 100644
index 00000000000..c6c188e323a
--- /dev/null
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/weights/snowbridge_pallet_system.rs
@@ -0,0 +1,256 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// SPDX-License-Identifier: Apache-2.0
+
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// 	http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Autogenerated weights for `snowbridge_system`
+//!
+//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
+//! DATE: 2023-10-09, STEPS: `2`, REPEAT: `1`, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! WORST CASE MAP SIZE: `1000000`
+//! HOSTNAME: `crake.local`, CPU: `<UNKNOWN>`
+//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("bridge-hub-rococo-dev"), DB CACHE: 1024
+
+// Executed Command:
+// target/release/polkadot-parachain
+// benchmark
+// pallet
+// --chain
+// bridge-hub-rococo-dev
+// --pallet=snowbridge_pallet_system
+// --extrinsic=*
+// --execution=wasm
+// --wasm-execution=compiled
+// --output
+// parachains/runtimes/bridge-hubs/bridge-hub-rococo/src/weights/snowbridge_pallet_system.rs
+
+#![cfg_attr(rustfmt, rustfmt_skip)]
+#![allow(unused_parens)]
+#![allow(unused_imports)]
+#![allow(missing_docs)]
+
+use frame_support::{traits::Get, weights::Weight};
+use core::marker::PhantomData;
+
+/// Weight functions for `snowbridge_system`.
+pub struct WeightInfo<T>(PhantomData<T>);
+impl<T: frame_system::Config> snowbridge_pallet_system::WeightInfo for WeightInfo<T> {
+	/// Storage: ParachainInfo ParachainId (r:1 w:0)
+	/// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
+	/// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0)
+	/// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen)
+	/// Storage: MessageQueue BookStateFor (r:1 w:1)
+	/// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen)
+	/// Storage: MessageQueue ServiceHead (r:1 w:1)
+	/// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen)
+	/// Storage: MessageQueue Pages (r:0 w:1)
+	/// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen)
+	fn upgrade() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `80`
+		//  Estimated: `3517`
+		// Minimum execution time: 47_000_000 picoseconds.
+		Weight::from_parts(47_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 3517))
+			.saturating_add(T::DbWeight::get().reads(4))
+			.saturating_add(T::DbWeight::get().writes(3))
+	}
+	/// Storage: EthereumSystem Agents (r:1 w:1)
+	/// Proof: EthereumSystem Agents (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen)
+	/// Storage: System Account (r:2 w:2)
+	/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
+	/// Storage: ParachainInfo ParachainId (r:1 w:0)
+	/// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
+	/// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0)
+	/// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen)
+	/// Storage: MessageQueue BookStateFor (r:1 w:1)
+	/// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen)
+	/// Storage: MessageQueue ServiceHead (r:1 w:1)
+	/// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen)
+	/// Storage: MessageQueue Pages (r:0 w:1)
+	/// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen)
+	fn create_agent() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `187`
+		//  Estimated: `6196`
+		// Minimum execution time: 87_000_000 picoseconds.
+		Weight::from_parts(87_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 6196))
+			.saturating_add(T::DbWeight::get().reads(7))
+			.saturating_add(T::DbWeight::get().writes(6))
+	}
+	/// Storage: System Account (r:2 w:2)
+	/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
+	/// Storage: EthereumSystem Agents (r:1 w:0)
+	/// Proof: EthereumSystem Agents (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen)
+	/// Storage: EthereumSystem Channels (r:1 w:1)
+	/// Proof: EthereumSystem Channels (max_values: None, max_size: Some(12), added: 2487, mode: MaxEncodedLen)
+	/// Storage: ParachainInfo ParachainId (r:1 w:0)
+	/// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
+	/// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0)
+	/// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen)
+	/// Storage: MessageQueue BookStateFor (r:1 w:1)
+	/// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen)
+	/// Storage: MessageQueue Pages (r:1 w:1)
+	/// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen)
+	fn create_channel() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `602`
+		//  Estimated: `69050`
+		// Minimum execution time: 84_000_000 picoseconds.
+		Weight::from_parts(84_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 69050))
+			.saturating_add(T::DbWeight::get().reads(8))
+			.saturating_add(T::DbWeight::get().writes(5))
+	}
+	/// Storage: EthereumSystem Channels (r:1 w:0)
+	/// Proof: EthereumSystem Channels (max_values: None, max_size: Some(12), added: 2487, mode: MaxEncodedLen)
+	/// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0)
+	/// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen)
+	/// Storage: MessageQueue BookStateFor (r:2 w:2)
+	/// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen)
+	/// Storage: MessageQueue ServiceHead (r:1 w:0)
+	/// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen)
+	/// Storage: MessageQueue Pages (r:0 w:1)
+	/// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen)
+	fn update_channel() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `256`
+		//  Estimated: `6044`
+		// Minimum execution time: 41_000_000 picoseconds.
+		Weight::from_parts(41_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 6044))
+			.saturating_add(T::DbWeight::get().reads(5))
+			.saturating_add(T::DbWeight::get().writes(3))
+	}
+	/// Storage: EthereumSystem Channels (r:1 w:0)
+	/// Proof: EthereumSystem Channels (max_values: None, max_size: Some(12), added: 2487, mode: MaxEncodedLen)
+	/// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0)
+	/// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen)
+	/// Storage: MessageQueue BookStateFor (r:2 w:2)
+	/// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen)
+	/// Storage: MessageQueue ServiceHead (r:1 w:0)
+	/// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen)
+	/// Storage: MessageQueue Pages (r:0 w:1)
+	/// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen)
+	fn force_update_channel() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `256`
+		//  Estimated: `6044`
+		// Minimum execution time: 41_000_000 picoseconds.
+		Weight::from_parts(41_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 6044))
+			.saturating_add(T::DbWeight::get().reads(5))
+			.saturating_add(T::DbWeight::get().writes(3))
+	}
+	/// Storage: ParachainInfo ParachainId (r:1 w:0)
+	/// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
+	/// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0)
+	/// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen)
+	/// Storage: MessageQueue BookStateFor (r:1 w:1)
+	/// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen)
+	/// Storage: MessageQueue ServiceHead (r:1 w:1)
+	/// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen)
+	/// Storage: MessageQueue Pages (r:0 w:1)
+	/// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen)
+	fn set_operating_mode() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `80`
+		//  Estimated: `3517`
+		// Minimum execution time: 30_000_000 picoseconds.
+		Weight::from_parts(30_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 3517))
+			.saturating_add(T::DbWeight::get().reads(4))
+			.saturating_add(T::DbWeight::get().writes(3))
+	}
+	/// Storage: EthereumSystem Agents (r:1 w:0)
+	/// Proof: EthereumSystem Agents (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen)
+	/// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0)
+	/// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen)
+	/// Storage: MessageQueue BookStateFor (r:2 w:2)
+	/// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen)
+	/// Storage: MessageQueue ServiceHead (r:1 w:0)
+	/// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen)
+	/// Storage: MessageQueue Pages (r:0 w:1)
+	/// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen)
+	fn transfer_native_from_agent() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `252`
+		//  Estimated: `6044`
+		// Minimum execution time: 43_000_000 picoseconds.
+		Weight::from_parts(43_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 6044))
+			.saturating_add(T::DbWeight::get().reads(5))
+			.saturating_add(T::DbWeight::get().writes(3))
+	}
+	/// Storage: EthereumSystem Agents (r:1 w:0)
+	/// Proof: EthereumSystem Agents (max_values: None, max_size: Some(40), added: 2515, mode: MaxEncodedLen)
+	/// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0)
+	/// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen)
+	/// Storage: MessageQueue BookStateFor (r:2 w:2)
+	/// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen)
+	/// Storage: MessageQueue ServiceHead (r:1 w:0)
+	/// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen)
+	/// Storage: MessageQueue Pages (r:0 w:1)
+	/// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen)
+	fn force_transfer_native_from_agent() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `252`
+		//  Estimated: `6044`
+		// Minimum execution time: 42_000_000 picoseconds.
+		Weight::from_parts(42_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 6044))
+			.saturating_add(T::DbWeight::get().reads(5))
+			.saturating_add(T::DbWeight::get().writes(3))
+	}
+
+	/// Storage: ParachainInfo ParachainId (r:1 w:0)
+	/// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
+	/// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0)
+	/// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen)
+	/// Storage: MessageQueue BookStateFor (r:1 w:1)
+	/// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen)
+	/// Storage: MessageQueue ServiceHead (r:1 w:1)
+	/// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen)
+	/// Storage: MessageQueue Pages (r:0 w:1)
+	/// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen)
+	fn set_token_transfer_fees() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `80`
+		//  Estimated: `3517`
+		// Minimum execution time: 31_000_000 picoseconds.
+		Weight::from_parts(42_000_000, 3517)
+			.saturating_add(T::DbWeight::get().reads(4_u64))
+			.saturating_add(T::DbWeight::get().writes(3_u64))
+	}
+
+	/// Storage: ParachainInfo ParachainId (r:1 w:0)
+	/// Proof: ParachainInfo ParachainId (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
+	/// Storage: EthereumOutboundQueue PalletOperatingMode (r:1 w:0)
+	/// Proof: EthereumOutboundQueue PalletOperatingMode (max_values: Some(1), max_size: Some(1), added: 496, mode: MaxEncodedLen)
+	/// Storage: MessageQueue BookStateFor (r:1 w:1)
+	/// Proof: MessageQueue BookStateFor (max_values: None, max_size: Some(52), added: 2527, mode: MaxEncodedLen)
+	/// Storage: MessageQueue ServiceHead (r:1 w:1)
+	/// Proof: MessageQueue ServiceHead (max_values: Some(1), max_size: Some(5), added: 500, mode: MaxEncodedLen)
+	/// Storage: MessageQueue Pages (r:0 w:1)
+	/// Proof: MessageQueue Pages (max_values: None, max_size: Some(65585), added: 68060, mode: MaxEncodedLen)
+	fn set_pricing_parameters() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `80`
+		//  Estimated: `3517`
+		// Minimum execution time: 31_000_000 picoseconds.
+		Weight::from_parts(42_000_000, 3517)
+			.saturating_add(T::DbWeight::get().reads(4_u64))
+			.saturating_add(T::DbWeight::get().writes(3_u64))
+	}
+}
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs
index 7f94b76a005..81705ee2dc9 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/src/xcm_config.rs
@@ -35,19 +35,24 @@ use parachains_common::{
 };
 use polkadot_parachain_primitives::primitives::Sibling;
 use polkadot_runtime_common::xcm_sender::ExponentialPrice;
+use snowbridge_runtime_common::XcmExportFeeToSibling;
 use sp_runtime::traits::AccountIdConversion;
+use sp_std::marker::PhantomData;
+use testnet_parachains_constants::westend::snowbridge::EthereumNetwork;
 use xcm::latest::prelude::*;
 use xcm_builder::{
 	AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowHrmpNotificationsFromRelayChain,
 	AllowKnownQueryResponses, AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom,
 	DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FrameTransactionalProcessor,
-	FungibleAdapter, IsConcrete, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative,
+	FungibleAdapter, HandleFee, IsConcrete, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative,
 	SendXcmFeeToAccount, SiblingParachainAsNative, SiblingParachainConvertsVia,
 	SignedAccountId32AsNative, SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit,
 	TrailingSetTopicAsId, UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic,
-	XcmFeeManagerFromComponents,
 };
-use xcm_executor::XcmExecutor;
+use xcm_executor::{
+	traits::{FeeManager, FeeReason, FeeReason::Export},
+	XcmExecutor,
+};
 
 parameter_types! {
 	pub const WestendLocation: Location = Location::parent();
@@ -193,11 +198,24 @@ impl xcm_executor::Config for XcmConfig {
 	type SubscriptionService = PolkadotXcm;
 	type PalletInstancesInfo = AllPalletsWithSystem;
 	type MaxAssetsIntoHolding = MaxAssetsIntoHolding;
-	type FeeManager = XcmFeeManagerFromComponents<
+	type FeeManager = XcmFeeManagerFromComponentsBridgeHub<
 		WaivedLocations,
-		SendXcmFeeToAccount<Self::AssetTransactor, TreasuryAccount>,
+		(
+			XcmExportFeeToSibling<
+				bp_westend::Balance,
+				AccountId,
+				WestendLocation,
+				EthereumNetwork,
+				Self::AssetTransactor,
+				crate::EthereumOutboundQueue,
+			>,
+			SendXcmFeeToAccount<Self::AssetTransactor, TreasuryAccount>,
+		),
 	>;
-	type MessageExporter = (crate::bridge_to_rococo_config::ToBridgeHubRococoHaulBlobExporter,);
+	type MessageExporter = (
+		crate::bridge_to_rococo_config::ToBridgeHubRococoHaulBlobExporter,
+		crate::bridge_to_ethereum_config::SnowbridgeExporter,
+	);
 	type UniversalAliases = Nothing;
 	type CallDispatcher = RuntimeCall;
 	type SafeCallFilter = Everything;
@@ -261,3 +279,24 @@ impl cumulus_pallet_xcm::Config for Runtime {
 	type RuntimeEvent = RuntimeEvent;
 	type XcmExecutor = XcmExecutor<XcmConfig>;
 }
+
+pub struct XcmFeeManagerFromComponentsBridgeHub<WaivedLocations, HandleFee>(
+	PhantomData<(WaivedLocations, HandleFee)>,
+);
+impl<WaivedLocations: Contains<Location>, FeeHandler: HandleFee> FeeManager
+	for XcmFeeManagerFromComponentsBridgeHub<WaivedLocations, FeeHandler>
+{
+	fn is_waived(origin: Option<&Location>, fee_reason: FeeReason) -> bool {
+		let Some(loc) = origin else { return false };
+		if let Export { network, destination: Here } = fee_reason {
+			if network == EthereumNetwork::get() {
+				return false
+			}
+		}
+		WaivedLocations::contains(loc)
+	}
+
+	fn handle_fee(fee: Assets, context: Option<&XcmContext>, reason: FeeReason) {
+		FeeHandler::handle_fee(fee, context, reason);
+	}
+}
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs
new file mode 100644
index 00000000000..46a0fa7a664
--- /dev/null
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/snowbridge.rs
@@ -0,0 +1,202 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// This file is part of Cumulus.
+
+// Cumulus 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.
+
+// Cumulus 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 Cumulus.  If not, see <http://www.gnu.org/licenses/>.
+
+#![cfg(test)]
+
+use bp_asset_hub_westend::ASSET_HUB_WESTEND_PARACHAIN_ID;
+use bp_bridge_hub_westend::BRIDGE_HUB_WESTEND_PARACHAIN_ID;
+use bp_polkadot_core::Signature;
+use bridge_hub_westend_runtime::{
+	bridge_to_rococo_config, xcm_config::XcmConfig, AllPalletsWithoutSystem,
+	BridgeRejectObsoleteHeadersAndMessages, Executive, MessageQueueServiceWeight, Runtime,
+	RuntimeCall, RuntimeEvent, SessionKeys, SignedExtra, UncheckedExtrinsic,
+};
+use codec::{Decode, Encode};
+use cumulus_primitives_core::XcmError::{FailedToTransactAsset, NotHoldingFees};
+use frame_support::parameter_types;
+use parachains_common::{AccountId, AuraId, Balance};
+use snowbridge_pallet_ethereum_client::WeightInfo;
+use sp_core::H160;
+use sp_keyring::AccountKeyring::Alice;
+use sp_runtime::{
+	generic::{Era, SignedPayload},
+	AccountId32,
+};
+
+parameter_types! {
+		pub const DefaultBridgeHubEthereumBaseFee: Balance = 2_750_872_500_000;
+}
+
+fn collator_session_keys() -> bridge_hub_test_utils::CollatorSessionKeys<Runtime> {
+	bridge_hub_test_utils::CollatorSessionKeys::new(
+		AccountId::from(Alice),
+		AccountId::from(Alice),
+		SessionKeys { aura: AuraId::from(Alice.public()) },
+	)
+}
+
+#[test]
+pub fn transfer_token_to_ethereum_works() {
+	snowbridge_runtime_test_common::send_transfer_token_message_success::<Runtime, XcmConfig>(
+		11155111,
+		collator_session_keys(),
+		BRIDGE_HUB_WESTEND_PARACHAIN_ID,
+		ASSET_HUB_WESTEND_PARACHAIN_ID,
+		H160::random(),
+		H160::random(),
+		DefaultBridgeHubEthereumBaseFee::get(),
+		Box::new(|runtime_event_encoded: Vec<u8>| {
+			match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) {
+				Ok(RuntimeEvent::EthereumOutboundQueue(event)) => Some(event),
+				_ => None,
+			}
+		}),
+	)
+}
+
+#[test]
+pub fn unpaid_transfer_token_to_ethereum_fails_with_barrier() {
+	snowbridge_runtime_test_common::send_unpaid_transfer_token_message::<Runtime, XcmConfig>(
+		11155111,
+		collator_session_keys(),
+		BRIDGE_HUB_WESTEND_PARACHAIN_ID,
+		ASSET_HUB_WESTEND_PARACHAIN_ID,
+		H160::random(),
+		H160::random(),
+	)
+}
+
+#[test]
+pub fn transfer_token_to_ethereum_fee_not_enough() {
+	snowbridge_runtime_test_common::send_transfer_token_message_failure::<Runtime, XcmConfig>(
+		11155111,
+		collator_session_keys(),
+		BRIDGE_HUB_WESTEND_PARACHAIN_ID,
+		ASSET_HUB_WESTEND_PARACHAIN_ID,
+		DefaultBridgeHubEthereumBaseFee::get() + 10_000_000_000,
+		H160::random(),
+		H160::random(),
+		// fee not enough
+		10_000_000_000,
+		NotHoldingFees,
+	)
+}
+
+#[test]
+pub fn transfer_token_to_ethereum_insufficient_fund() {
+	snowbridge_runtime_test_common::send_transfer_token_message_failure::<Runtime, XcmConfig>(
+		11155111,
+		collator_session_keys(),
+		BRIDGE_HUB_WESTEND_PARACHAIN_ID,
+		ASSET_HUB_WESTEND_PARACHAIN_ID,
+		1_000_000_000,
+		H160::random(),
+		H160::random(),
+		DefaultBridgeHubEthereumBaseFee::get(),
+		FailedToTransactAsset("Funds are unavailable"),
+	)
+}
+
+#[test]
+fn max_message_queue_service_weight_is_more_than_beacon_extrinsic_weights() {
+	let max_message_queue_weight = MessageQueueServiceWeight::get();
+	let force_checkpoint =
+		<Runtime as snowbridge_pallet_ethereum_client::Config>::WeightInfo::force_checkpoint();
+	let submit_checkpoint =
+		<Runtime as snowbridge_pallet_ethereum_client::Config>::WeightInfo::submit();
+	max_message_queue_weight.all_gt(force_checkpoint);
+	max_message_queue_weight.all_gt(submit_checkpoint);
+}
+
+#[test]
+fn ethereum_client_consensus_extrinsics_work() {
+	snowbridge_runtime_test_common::ethereum_extrinsic(
+		collator_session_keys(),
+		BRIDGE_HUB_WESTEND_PARACHAIN_ID,
+		construct_and_apply_extrinsic,
+	);
+}
+
+#[test]
+fn ethereum_to_polkadot_message_extrinsics_work() {
+	snowbridge_runtime_test_common::ethereum_to_polkadot_message_extrinsics_work(
+		collator_session_keys(),
+		BRIDGE_HUB_WESTEND_PARACHAIN_ID,
+		construct_and_apply_extrinsic,
+	);
+}
+
+/// Tests that the digest items are as expected when a Ethereum Outbound message is received.
+/// If the MessageQueue pallet is configured before (i.e. the MessageQueue pallet is listed before
+/// the EthereumOutboundQueue in the construct_runtime macro) the EthereumOutboundQueue, this test
+/// will fail.
+#[test]
+pub fn ethereum_outbound_queue_processes_messages_before_message_queue_works() {
+	snowbridge_runtime_test_common::ethereum_outbound_queue_processes_messages_before_message_queue_works::<
+		Runtime,
+		XcmConfig,
+		AllPalletsWithoutSystem,
+	>(
+		11155111,
+		collator_session_keys(),
+		BRIDGE_HUB_WESTEND_PARACHAIN_ID,
+		ASSET_HUB_WESTEND_PARACHAIN_ID,
+		H160::random(),
+		H160::random(),
+		DefaultBridgeHubEthereumBaseFee::get(),
+		Box::new(|runtime_event_encoded: Vec<u8>| {
+			match RuntimeEvent::decode(&mut &runtime_event_encoded[..]) {
+				Ok(RuntimeEvent::EthereumOutboundQueue(event)) => Some(event),
+				_ => None,
+			}
+		}),
+	)
+}
+
+fn construct_extrinsic(
+	sender: sp_keyring::AccountKeyring,
+	call: RuntimeCall,
+) -> UncheckedExtrinsic {
+	let account_id = AccountId32::from(sender.public());
+	let extra: SignedExtra = (
+		frame_system::CheckNonZeroSender::<Runtime>::new(),
+		frame_system::CheckSpecVersion::<Runtime>::new(),
+		frame_system::CheckTxVersion::<Runtime>::new(),
+		frame_system::CheckGenesis::<Runtime>::new(),
+		frame_system::CheckEra::<Runtime>::from(Era::immortal()),
+		frame_system::CheckNonce::<Runtime>::from(
+			frame_system::Pallet::<Runtime>::account(&account_id).nonce,
+		),
+		frame_system::CheckWeight::<Runtime>::new(),
+		pallet_transaction_payment::ChargeTransactionPayment::<Runtime>::from(0),
+		BridgeRejectObsoleteHeadersAndMessages::default(),
+		(bridge_to_rococo_config::OnBridgeHubWestendRefundBridgeHubRococoMessages::default(),),
+		cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::new(),
+		frame_metadata_hash_extension::CheckMetadataHash::<Runtime>::new(false),
+	);
+	let payload = SignedPayload::new(call.clone(), extra.clone()).unwrap();
+	let signature = payload.using_encoded(|e| sender.sign(e));
+	UncheckedExtrinsic::new_signed(call, account_id.into(), Signature::Sr25519(signature), extra)
+}
+
+fn construct_and_apply_extrinsic(
+	origin: sp_keyring::AccountKeyring,
+	call: RuntimeCall,
+) -> sp_runtime::DispatchOutcome {
+	let xt = construct_extrinsic(origin, call);
+	let r = Executive::apply_extrinsic(xt);
+	r.unwrap()
+}
diff --git a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs
index 98a789eb4e0..a66c0f84240 100644
--- a/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs
+++ b/cumulus/parachains/runtimes/bridge-hubs/bridge-hub-westend/tests/tests.rs
@@ -77,6 +77,7 @@ fn construct_extrinsic(
 		BridgeRejectObsoleteHeadersAndMessages::default(),
 		(bridge_to_rococo_config::OnBridgeHubWestendRefundBridgeHubRococoMessages::default(),),
 		cumulus_primitives_storage_weight_reclaim::StorageWeightReclaim::new(),
+		frame_metadata_hash_extension::CheckMetadataHash::new(false),
 	);
 	let payload = SignedPayload::new(call.clone(), extra.clone()).unwrap();
 	let signature = payload.using_encoded(|e| sender.sign(e));
@@ -299,8 +300,8 @@ pub fn can_calculate_fee_for_standalone_message_delivery_transaction() {
 				RuntimeTestsAdapter,
 			>(collator_session_keys(), construct_and_estimate_extrinsic_fee)
 		},
-		Perbill::from_percent(33),
-		Some(-33),
+		Perbill::from_percent(25),
+		Some(-25),
 		&format!(
 			"Estimate fee for `single message delivery` for runtime: {:?}",
 			<Runtime as frame_system::Config>::Version::get()
@@ -318,8 +319,8 @@ pub fn can_calculate_fee_for_standalone_message_confirmation_transaction() {
 				RuntimeTestsAdapter,
 			>(collator_session_keys(), construct_and_estimate_extrinsic_fee)
 		},
-		Perbill::from_percent(33),
-		Some(-33),
+		Perbill::from_percent(25),
+		Some(-25),
 		&format!(
 			"Estimate fee for `single message confirmation` for runtime: {:?}",
 			<Runtime as frame_system::Config>::Version::get()
diff --git a/cumulus/parachains/runtimes/constants/src/westend.rs b/cumulus/parachains/runtimes/constants/src/westend.rs
index 607d91e8808..fec66cec2eb 100644
--- a/cumulus/parachains/runtimes/constants/src/westend.rs
+++ b/cumulus/parachains/runtimes/constants/src/westend.rs
@@ -168,3 +168,19 @@ pub mod time {
 	pub const HOURS: BlockNumber = MINUTES * 60;
 	pub const DAYS: BlockNumber = HOURS * 24;
 }
+
+pub mod snowbridge {
+	use frame_support::parameter_types;
+	use xcm::opaque::lts::NetworkId;
+
+	/// The pallet index of the Ethereum inbound queue pallet in the bridge hub runtime.
+	pub const INBOUND_QUEUE_PALLET_INDEX: u8 = 80;
+
+	parameter_types! {
+		/// Network and location for the Ethereum chain. On Westend, the Ethereum chain bridged
+		/// to is the Sepolia Ethereum testnet, with chain ID 11155111.
+		/// <https://chainlist.org/chain/11155111>
+		/// <https://ethereum.org/en/developers/docs/apis/json-rpc/#net_version>
+		pub EthereumNetwork: NetworkId = NetworkId::Ethereum { chain_id: 11155111 };
+	}
+}
diff --git a/cumulus/polkadot-parachain/Cargo.toml b/cumulus/polkadot-parachain/Cargo.toml
index e1016cebb39..5d654844746 100644
--- a/cumulus/polkadot-parachain/Cargo.toml
+++ b/cumulus/polkadot-parachain/Cargo.toml
@@ -175,6 +175,7 @@ try-runtime = [
 ]
 fast-runtime = [
 	"bridge-hub-rococo-runtime/fast-runtime",
+	"bridge-hub-westend-runtime/fast-runtime",
 	"coretime-rococo-runtime/fast-runtime",
 	"coretime-westend-runtime/fast-runtime",
 ]
diff --git a/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs b/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs
index 15e8a1bf11a..3b7376d045b 100644
--- a/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs
+++ b/cumulus/polkadot-parachain/src/chain_spec/bridge_hubs.rs
@@ -357,6 +357,10 @@ pub mod westend {
 			},
 			"bridgeRococoMessages":  {
 				"owner": bridges_pallet_owner.clone(),
+			},
+			"ethereumSystem": {
+				"paraId": id,
+				"assetHubParaId": 1000
 			}
 		})
 	}
diff --git a/cumulus/polkadot-parachain/src/command.rs b/cumulus/polkadot-parachain/src/command.rs
index e867a41bee2..3df90c889e8 100644
--- a/cumulus/polkadot-parachain/src/command.rs
+++ b/cumulus/polkadot-parachain/src/command.rs
@@ -295,6 +295,7 @@ fn extract_parachain_id(id: &str) -> (&str, &str, Option<ParaId>) {
 	let para_prefixes = [
 		// Penpal
 		"penpal-rococo-",
+		"penpal-westend-",
 		"penpal-kusama-",
 		"penpal-polkadot-",
 		// Glutton Kusama
diff --git a/polkadot/node/service/src/chain_spec.rs b/polkadot/node/service/src/chain_spec.rs
index 0358bc300ab..f930476a554 100644
--- a/polkadot/node/service/src/chain_spec.rs
+++ b/polkadot/node/service/src/chain_spec.rs
@@ -658,7 +658,8 @@ fn westend_local_testnet_genesis() -> serde_json::Value {
 #[cfg(feature = "westend-native")]
 pub fn westend_local_testnet_config() -> Result<WestendChainSpec, String> {
 	Ok(WestendChainSpec::builder(
-		westend::WASM_BINARY.ok_or("Westend development wasm not available")?,
+		westend::fast_runtime_binary::WASM_BINARY
+			.ok_or("Westend development wasm not available")?,
 		Default::default(),
 	)
 	.with_name("Westend Local Testnet")
diff --git a/polkadot/runtime/westend/build.rs b/polkadot/runtime/westend/build.rs
index 8ff3a4fb911..55ccd364012 100644
--- a/polkadot/runtime/westend/build.rs
+++ b/polkadot/runtime/westend/build.rs
@@ -17,6 +17,10 @@
 #[cfg(all(not(feature = "metadata-hash"), feature = "std"))]
 fn main() {
 	substrate_wasm_builder::WasmBuilder::build_using_defaults();
+	substrate_wasm_builder::WasmBuilder::init_with_defaults()
+		.set_file_name("fast_runtime_binary.rs")
+		.enable_feature("fast-runtime")
+		.build();
 }
 
 #[cfg(all(feature = "metadata-hash", feature = "std"))]
@@ -24,6 +28,11 @@ fn main() {
 	substrate_wasm_builder::WasmBuilder::init_with_defaults()
 		.enable_metadata_hash("WND", 12)
 		.build();
+	substrate_wasm_builder::WasmBuilder::init_with_defaults()
+		.set_file_name("fast_runtime_binary.rs")
+		.enable_feature("fast-runtime")
+		.enable_metadata_hash("WND", 12)
+		.build();
 }
 
 #[cfg(not(feature = "std"))]
diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs
index eddb7cbd21e..838ba17e561 100644
--- a/polkadot/runtime/westend/src/lib.rs
+++ b/polkadot/runtime/westend/src/lib.rs
@@ -156,6 +156,11 @@ impl_runtime_weights!(westend_runtime_constants);
 #[cfg(feature = "std")]
 include!(concat!(env!("OUT_DIR"), "/wasm_binary.rs"));
 
+#[cfg(feature = "std")]
+pub mod fast_runtime_binary {
+	include!(concat!(env!("OUT_DIR"), "/fast_runtime_binary.rs"));
+}
+
 /// Runtime version (Westend).
 #[sp_version::runtime_version]
 pub const VERSION: RuntimeVersion = RuntimeVersion {
diff --git a/prdoc/pr_5074.prdoc b/prdoc/pr_5074.prdoc
new file mode 100644
index 00000000000..cddf15ffb47
--- /dev/null
+++ b/prdoc/pr_5074.prdoc
@@ -0,0 +1,33 @@
+title: "Snowbridge on Westend"
+
+doc:
+  - audience: Runtime Dev
+    description: |
+      Since Rococo is now deprecated, we need another testnet to detect bleeding-edge changes 
+      to Substrate, Polkadot, BEEFY consensus protocols that could brick the bridge.
+  - audience: Runtime Dev
+    description: |
+      Like Rococo this PR enables the fast-runtime feature by default which is easier 
+      for testing beefy stuff on westend-local.
+
+crates:
+  - name: asset-hub-westend-runtime
+    bump: patch
+  - name: bridge-hub-westend-runtime
+    bump: major
+  - name: bridge-hub-rococo-runtime
+    bump: patch
+  - name: testnet-parachains-constants
+    bump: patch
+  - name: bridge-hub-westend-emulated-chain
+    bump: minor
+  - name: bridge-hub-westend-integration-tests
+    bump: minor
+  - name: polkadot-parachain-bin
+    bump: patch
+  - name: westend-runtime
+    bump: patch
+  - name: polkadot-service
+    bump: patch
+  
+
-- 
GitLab