diff --git a/substrate/Cargo.lock b/substrate/Cargo.lock
index 4a4413905a40e5e55bd8f04dbd9e82c52dfe2f17..b59f18c3c4c541ebd354ba5c5617ef64b857a3c3 100644
--- a/substrate/Cargo.lock
+++ b/substrate/Cargo.lock
@@ -4048,6 +4048,7 @@ dependencies = [
  "node-primitives",
  "pallet-alliance",
  "pallet-asset-conversion",
+ "pallet-asset-conversion-tx-payment",
  "pallet-asset-rate",
  "pallet-asset-tx-payment",
  "pallet-assets",
@@ -5398,6 +5399,7 @@ dependencies = [
  "node-primitives",
  "node-rpc",
  "pallet-asset-conversion",
+ "pallet-asset-conversion-tx-payment",
  "pallet-asset-tx-payment",
  "pallet-assets",
  "pallet-balances",
@@ -5680,6 +5682,7 @@ dependencies = [
  "node-executor",
  "node-primitives",
  "pallet-asset-conversion",
+ "pallet-asset-conversion-tx-payment",
  "pallet-asset-tx-payment",
  "pallet-assets",
  "pallet-transaction-payment",
@@ -5993,6 +5996,25 @@ dependencies = [
  "sp-std",
 ]
 
+[[package]]
+name = "pallet-asset-conversion-tx-payment"
+version = "4.0.0-dev"
+dependencies = [
+ "frame-support",
+ "frame-system",
+ "pallet-asset-conversion",
+ "pallet-assets",
+ "pallet-balances",
+ "pallet-transaction-payment",
+ "parity-scale-codec",
+ "scale-info",
+ "sp-core",
+ "sp-io",
+ "sp-runtime",
+ "sp-std",
+ "sp-storage",
+]
+
 [[package]]
 name = "pallet-asset-rate"
 version = "4.0.0-dev"
diff --git a/substrate/Cargo.toml b/substrate/Cargo.toml
index ce93421bc9ff163827aa7462bd81c5979709e211..e0887b000728904f318fae19d20cadf3a869cf20 100644
--- a/substrate/Cargo.toml
+++ b/substrate/Cargo.toml
@@ -169,6 +169,7 @@ members = [
 	"frame/system/rpc/runtime-api",
 	"frame/timestamp",
 	"frame/transaction-payment",
+	"frame/transaction-payment/asset-conversion-tx-payment",
 	"frame/transaction-payment/asset-tx-payment",
 	"frame/transaction-payment/rpc",
 	"frame/transaction-payment/rpc/runtime-api",
diff --git a/substrate/bin/node/cli/Cargo.toml b/substrate/bin/node/cli/Cargo.toml
index adbf9e85a9fba8624321ccb51f43fe2dfd044c0b..197c0771c1c34f467aa1424b1f93ae976cc22661 100644
--- a/substrate/bin/node/cli/Cargo.toml
+++ b/substrate/bin/node/cli/Cargo.toml
@@ -90,7 +90,8 @@ frame-system-rpc-runtime-api = { version = "4.0.0-dev", path = "../../../frame/s
 pallet-transaction-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment" }
 pallet-asset-conversion = { version = "4.0.0-dev", path = "../../../frame/asset-conversion" }
 pallet-assets = { version = "4.0.0-dev", path = "../../../frame/assets/" }
-pallet-asset-tx-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment/asset-tx-payment/" }
+pallet-asset-conversion-tx-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment/asset-conversion-tx-payment" }
+pallet-asset-tx-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment/asset-tx-payment" }
 pallet-im-online = { version = "4.0.0-dev", default-features = false, path = "../../../frame/im-online" }
 
 # node-specific dependencies
diff --git a/substrate/bin/node/cli/src/service.rs b/substrate/bin/node/cli/src/service.rs
index 8fc44c7c5eddf272cea69667fa981b556214cff5..81f457538447377996d23ffcfd7853a37aee673a 100644
--- a/substrate/bin/node/cli/src/service.rs
+++ b/substrate/bin/node/cli/src/service.rs
@@ -98,7 +98,7 @@ pub fn create_extrinsic(
 		)),
 		frame_system::CheckNonce::<kitchensink_runtime::Runtime>::from(nonce),
 		frame_system::CheckWeight::<kitchensink_runtime::Runtime>::new(),
-		pallet_asset_tx_payment::ChargeAssetTxPayment::<kitchensink_runtime::Runtime>::from(
+		pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::<kitchensink_runtime::Runtime>::from(
 			tip, None,
 		),
 	);
@@ -815,7 +815,8 @@ mod tests {
 				let check_era = frame_system::CheckEra::from(Era::Immortal);
 				let check_nonce = frame_system::CheckNonce::from(index);
 				let check_weight = frame_system::CheckWeight::new();
-				let tx_payment = pallet_asset_tx_payment::ChargeAssetTxPayment::from(0, None);
+				let tx_payment =
+					pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::from(0, None);
 				let extra = (
 					check_non_zero_sender,
 					check_spec_version,
diff --git a/substrate/bin/node/runtime/Cargo.toml b/substrate/bin/node/runtime/Cargo.toml
index 55c21b920c3d77c1ad9dda56901ee08940a233fa..30f0052c6eaa6eb683d44e1e3f701449971cd959 100644
--- a/substrate/bin/node/runtime/Cargo.toml
+++ b/substrate/bin/node/runtime/Cargo.toml
@@ -121,7 +121,8 @@ pallet-treasury = { version = "4.0.0-dev", default-features = false, path = "../
 pallet-utility = { version = "4.0.0-dev", default-features = false, path = "../../../frame/utility" }
 pallet-transaction-payment = { version = "4.0.0-dev", default-features = false, path = "../../../frame/transaction-payment" }
 pallet-transaction-payment-rpc-runtime-api = { version = "4.0.0-dev", default-features = false, path = "../../../frame/transaction-payment/rpc/runtime-api/" }
-pallet-asset-tx-payment = { version = "4.0.0-dev", default-features = false, path = "../../../frame/transaction-payment/asset-tx-payment/" }
+pallet-asset-conversion-tx-payment = { version = "4.0.0-dev", default-features = false, path = "../../../frame/transaction-payment/asset-conversion-tx-payment" }
+pallet-asset-tx-payment = { version = "4.0.0-dev", default-features = false, path = "../../../frame/transaction-payment/asset-tx-payment" }
 pallet-transaction-storage = { version = "4.0.0-dev", default-features = false, path = "../../../frame/transaction-storage" }
 pallet-uniques = { version = "4.0.0-dev", default-features = false, path = "../../../frame/uniques" }
 pallet-vesting = { version = "4.0.0-dev", default-features = false, path = "../../../frame/vesting" }
@@ -137,6 +138,7 @@ std = [
 	"pallet-whitelist/std",
 	"pallet-offences-benchmarking?/std",
 	"pallet-election-provider-support-benchmarking?/std",
+	"pallet-asset-conversion-tx-payment/std",
 	"pallet-asset-tx-payment/std",
 	"frame-system-benchmarking?/std",
 	"frame-election-provider-support/std",
@@ -355,6 +357,7 @@ try-runtime = [
 	"pallet-asset-rate/try-runtime",
 	"pallet-utility/try-runtime",
 	"pallet-transaction-payment/try-runtime",
+	"pallet-asset-conversion-tx-payment/try-runtime",
 	"pallet-asset-tx-payment/try-runtime",
 	"pallet-transaction-storage/try-runtime",
 	"pallet-uniques/try-runtime",
diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs
index 7d16a1afa1f2db926c32bb908a1273157e3c9043..2270cd0145353c26d140efbb7041ab6ba6bef776 100644
--- a/substrate/bin/node/runtime/src/lib.rs
+++ b/substrate/bin/node/runtime/src/lib.rs
@@ -493,6 +493,13 @@ impl pallet_asset_tx_payment::Config for Runtime {
 	>;
 }
 
+impl pallet_asset_conversion_tx_payment::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type Fungibles = Assets;
+	type OnChargeAssetTransaction =
+		pallet_asset_conversion_tx_payment::AssetConversionAdapter<Balances, AssetConversion>;
+}
+
 parameter_types! {
 	pub const MinimumPeriod: Moment = SLOT_DURATION / 2;
 }
@@ -1292,7 +1299,7 @@ where
 			frame_system::CheckEra::<Runtime>::from(era),
 			frame_system::CheckNonce::<Runtime>::from(nonce),
 			frame_system::CheckWeight::<Runtime>::new(),
-			pallet_asset_tx_payment::ChargeAssetTxPayment::<Runtime>::from(tip, None),
+			pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::<Runtime>::from(tip, None),
 		);
 		let raw_payload = SignedPayload::new(call, extra)
 			.map_err(|e| {
@@ -1876,6 +1883,7 @@ construct_runtime!(
 		Balances: pallet_balances,
 		TransactionPayment: pallet_transaction_payment,
 		AssetTxPayment: pallet_asset_tx_payment,
+		AssetConversionTxPayment: pallet_asset_conversion_tx_payment,
 		ElectionProviderMultiPhase: pallet_election_provider_multi_phase,
 		Staking: pallet_staking,
 		Session: pallet_session,
@@ -1960,7 +1968,7 @@ pub type SignedExtra = (
 	frame_system::CheckEra<Runtime>,
 	frame_system::CheckNonce<Runtime>,
 	frame_system::CheckWeight<Runtime>,
-	pallet_asset_tx_payment::ChargeAssetTxPayment<Runtime>,
+	pallet_asset_conversion_tx_payment::ChargeAssetTxPayment<Runtime>,
 );
 
 /// Unchecked extrinsic type as expected by this runtime.
diff --git a/substrate/bin/node/testing/Cargo.toml b/substrate/bin/node/testing/Cargo.toml
index c5a9b018e0dab913c69acbaba9ed98b8cc973368..81a15f528057aa24571ef4e0a0e5a212fd238bb0 100644
--- a/substrate/bin/node/testing/Cargo.toml
+++ b/substrate/bin/node/testing/Cargo.toml
@@ -24,6 +24,7 @@ node-primitives = { version = "2.0.0", path = "../primitives" }
 kitchensink-runtime = { version = "3.0.0-dev", path = "../runtime" }
 pallet-asset-conversion = { version = "4.0.0-dev", path = "../../../frame/asset-conversion" }
 pallet-assets = { version = "4.0.0-dev", path = "../../../frame/assets" }
+pallet-asset-conversion-tx-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment/asset-conversion-tx-payment" }
 pallet-asset-tx-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment/asset-tx-payment" }
 pallet-transaction-payment = { version = "4.0.0-dev", path = "../../../frame/transaction-payment" }
 sc-block-builder = { version = "0.10.0-dev", path = "../../../client/block-builder" }
diff --git a/substrate/bin/node/testing/src/keyring.rs b/substrate/bin/node/testing/src/keyring.rs
index e16502bf17554c989f0228d1932031ab41fef145..c3f8d8e6d94855b9ae214a6ba03c46d4f3acbd87 100644
--- a/substrate/bin/node/testing/src/keyring.rs
+++ b/substrate/bin/node/testing/src/keyring.rs
@@ -77,7 +77,7 @@ pub fn signed_extra(nonce: Index, extra_fee: Balance) -> SignedExtra {
 		frame_system::CheckEra::from(Era::mortal(256, 0)),
 		frame_system::CheckNonce::from(nonce),
 		frame_system::CheckWeight::new(),
-		pallet_asset_tx_payment::ChargeAssetTxPayment::from(extra_fee, None),
+		pallet_asset_conversion_tx_payment::ChargeAssetTxPayment::from(extra_fee, None),
 	)
 }
 
diff --git a/substrate/frame/asset-conversion/Cargo.toml b/substrate/frame/asset-conversion/Cargo.toml
index 4025be0db9a9c598608eb3a01a763b8cbeb629c5..0a7f48b0047f53501868f9c477f92e86f74979cb 100644
--- a/substrate/frame/asset-conversion/Cargo.toml
+++ b/substrate/frame/asset-conversion/Cargo.toml
@@ -13,12 +13,13 @@ readme = "README.md"
 targets = ["x86_64-unknown-linux-gnu"]
 
 [dependencies]
-codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false, features = ["derive"]  }
+codec = { package = "parity-scale-codec", version = "3.6.1", default-features = false }
 frame-support = { version = "4.0.0-dev", default-features = false, path = "../support" }
 frame-system = { version = "4.0.0-dev", default-features = false, path = "../system" }
 frame-benchmarking = { version = "4.0.0-dev", default-features = false, path = "../benchmarking", optional = true }
-scale-info = { version = "2.0.0", default-features = false, features = ["derive"] }
+scale-info = { version = "2.5.0", default-features = false, features = ["derive"] }
 sp-api = { version = "4.0.0-dev", default-features = false, path = "../../primitives/api" }
+sp-core = { version = "21.0.0", default-features = false, path = "../../primitives/core" }
 sp-io = { version = "23.0.0", default-features = false, path = "../../primitives/io" }
 sp-std = { version = "8.0.0", default-features = false, path = "../../primitives/std" }
 sp-runtime = { version = "24.0.0", default-features = false, path = "../../primitives/runtime" }
@@ -28,9 +29,6 @@ sp-arithmetic = { version = "16.0.0", default-features = false, path = "../../pr
 pallet-balances = { version = "4.0.0-dev", path = "../balances" }
 pallet-assets = { version = "4.0.0-dev", path = "../assets" }
 primitive-types = { version = "0.12.0", default-features = false, features = ["codec", "scale-info", "num-traits"] }
-sp-std = { version = "8.0.0", path = "../../primitives/std" }
-sp-core = { version = "21.0.0", path = "../../primitives/core" }
-sp-io = { version = "23.0.0", path = "../../primitives/io" }
 
 [features]
 default = ["std"]
diff --git a/substrate/frame/asset-conversion/src/lib.rs b/substrate/frame/asset-conversion/src/lib.rs
index c97ab537a945f4bf2722c30b5e26051dad35e46f..68a0210b40058f5adff6932f8b7b204ef7536b0c 100644
--- a/substrate/frame/asset-conversion/src/lib.rs
+++ b/substrate/frame/asset-conversion/src/lib.rs
@@ -53,7 +53,7 @@
 //! (This can be run against the kitchen sync node in the `node` folder of this repo.)
 #![deny(missing_docs)]
 #![cfg_attr(not(feature = "std"), no_std)]
-use frame_support::traits::Incrementable;
+use frame_support::traits::{DefensiveOption, Incrementable};
 
 #[cfg(feature = "runtime-benchmarks")]
 mod benchmarking;
@@ -307,7 +307,10 @@ pub mod pallet {
 		WrongDesiredAmount,
 		/// Provided amount should be greater than or equal to the existential deposit/asset's
 		/// minimal amount.
-		AmountLessThanMinimal,
+		AmountOneLessThanMinimal,
+		/// Provided amount should be greater than or equal to the existential deposit/asset's
+		/// minimal amount.
+		AmountTwoLessThanMinimal,
 		/// Reserve needs to always be greater than or equal to the existential deposit/asset's
 		/// minimal amount.
 		ReserveLeftLessThanMinimal,
@@ -347,6 +350,20 @@ pub mod pallet {
 		PathError,
 		/// The provided path must consists of unique assets.
 		NonUniquePath,
+		/// Unable to find an element in an array/vec that should have one-to-one correspondence
+		/// with another. For example, an array of assets constituting a `path` should have a
+		/// corresponding array of `amounts` along the path.
+		CorrespondenceError,
+	}
+
+	#[pallet::hooks]
+	impl<T: Config> Hooks<T::BlockNumber> for Pallet<T> {
+		fn integrity_test() {
+			assert!(
+				T::MaxSwapPathLength::get() > 1,
+				"the `MaxSwapPathLength` should be greater than 1",
+			);
+		}
 	}
 
 	/// Pallet's callable functions.
@@ -488,9 +505,9 @@ pub mod pallet {
 			}
 
 			Self::validate_minimal_amount(amount1.saturating_add(reserve1), &asset1)
-				.map_err(|_| Error::<T>::AmountLessThanMinimal)?;
+				.map_err(|_| Error::<T>::AmountOneLessThanMinimal)?;
 			Self::validate_minimal_amount(amount2.saturating_add(reserve2), &asset2)
-				.map_err(|_| Error::<T>::AmountLessThanMinimal)?;
+				.map_err(|_| Error::<T>::AmountTwoLessThanMinimal)?;
 
 			Self::transfer(&asset1, &sender, &pool_account, amount1, true)?;
 			Self::transfer(&asset2, &sender, &pool_account, amount2, true)?;
@@ -635,16 +652,7 @@ pub mod pallet {
 			let amount_out = *amounts.last().expect("Has always more than 1 element");
 			ensure!(amount_out >= amount_out_min, Error::<T>::ProvidedMinimumNotSufficientForSwap);
 
-			Self::do_swap(&sender, &amounts, &path, &send_to, keep_alive)?;
-
-			Self::deposit_event(Event::SwapExecuted {
-				who: sender,
-				send_to,
-				path,
-				amount_in,
-				amount_out,
-			});
-
+			Self::do_swap(sender, &amounts, path, send_to, keep_alive)?;
 			Ok(())
 		}
 
@@ -676,21 +684,13 @@ pub mod pallet {
 			let amount_in = *amounts.first().expect("Always has more than one element");
 			ensure!(amount_in <= amount_in_max, Error::<T>::ProvidedMaximumNotSufficientForSwap);
 
-			Self::do_swap(&sender, &amounts, &path, &send_to, keep_alive)?;
-
-			Self::deposit_event(Event::SwapExecuted {
-				who: sender,
-				send_to,
-				path,
-				amount_in,
-				amount_out,
-			});
-
+			Self::do_swap(sender, &amounts, path, send_to, keep_alive)?;
 			Ok(())
 		}
 	}
 
 	impl<T: Config> Pallet<T> {
+		/// Transfer an `amount` of `asset_id`, respecting the `keep_alive` requirements.
 		fn transfer(
 			asset_id: &T::MultiAssetId,
 			from: &T::AccountId,
@@ -709,8 +709,13 @@ pub mod pallet {
 					true => Preserve,
 					false => Expendable,
 				};
-				let amount = Self::asset_to_native(amount)?;
-				Ok(Self::native_to_asset(T::Currency::transfer(from, to, amount, preservation)?)?)
+				let amount = Self::convert_asset_balance_to_native_balance(amount)?;
+				Ok(Self::convert_native_balance_to_asset_balance(T::Currency::transfer(
+					from,
+					to,
+					amount,
+					preservation,
+				)?)?)
 			} else {
 				T::Assets::transfer(
 					T::MultiAssetIdConverter::try_convert(&asset_id)
@@ -723,31 +728,40 @@ pub mod pallet {
 			}
 		}
 
-		pub(crate) fn native_to_asset(amount: T::Balance) -> Result<T::AssetBalance, Error<T>> {
+		/// Convert a `Balance` type to an `AssetBalance`.
+		pub(crate) fn convert_native_balance_to_asset_balance(
+			amount: T::Balance,
+		) -> Result<T::AssetBalance, Error<T>> {
 			T::HigherPrecisionBalance::from(amount)
 				.try_into()
 				.map_err(|_| Error::<T>::Overflow)
 		}
 
-		pub(crate) fn asset_to_native(amount: T::AssetBalance) -> Result<T::Balance, Error<T>> {
+		/// Convert an `AssetBalance` type to a `Balance`.
+		pub(crate) fn convert_asset_balance_to_native_balance(
+			amount: T::AssetBalance,
+		) -> Result<T::Balance, Error<T>> {
 			T::HigherPrecisionBalance::from(amount)
 				.try_into()
 				.map_err(|_| Error::<T>::Overflow)
 		}
 
+		/// Swap assets along a `path`, depositing in `send_to`.
 		pub(crate) fn do_swap(
-			sender: &T::AccountId,
+			sender: T::AccountId,
 			amounts: &Vec<T::AssetBalance>,
-			path: &BoundedVec<T::MultiAssetId, T::MaxSwapPathLength>,
-			send_to: &T::AccountId,
+			path: BoundedVec<T::MultiAssetId, T::MaxSwapPathLength>,
+			send_to: T::AccountId,
 			keep_alive: bool,
 		) -> Result<(), DispatchError> {
-			if let Some([asset1, asset2]) = path.get(0..2) {
+			ensure!(amounts.len() > 1, Error::<T>::CorrespondenceError);
+			if let Some([asset1, asset2]) = &path.get(0..2) {
 				let pool_id = Self::get_pool_id(asset1.clone(), asset2.clone());
 				let pool_account = Self::get_pool_account(&pool_id);
-				let first_amount = amounts.first().expect("Always has more than one element");
+				// amounts should always contain a corresponding element to path.
+				let first_amount = amounts.first().ok_or(Error::<T>::CorrespondenceError)?;
 
-				Self::transfer(asset1, sender, &pool_account, *first_amount, keep_alive)?;
+				Self::transfer(asset1, &sender, &pool_account, *first_amount, keep_alive)?;
 
 				let mut i = 0;
 				let path_len = path.len() as u32;
@@ -757,7 +771,7 @@ pub mod pallet {
 						let pool_account = Self::get_pool_account(&pool_id);
 
 						let amount_out =
-							amounts.get((i + 1) as usize).ok_or(Error::<T>::PathError)?;
+							amounts.get((i + 1) as usize).ok_or(Error::<T>::CorrespondenceError)?;
 
 						let to = if i < path_len - 2 {
 							let asset3 = path.get((i + 2) as usize).ok_or(Error::<T>::PathError)?;
@@ -778,6 +792,15 @@ pub mod pallet {
 					}
 					i.saturating_inc();
 				}
+				Self::deposit_event(Event::SwapExecuted {
+					who: sender,
+					send_to,
+					path,
+					amount_in: *first_amount,
+					amount_out: *amounts.last().expect("Always has more than 1 element"),
+				});
+			} else {
+				return Err(Error::<T>::InvalidPath.into())
 			}
 			Ok(())
 		}
@@ -793,14 +816,16 @@ pub mod pallet {
 				.expect("infinite length input; no invalid inputs for type; qed")
 		}
 
+		/// Get the `owner`'s balance of `asset`, which could be the chain's native asset or another
+		/// fungible. Returns a value in the form of an `AssetBalance`.
 		fn get_balance(
 			owner: &T::AccountId,
 			asset: &T::MultiAssetId,
 		) -> Result<T::AssetBalance, Error<T>> {
 			if T::MultiAssetIdConverter::is_native(asset) {
-				Self::native_to_asset(<<T as Config>::Currency>::reducible_balance(
-					owner, Expendable, Polite,
-				))
+				Self::convert_native_balance_to_asset_balance(
+					<<T as Config>::Currency>::reducible_balance(owner, Expendable, Polite),
+				)
 			} else {
 				Ok(<<T as Config>::Assets>::reducible_balance(
 					T::MultiAssetIdConverter::try_convert(asset)
@@ -841,6 +866,7 @@ pub mod pallet {
 			Ok((balance1, balance2))
 		}
 
+		/// Leading to an amount at the end of a `path`, get the required amounts in.
 		pub(crate) fn get_amounts_in(
 			amount_out: &T::AssetBalance,
 			path: &BoundedVec<T::MultiAssetId, T::MaxSwapPathLength>,
@@ -860,6 +886,7 @@ pub mod pallet {
 			Ok(amounts)
 		}
 
+		/// Following an amount into a `path`, get the corresponding amounts out.
 		pub(crate) fn get_amounts_out(
 			amount_in: &T::AssetBalance,
 			path: &BoundedVec<T::MultiAssetId, T::MaxSwapPathLength>,
@@ -969,10 +996,10 @@ pub mod pallet {
 			result.try_into().map_err(|_| Error::<T>::Overflow)
 		}
 
-		/// Calculates amount out
+		/// Calculates amount out.
 		///
 		/// Given an input amount of an asset and pair reserves, returns the maximum output amount
-		/// of the other asset
+		/// of the other asset.
 		pub fn get_amount_out(
 			amount_in: &T::AssetBalance,
 			reserve_in: &T::AssetBalance,
@@ -1004,10 +1031,10 @@ pub mod pallet {
 			result.try_into().map_err(|_| Error::<T>::Overflow)
 		}
 
-		/// Calculates amount in
+		/// Calculates amount in.
 		///
 		/// Given an output amount of an asset and pair reserves, returns a required input amount
-		/// of the other asset
+		/// of the other asset.
 		pub fn get_amount_in(
 			amount_out: &T::AssetBalance,
 			reserve_in: &T::AssetBalance,
@@ -1046,6 +1073,7 @@ pub mod pallet {
 			result.try_into().map_err(|_| Error::<T>::Overflow)
 		}
 
+		/// Ensure that a `value` meets the minimum balance requirements of an `asset` class.
 		fn validate_minimal_amount(
 			value: T::AssetBalance,
 			asset: &T::MultiAssetId,
@@ -1064,6 +1092,7 @@ pub mod pallet {
 			Ok(())
 		}
 
+		/// Ensure that a path is valid.
 		fn validate_swap_path(
 			path: &BoundedVec<T::MultiAssetId, T::MaxSwapPathLength>,
 		) -> Result<(), DispatchError> {
@@ -1092,7 +1121,7 @@ pub mod pallet {
 }
 
 impl<T: Config>
-	frame_support::traits::tokens::fungibles::SwapForNative<
+	frame_support::traits::tokens::fungibles::SwapNative<
 		T::RuntimeOrigin,
 		T::AccountId,
 		T::Balance,
@@ -1103,7 +1132,11 @@ where
 	<T as pallet::Config>::Currency:
 		frame_support::traits::tokens::fungible::Inspect<<T as frame_system::Config>::AccountId>,
 {
-	// If successful returns the amount in.
+	/// Take an `asset_id` and swap some amount for `amount_out` of the chain's native asset. If an
+	/// `amount_in_max` is specified, it will return an error if acquiring `amount_out` would be
+	/// too costly.
+	///
+	/// If successful returns the amount of the `asset_id` taken to provide `amount_out`.
 	fn swap_tokens_for_exact_native(
 		sender: T::AccountId,
 		asset_id: T::AssetId,
@@ -1121,25 +1154,58 @@ where
 		path.push(T::MultiAssetIdConverter::get_native());
 		let path = path.try_into().unwrap();
 
-		let amount_out = Self::native_to_asset(amount_out)?;
+		// convert `amount_out` from native balance type, to asset balance type
+		let amount_out = Self::convert_native_balance_to_asset_balance(amount_out)?;
 
+		// calculate the amount we need to provide
 		let amounts = Self::get_amounts_in(&amount_out, &path)?;
-		let amount_in = *amounts.first().expect("Always has more than one element");
+		let amount_in =
+			*amounts.first().defensive_ok_or("get_amounts_in() returned an empty result")?;
 		if let Some(amount_in_max) = amount_in_max {
 			ensure!(amount_in <= amount_in_max, Error::<T>::ProvidedMaximumNotSufficientForSwap);
 		}
 
-		Self::do_swap(&sender, &amounts, &path, &send_to, keep_alive)?;
+		Self::do_swap(sender, &amounts, path, send_to, keep_alive)?;
+		Ok(amount_in)
+	}
 
-		Self::deposit_event(Event::SwapExecuted {
-			who: sender,
-			send_to,
-			path,
-			amount_in,
-			amount_out,
-		});
+	/// Take an `asset_id` and swap `amount_in` of the chain's native asset for it. If an
+	/// `amount_out_min` is specified, it will return an error if it is unable to acquire the amount
+	/// desired.
+	///
+	/// If successful, returns the amount of `asset_id` acquired for the `amount_in`.
+	fn swap_exact_native_for_tokens(
+		sender: T::AccountId,
+		asset_id: T::AssetId,
+		amount_in: T::Balance,
+		amount_out_min: Option<T::AssetBalance>,
+		send_to: T::AccountId,
+		keep_alive: bool,
+	) -> Result<T::AssetBalance, DispatchError> {
+		ensure!(amount_in > Zero::zero(), Error::<T>::ZeroAmount);
+		if let Some(amount_out_min) = amount_out_min {
+			ensure!(amount_out_min > Zero::zero(), Error::<T>::ZeroAmount);
+		}
+		let mut path = sp_std::vec::Vec::new();
+		path.push(T::MultiAssetIdConverter::get_native());
+		path.push(T::MultiAssetIdConverter::into_multiasset_id(&asset_id));
+		let path = path.try_into().expect(
+			"`MaxSwapPathLength` is ensured by to be greater than 2; pushed only twice; qed",
+		);
+
+		// convert `amount_in` from native balance type, to asset balance type
+		let amount_in = Self::convert_native_balance_to_asset_balance(amount_in)?;
+
+		// calculate the amount we should receive
+		let amounts = Self::get_amounts_out(&amount_in, &path)?;
+		let amount_out =
+			*amounts.last().defensive_ok_or("get_amounts_out() returned an empty result")?;
+		if let Some(amount_out_min) = amount_out_min {
+			ensure!(amount_out >= amount_out_min, Error::<T>::ProvidedMaximumNotSufficientForSwap);
+		}
 
-		Ok(amount_in)
+		Self::do_swap(sender, &amounts, path, send_to, keep_alive)?;
+		Ok(amount_out)
 	}
 }
 
@@ -1167,3 +1233,5 @@ sp_api::decl_runtime_apis! {
 		fn get_reserves(asset1: AssetId, asset2: AssetId) -> Option<(Balance, Balance)>;
 	}
 }
+
+sp_core::generate_feature_enabled_macro!(runtime_benchmarks_enabled, feature = "runtime-benchmarks", $);
diff --git a/substrate/frame/asset-conversion/src/tests.rs b/substrate/frame/asset-conversion/src/tests.rs
index 4b166c5cb44c53b399cce8250063113003171979..a4c02f365bc08e0ce002b62e4f3e05452cab4460 100644
--- a/substrate/frame/asset-conversion/src/tests.rs
+++ b/substrate/frame/asset-conversion/src/tests.rs
@@ -356,7 +356,7 @@ fn add_tiny_liquidity_leads_to_insufficient_liquidity_minted_error() {
 				1,
 				user
 			),
-			Error::<Test>::AmountLessThanMinimal
+			Error::<Test>::AmountOneLessThanMinimal
 		);
 
 		assert_noop!(
diff --git a/substrate/frame/support/src/traits/tokens/fungibles/mod.rs b/substrate/frame/support/src/traits/tokens/fungibles/mod.rs
index 10ca82b874c5b5cdaeae81d972145b4c9b692f6a..8070d916fb33079ae728040cb93648f8398f9ab5 100644
--- a/substrate/frame/support/src/traits/tokens/fungibles/mod.rs
+++ b/substrate/frame/support/src/traits/tokens/fungibles/mod.rs
@@ -36,5 +36,5 @@ pub use hold::{
 pub use imbalance::{Credit, Debt, HandleImbalanceDrop, Imbalance};
 pub use lifetime::{Create, Destroy};
 pub use regular::{
-	Balanced, DecreaseIssuance, Dust, IncreaseIssuance, Inspect, Mutate, SwapForNative, Unbalanced,
+	Balanced, DecreaseIssuance, Dust, IncreaseIssuance, Inspect, Mutate, SwapNative, Unbalanced,
 };
diff --git a/substrate/frame/support/src/traits/tokens/fungibles/regular.rs b/substrate/frame/support/src/traits/tokens/fungibles/regular.rs
index 089dc38ad690eaada9757c98a017168932fcfe8a..3df0a6b6d3defe9fbd4a7afa47ef17f668ea93e0 100644
--- a/substrate/frame/support/src/traits/tokens/fungibles/regular.rs
+++ b/substrate/frame/support/src/traits/tokens/fungibles/regular.rs
@@ -584,9 +584,16 @@ pub trait Balanced<AccountId>: Inspect<AccountId> + Unbalanced<AccountId> {
 	fn done_withdraw(_asset: Self::AssetId, _who: &AccountId, _amount: Self::Balance) {}
 }
 
-/// Use an on-chain exchange to convert the asset to the equivalent in native tokens.
-pub trait SwapForNative<Origin, AccountId, Balance, AssetBalance, AssetId> {
-	// If successful returns the amount in native tokens.
+/// Trait for providing methods to swap between the chain's native token and other asset classes.
+pub trait SwapNative<Origin, AccountId, Balance, AssetBalance, AssetId> {
+	/// Take an `asset_id` and swap some amount for `amount_out` of the chain's native asset. If an
+	/// `amount_in_max` is specified, it will return an error if acquiring `amount_out` would be
+	/// too costly.
+	///
+	/// Withdraws `asset_id` from `sender`, deposits native asset to `send_to`, respecting
+	/// `keep_alive`.
+	///
+	/// If successful returns the amount of the `asset_id` taken to provide `amount_out`.
 	fn swap_tokens_for_exact_native(
 		sender: AccountId,
 		asset_id: AssetId,
@@ -595,4 +602,21 @@ pub trait SwapForNative<Origin, AccountId, Balance, AssetBalance, AssetId> {
 		send_to: AccountId,
 		keep_alive: bool,
 	) -> Result<AssetBalance, DispatchError>;
+
+	/// Take an `asset_id` and swap `amount_in` of the chain's native asset for it. If an
+	/// `amount_out_min` is specified, it will return an error if it is unable to acquire the amount
+	/// desired.
+	///
+	/// Withdraws native asset from `sender`, deposits `asset_id` to `send_to`, respecting
+	/// `keep_alive`.
+	///
+	/// If successful, returns the amount of `asset_id` acquired for the `amount_in`.
+	fn swap_exact_native_for_tokens(
+		sender: AccountId,
+		asset_id: AssetId,
+		amount_in: Balance,
+		amount_out_min: Option<AssetBalance>,
+		send_to: AccountId,
+		keep_alive: bool,
+	) -> Result<AssetBalance, DispatchError>;
 }
diff --git a/substrate/frame/transaction-payment/asset-conversion-tx-payment/Cargo.toml b/substrate/frame/transaction-payment/asset-conversion-tx-payment/Cargo.toml
new file mode 100644
index 0000000000000000000000000000000000000000..8f9e1af04a76a45f26a238f3ca91227e43cb70b9
--- /dev/null
+++ b/substrate/frame/transaction-payment/asset-conversion-tx-payment/Cargo.toml
@@ -0,0 +1,46 @@
+[package]
+name = "pallet-asset-conversion-tx-payment"
+version = "4.0.0-dev"
+authors = ["Parity Technologies <admin@parity.io>"]
+edition = "2021"
+license = "Apache-2.0"
+homepage = "https://substrate.io"
+repository = "https://github.com/paritytech/substrate/"
+description = "Pallet to manage transaction payments in assets by converting them to native assets."
+readme = "README.md"
+
+[package.metadata.docs.rs]
+targets = ["x86_64-unknown-linux-gnu"]
+
+[dependencies]
+# Substrate dependencies
+sp-runtime = { version = "24.0.0", default-features = false, path = "../../../primitives/runtime" }
+sp-std = { version = "8.0.0", default-features = false, path = "../../../primitives/std" }
+frame-support = { version = "4.0.0-dev", default-features = false, path = "../../support" }
+frame-system = { version = "4.0.0-dev", default-features = false, path = "../../system" }
+pallet-transaction-payment = { version = "4.0.0-dev", default-features = false, path = ".." }
+codec = { package = "parity-scale-codec", version = "3.2.2", default-features = false, features = ["derive"] }
+scale-info = { version = "2.5.0", default-features = false, features = ["derive"] }
+
+[dev-dependencies]
+sp-core = { version = "21.0.0", default-features = false, path = "../../../primitives/core" }
+sp-io = { version = "23.0.0", default-features = false, path = "../../../primitives/io" }
+sp-storage = { version = "13.0.0", default-features = false, path = "../../../primitives/storage" }
+pallet-assets = { version = "4.0.0-dev", path = "../../assets" }
+pallet-balances = { version = "4.0.0-dev", path = "../../balances" }
+pallet-asset-conversion = { version = "4.0.0-dev", path = "../../asset-conversion" }
+
+[features]
+default = ["std"]
+std = [
+	"scale-info/std",
+	"codec/std",
+	"sp-std/std",
+	"sp-runtime/std",
+	"frame-support/std",
+	"frame-system/std",
+	"sp-io/std",
+	"sp-core/std",
+	"pallet-transaction-payment/std",
+]
+try-runtime = ["frame-support/try-runtime"]
diff --git a/substrate/frame/transaction-payment/asset-conversion-tx-payment/README.md b/substrate/frame/transaction-payment/asset-conversion-tx-payment/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..eccba773673e690a6d415a4c61d267ba75a1c12e
--- /dev/null
+++ b/substrate/frame/transaction-payment/asset-conversion-tx-payment/README.md
@@ -0,0 +1,21 @@
+# pallet-asset-conversion-tx-payment
+
+## Asset Conversion Transaction Payment Pallet
+
+This pallet allows runtimes that include it to pay for transactions in assets other than the
+native token of the chain.
+
+### Overview
+It does this by extending transactions to include an optional `AssetId` that specifies the asset
+to be used for payment (defaulting to the native token on `None`). It expects an
+[`OnChargeAssetTransaction`] implementation analogously to [`pallet-transaction-payment`]. The
+included [`AssetConversionAdapter`] (implementing [`OnChargeAssetTransaction`]) determines the fee
+amount by converting the fee calculated by [`pallet-transaction-payment`] into the desired
+asset.
+
+### Integration
+This pallet wraps FRAME's transaction payment pallet and functions as a replacement. This means
+you should include both pallets in your `construct_runtime` macro, but only include this
+pallet's [`SignedExtension`] ([`ChargeAssetTxPayment`]).
+
+License: Apache-2.0
diff --git a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/lib.rs b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/lib.rs
new file mode 100644
index 0000000000000000000000000000000000000000..7f6da90dfe95f60cfd901e381f3163f9bc912983
--- /dev/null
+++ b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/lib.rs
@@ -0,0 +1,351 @@
+// 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.
+
+//! # Asset Conversion Transaction Payment Pallet
+//!
+//! This pallet allows runtimes that include it to pay for transactions in assets other than the
+//! chain's native asset.
+//!
+//! ## Overview
+//!
+//! This pallet provides a `SignedExtension` with an optional `AssetId` that specifies the asset
+//! to be used for payment (defaulting to the native token on `None`). It expects an
+//! [`OnChargeAssetTransaction`] implementation analogous to [`pallet-transaction-payment`]. The
+//! included [`AssetConversionAdapter`] (implementing [`OnChargeAssetTransaction`]) determines the
+//! fee amount by converting the fee calculated by [`pallet-transaction-payment`] in the native
+//! asset into the amount required of the specified asset.
+//!
+//! ## Pallet API
+//!
+//! This pallet does not have any dispatchable calls or storage. It wraps FRAME's Transaction
+//! Payment pallet and functions as a replacement. This means you should include both pallets in
+//! your `construct_runtime` macro, but only include this pallet's [`SignedExtension`]
+//! ([`ChargeAssetTxPayment`]).
+//!
+//! ## Terminology
+//!
+//! - Native Asset or Native Currency: The asset that a chain considers native, as in its default
+//!   for transaction fee payment, deposits, inflation, etc.
+//! - Other assets: Other assets that may exist on chain, for example under the Assets pallet.
+
+#![cfg_attr(not(feature = "std"), no_std)]
+
+use sp_std::prelude::*;
+
+use codec::{Decode, Encode};
+use frame_support::{
+	dispatch::{DispatchInfo, DispatchResult, PostDispatchInfo},
+	traits::{
+		tokens::fungibles::{Balanced, Inspect},
+		IsType,
+	},
+	DefaultNoBound,
+};
+use pallet_transaction_payment::OnChargeTransaction;
+use scale_info::TypeInfo;
+use sp_runtime::{
+	traits::{DispatchInfoOf, Dispatchable, PostDispatchInfoOf, SignedExtension, Zero},
+	transaction_validity::{
+		InvalidTransaction, TransactionValidity, TransactionValidityError, ValidTransaction,
+	},
+	FixedPointOperand,
+};
+
+#[cfg(test)]
+mod mock;
+#[cfg(test)]
+mod tests;
+
+mod payment;
+pub use payment::*;
+
+/// Type aliases used for interaction with `OnChargeTransaction`.
+pub(crate) type OnChargeTransactionOf<T> =
+	<T as pallet_transaction_payment::Config>::OnChargeTransaction;
+/// Balance type alias for balances of the chain's native asset.
+pub(crate) type BalanceOf<T> = <OnChargeTransactionOf<T> as OnChargeTransaction<T>>::Balance;
+/// Liquidity info type alias.
+pub(crate) type LiquidityInfoOf<T> =
+	<OnChargeTransactionOf<T> as OnChargeTransaction<T>>::LiquidityInfo;
+
+/// Balance type alias for balances of assets that implement the `fungibles` trait.
+pub(crate) type AssetBalanceOf<T> =
+	<<T as Config>::Fungibles as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
+/// Type alias for Asset IDs.
+pub(crate) type AssetIdOf<T> =
+	<<T as Config>::Fungibles as Inspect<<T as frame_system::Config>::AccountId>>::AssetId;
+
+/// Type alias for the interaction of balances with `OnChargeAssetTransaction`.
+pub(crate) type ChargeAssetBalanceOf<T> =
+	<<T as Config>::OnChargeAssetTransaction as OnChargeAssetTransaction<T>>::Balance;
+/// Type alias for Asset IDs in their interaction with `OnChargeAssetTransaction`.
+pub(crate) type ChargeAssetIdOf<T> =
+	<<T as Config>::OnChargeAssetTransaction as OnChargeAssetTransaction<T>>::AssetId;
+/// Liquidity info type alias for interaction with `OnChargeAssetTransaction`.
+pub(crate) type ChargeAssetLiquidityOf<T> =
+	<<T as Config>::OnChargeAssetTransaction as OnChargeAssetTransaction<T>>::LiquidityInfo;
+
+/// Used to pass the initial payment info from pre- to post-dispatch.
+#[derive(Encode, Decode, DefaultNoBound, TypeInfo)]
+pub enum InitialPayment<T: Config> {
+	/// No initial fee was paid.
+	#[default]
+	Nothing,
+	/// The initial fee was paid in the native currency.
+	Native(LiquidityInfoOf<T>),
+	/// The initial fee was paid in an asset.
+	Asset((LiquidityInfoOf<T>, BalanceOf<T>, AssetBalanceOf<T>)),
+}
+
+pub use pallet::*;
+
+#[frame_support::pallet]
+pub mod pallet {
+	use super::*;
+
+	#[pallet::config]
+	pub trait Config: frame_system::Config + pallet_transaction_payment::Config {
+		/// The overarching event type.
+		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
+		/// The fungibles instance used to pay for transactions in assets.
+		type Fungibles: Balanced<Self::AccountId>;
+		/// The actual transaction charging logic that charges the fees.
+		type OnChargeAssetTransaction: OnChargeAssetTransaction<Self>;
+	}
+
+	#[pallet::pallet]
+	pub struct Pallet<T>(_);
+
+	#[pallet::event]
+	#[pallet::generate_deposit(pub(super) fn deposit_event)]
+	pub enum Event<T: Config> {
+		/// A transaction fee `actual_fee`, of which `tip` was added to the minimum inclusion fee,
+		/// has been paid by `who` in an asset `asset_id`.
+		AssetTxFeePaid {
+			who: T::AccountId,
+			actual_fee: AssetBalanceOf<T>,
+			tip: BalanceOf<T>,
+			asset_id: ChargeAssetIdOf<T>,
+		},
+		/// A swap of the refund in native currency back to asset failed.
+		AssetRefundFailed { native_amount_kept: BalanceOf<T> },
+	}
+}
+
+/// Require payment for transaction inclusion and optionally include a tip to gain additional
+/// priority in the queue. Allows paying via both `Currency` as well as `fungibles::Balanced`.
+///
+/// Wraps the transaction logic in [`pallet_transaction_payment`] and extends it with assets.
+/// An asset ID of `None` falls back to the underlying transaction payment logic via the native
+/// currency.
+#[derive(Encode, Decode, Clone, Eq, PartialEq, TypeInfo)]
+#[scale_info(skip_type_params(T))]
+pub struct ChargeAssetTxPayment<T: Config> {
+	#[codec(compact)]
+	tip: BalanceOf<T>,
+	asset_id: Option<ChargeAssetIdOf<T>>,
+}
+
+impl<T: Config> ChargeAssetTxPayment<T>
+where
+	T::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
+	AssetBalanceOf<T>: Send + Sync + FixedPointOperand,
+	BalanceOf<T>: Send
+		+ Sync
+		+ FixedPointOperand
+		+ Into<ChargeAssetBalanceOf<T>>
+		+ From<ChargeAssetLiquidityOf<T>>,
+	ChargeAssetIdOf<T>: Send + Sync,
+{
+	/// Utility constructor. Used only in client/factory code.
+	pub fn from(tip: BalanceOf<T>, asset_id: Option<ChargeAssetIdOf<T>>) -> Self {
+		Self { tip, asset_id }
+	}
+
+	/// Fee withdrawal logic that dispatches to either `OnChargeAssetTransaction` or
+	/// `OnChargeTransaction`.
+	fn withdraw_fee(
+		&self,
+		who: &T::AccountId,
+		call: &T::RuntimeCall,
+		info: &DispatchInfoOf<T::RuntimeCall>,
+		len: usize,
+	) -> Result<(BalanceOf<T>, InitialPayment<T>), TransactionValidityError> {
+		let fee = pallet_transaction_payment::Pallet::<T>::compute_fee(len as u32, info, self.tip);
+		debug_assert!(self.tip <= fee, "tip should be included in the computed fee");
+		if fee.is_zero() {
+			Ok((fee, InitialPayment::Nothing))
+		} else if let Some(asset_id) = self.asset_id {
+			T::OnChargeAssetTransaction::withdraw_fee(
+				who,
+				call,
+				info,
+				asset_id,
+				fee.into(),
+				self.tip.into(),
+			)
+			.map(|(used_for_fee, received_exchanged, asset_consumed)| {
+				(
+					fee,
+					InitialPayment::Asset((
+						used_for_fee.into(),
+						received_exchanged.into(),
+						asset_consumed.into(),
+					)),
+				)
+			})
+		} else {
+			<OnChargeTransactionOf<T> as OnChargeTransaction<T>>::withdraw_fee(
+				who, call, info, fee, self.tip,
+			)
+			.map(|i| (fee, InitialPayment::Native(i)))
+			.map_err(|_| -> TransactionValidityError { InvalidTransaction::Payment.into() })
+		}
+	}
+}
+
+impl<T: Config> sp_std::fmt::Debug for ChargeAssetTxPayment<T> {
+	#[cfg(feature = "std")]
+	fn fmt(&self, f: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
+		write!(f, "ChargeAssetTxPayment<{:?}, {:?}>", self.tip, self.asset_id.encode())
+	}
+	#[cfg(not(feature = "std"))]
+	fn fmt(&self, _: &mut sp_std::fmt::Formatter) -> sp_std::fmt::Result {
+		Ok(())
+	}
+}
+
+impl<T: Config> SignedExtension for ChargeAssetTxPayment<T>
+where
+	T::RuntimeCall: Dispatchable<Info = DispatchInfo, PostInfo = PostDispatchInfo>,
+	AssetBalanceOf<T>: Send + Sync + FixedPointOperand,
+	BalanceOf<T>: Send
+		+ Sync
+		+ From<u64>
+		+ FixedPointOperand
+		+ Into<ChargeAssetBalanceOf<T>>
+		+ Into<ChargeAssetLiquidityOf<T>>
+		+ From<ChargeAssetLiquidityOf<T>>,
+	ChargeAssetIdOf<T>: Send + Sync,
+{
+	const IDENTIFIER: &'static str = "ChargeAssetTxPayment";
+	type AccountId = T::AccountId;
+	type Call = T::RuntimeCall;
+	type AdditionalSigned = ();
+	type Pre = (
+		// tip
+		BalanceOf<T>,
+		// who paid the fee
+		Self::AccountId,
+		// imbalance resulting from withdrawing the fee
+		InitialPayment<T>,
+		// asset_id for the transaction payment
+		Option<ChargeAssetIdOf<T>>,
+	);
+
+	fn additional_signed(&self) -> sp_std::result::Result<(), TransactionValidityError> {
+		Ok(())
+	}
+
+	fn validate(
+		&self,
+		who: &Self::AccountId,
+		call: &Self::Call,
+		info: &DispatchInfoOf<Self::Call>,
+		len: usize,
+	) -> TransactionValidity {
+		use pallet_transaction_payment::ChargeTransactionPayment;
+		let (fee, _) = self.withdraw_fee(who, call, info, len)?;
+		let priority = ChargeTransactionPayment::<T>::get_priority(info, len, self.tip, fee);
+		Ok(ValidTransaction { priority, ..Default::default() })
+	}
+
+	fn pre_dispatch(
+		self,
+		who: &Self::AccountId,
+		call: &Self::Call,
+		info: &DispatchInfoOf<Self::Call>,
+		len: usize,
+	) -> Result<Self::Pre, TransactionValidityError> {
+		let (_fee, initial_payment) = self.withdraw_fee(who, call, info, len)?;
+		Ok((self.tip, who.clone(), initial_payment, self.asset_id))
+	}
+
+	fn post_dispatch(
+		pre: Option<Self::Pre>,
+		info: &DispatchInfoOf<Self::Call>,
+		post_info: &PostDispatchInfoOf<Self::Call>,
+		len: usize,
+		result: &DispatchResult,
+	) -> Result<(), TransactionValidityError> {
+		if let Some((tip, who, initial_payment, asset_id)) = pre {
+			match initial_payment {
+				InitialPayment::Native(already_withdrawn) => {
+					debug_assert!(
+						asset_id.is_none(),
+						"For that payment type the `asset_id` should be None"
+					);
+					pallet_transaction_payment::ChargeTransactionPayment::<T>::post_dispatch(
+						Some((tip, who, already_withdrawn)),
+						info,
+						post_info,
+						len,
+						result,
+					)?;
+				},
+				InitialPayment::Asset(already_withdrawn) => {
+					debug_assert!(
+						asset_id.is_some(),
+						"For that payment type the `asset_id` should be set"
+					);
+					let actual_fee = pallet_transaction_payment::Pallet::<T>::compute_actual_fee(
+						len as u32, info, post_info, tip,
+					);
+
+					if let Some(asset_id) = asset_id {
+						let (used_for_fee, received_exchanged, asset_consumed) = already_withdrawn;
+						let converted_fee = T::OnChargeAssetTransaction::correct_and_deposit_fee(
+							&who,
+							info,
+							post_info,
+							actual_fee.into(),
+							tip.into(),
+							used_for_fee.into(),
+							received_exchanged.into(),
+							asset_id,
+							asset_consumed.into(),
+						)?;
+
+						Pallet::<T>::deposit_event(Event::<T>::AssetTxFeePaid {
+							who,
+							actual_fee: converted_fee,
+							tip,
+							asset_id,
+						});
+					}
+				},
+				InitialPayment::Nothing => {
+					// `actual_fee` should be zero here for any signed extrinsic. It would be
+					// non-zero here in case of unsigned extrinsics as they don't pay fees but
+					// `compute_actual_fee` is not aware of them. In both cases it's fine to just
+					// move ahead without adjusting the fee, though, so we do nothing.
+					debug_assert!(tip.is_zero(), "tip should be zero if initial fee was zero.");
+				},
+			}
+		}
+
+		Ok(())
+	}
+}
diff --git a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/mock.rs b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/mock.rs
new file mode 100644
index 0000000000000000000000000000000000000000..6e05ec6163ddb36e842ae228e20051e19a203205
--- /dev/null
+++ b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/mock.rs
@@ -0,0 +1,274 @@
+// 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 super::*;
+use crate as pallet_asset_conversion_tx_payment;
+
+use codec;
+use frame_support::{
+	dispatch::DispatchClass,
+	instances::Instance2,
+	ord_parameter_types,
+	pallet_prelude::*,
+	parameter_types,
+	traits::{AsEnsureOriginWithArg, ConstU32, ConstU64, ConstU8, Imbalance, OnUnbalanced},
+	weights::{Weight, WeightToFee as WeightToFeeT},
+	PalletId,
+};
+use frame_system as system;
+use frame_system::{EnsureRoot, EnsureSignedBy};
+use pallet_asset_conversion::{NativeOrAssetId, NativeOrAssetIdConverter};
+use pallet_transaction_payment::CurrencyAdapter;
+use sp_core::H256;
+use sp_runtime::{
+	testing::Header,
+	traits::{AccountIdConversion, BlakeTwo256, IdentityLookup, SaturatedConversion},
+	Permill,
+};
+
+type UncheckedExtrinsic = frame_system::mocking::MockUncheckedExtrinsic<Runtime>;
+type Block = frame_system::mocking::MockBlock<Runtime>;
+type Balance = u64;
+type AccountId = u64;
+
+frame_support::construct_runtime!(
+	pub enum Runtime where
+		Block = Block,
+		NodeBlock = Block,
+		UncheckedExtrinsic = UncheckedExtrinsic,
+	{
+		System: system,
+		Balances: pallet_balances,
+		TransactionPayment: pallet_transaction_payment,
+		Assets: pallet_assets,
+		PoolAssets: pallet_assets::<Instance2>,
+		AssetConversion: pallet_asset_conversion,
+		AssetTxPayment: pallet_asset_conversion_tx_payment,
+	}
+);
+
+parameter_types! {
+	pub(crate) static ExtrinsicBaseWeight: Weight = Weight::zero();
+}
+
+pub struct BlockWeights;
+impl Get<frame_system::limits::BlockWeights> for BlockWeights {
+	fn get() -> frame_system::limits::BlockWeights {
+		frame_system::limits::BlockWeights::builder()
+			.base_block(Weight::zero())
+			.for_class(DispatchClass::all(), |weights| {
+				weights.base_extrinsic = ExtrinsicBaseWeight::get().into();
+			})
+			.for_class(DispatchClass::non_mandatory(), |weights| {
+				weights.max_total = Weight::from_parts(1024, u64::MAX).into();
+			})
+			.build_or_panic()
+	}
+}
+
+parameter_types! {
+	pub static WeightToFee: u64 = 1;
+	pub static TransactionByteFee: u64 = 1;
+}
+
+impl frame_system::Config for Runtime {
+	type BaseCallFilter = frame_support::traits::Everything;
+	type BlockWeights = BlockWeights;
+	type BlockLength = ();
+	type DbWeight = ();
+	type RuntimeOrigin = RuntimeOrigin;
+	type Index = u64;
+	type BlockNumber = u64;
+	type RuntimeCall = RuntimeCall;
+	type Hash = H256;
+	type Hashing = BlakeTwo256;
+	type AccountId = AccountId;
+	type Lookup = IdentityLookup<Self::AccountId>;
+	type Header = Header;
+	type RuntimeEvent = RuntimeEvent;
+	type BlockHashCount = ConstU64<250>;
+	type Version = ();
+	type PalletInfo = PalletInfo;
+	type AccountData = pallet_balances::AccountData<u64>;
+	type OnNewAccount = ();
+	type OnKilledAccount = ();
+	type SystemWeightInfo = ();
+	type SS58Prefix = ();
+	type OnSetCode = ();
+	type MaxConsumers = ConstU32<16>;
+}
+
+parameter_types! {
+	pub const ExistentialDeposit: u64 = 10;
+}
+
+impl pallet_balances::Config for Runtime {
+	type Balance = Balance;
+	type RuntimeEvent = RuntimeEvent;
+	type DustRemoval = ();
+	type ExistentialDeposit = ConstU64<10>;
+	type AccountStore = System;
+	type MaxLocks = ();
+	type WeightInfo = ();
+	type MaxReserves = ConstU32<50>;
+	type ReserveIdentifier = [u8; 8];
+	type FreezeIdentifier = ();
+	type MaxFreezes = ();
+	type RuntimeHoldReason = ();
+	type MaxHolds = ();
+}
+
+impl WeightToFeeT for WeightToFee {
+	type Balance = u64;
+
+	fn weight_to_fee(weight: &Weight) -> Self::Balance {
+		Self::Balance::saturated_from(weight.ref_time())
+			.saturating_mul(WEIGHT_TO_FEE.with(|v| *v.borrow()))
+	}
+}
+
+impl WeightToFeeT for TransactionByteFee {
+	type Balance = u64;
+
+	fn weight_to_fee(weight: &Weight) -> Self::Balance {
+		Self::Balance::saturated_from(weight.ref_time())
+			.saturating_mul(TRANSACTION_BYTE_FEE.with(|v| *v.borrow()))
+	}
+}
+
+parameter_types! {
+	pub(crate) static TipUnbalancedAmount: u64 = 0;
+	pub(crate) static FeeUnbalancedAmount: u64 = 0;
+}
+
+pub struct DealWithFees;
+impl OnUnbalanced<pallet_balances::NegativeImbalance<Runtime>> for DealWithFees {
+	fn on_unbalanceds<B>(
+		mut fees_then_tips: impl Iterator<Item = pallet_balances::NegativeImbalance<Runtime>>,
+	) {
+		if let Some(fees) = fees_then_tips.next() {
+			FeeUnbalancedAmount::mutate(|a| *a += fees.peek());
+			if let Some(tips) = fees_then_tips.next() {
+				TipUnbalancedAmount::mutate(|a| *a += tips.peek());
+			}
+		}
+	}
+}
+
+impl pallet_transaction_payment::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type OnChargeTransaction = CurrencyAdapter<Balances, DealWithFees>;
+	type WeightToFee = WeightToFee;
+	type LengthToFee = TransactionByteFee;
+	type FeeMultiplierUpdate = ();
+	type OperationalFeeMultiplier = ConstU8<5>;
+}
+
+type AssetId = u32;
+
+impl pallet_assets::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type Balance = Balance;
+	type AssetId = AssetId;
+	type AssetIdParameter = codec::Compact<AssetId>;
+	type Currency = Balances;
+	type CreateOrigin = AsEnsureOriginWithArg<frame_system::EnsureSigned<AccountId>>;
+	type ForceOrigin = EnsureRoot<AccountId>;
+	type AssetDeposit = ConstU64<2>;
+	type AssetAccountDeposit = ConstU64<2>;
+	type MetadataDepositBase = ConstU64<0>;
+	type MetadataDepositPerByte = ConstU64<0>;
+	type ApprovalDeposit = ConstU64<0>;
+	type StringLimit = ConstU32<20>;
+	type Freezer = ();
+	type Extra = ();
+	type CallbackHandle = ();
+	type WeightInfo = ();
+	type RemoveItemsLimit = ConstU32<1000>;
+	pallet_assets::runtime_benchmarks_enabled! {
+		type BenchmarkHelper = ();
+	}
+}
+
+impl pallet_assets::Config<Instance2> for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type Balance = u64;
+	type RemoveItemsLimit = ConstU32<1000>;
+	type AssetId = u32;
+	type AssetIdParameter = u32;
+	type Currency = Balances;
+	type CreateOrigin = AsEnsureOriginWithArg<EnsureSignedBy<AssetConversionOrigin, u64>>;
+	type ForceOrigin = frame_system::EnsureRoot<u64>;
+	type AssetDeposit = ConstU64<0>;
+	type AssetAccountDeposit = ConstU64<0>;
+	type MetadataDepositBase = ConstU64<0>;
+	type MetadataDepositPerByte = ConstU64<0>;
+	type ApprovalDeposit = ConstU64<0>;
+	type StringLimit = ConstU32<50>;
+	type Freezer = ();
+	type Extra = ();
+	type WeightInfo = ();
+	type CallbackHandle = ();
+	pallet_assets::runtime_benchmarks_enabled! {
+		type BenchmarkHelper = ();
+	}
+}
+
+parameter_types! {
+	pub const AssetConversionPalletId: PalletId = PalletId(*b"py/ascon");
+	pub storage AllowMultiAssetPools: bool = false;
+	// should be non-zero if AllowMultiAssetPools is true, otherwise can be zero
+	pub storage LiquidityWithdrawalFee: Permill = Permill::from_percent(0);
+	pub const MaxSwapPathLength: u32 = 4;
+}
+
+ord_parameter_types! {
+	pub const AssetConversionOrigin: u64 = AccountIdConversion::<u64>::into_account_truncating(&AssetConversionPalletId::get());
+}
+
+impl pallet_asset_conversion::Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type Currency = Balances;
+	type AssetBalance = <Self as pallet_balances::Config>::Balance;
+	type AssetId = u32;
+	type PoolAssetId = u32;
+	type Assets = Assets;
+	type PoolAssets = PoolAssets;
+	type PalletId = AssetConversionPalletId;
+	type WeightInfo = ();
+	type LPFee = ConstU32<3>; // means 0.3%
+	type PoolSetupFee = ConstU64<100>; // should be more or equal to the existential deposit
+	type PoolSetupFeeReceiver = AssetConversionOrigin;
+	type LiquidityWithdrawalFee = LiquidityWithdrawalFee;
+	type AllowMultiAssetPools = AllowMultiAssetPools;
+	type MaxSwapPathLength = MaxSwapPathLength;
+	type MintMinLiquidity = ConstU64<100>; // 100 is good enough when the main currency has 12 decimals.
+
+	type Balance = u64;
+	type HigherPrecisionBalance = u128;
+
+	type MultiAssetId = NativeOrAssetId<u32>;
+	type MultiAssetIdConverter = NativeOrAssetIdConverter<u32>;
+
+	pallet_asset_conversion::runtime_benchmarks_enabled! {
+		type BenchmarkHelper = ();
+	}
+}
+
+impl Config for Runtime {
+	type RuntimeEvent = RuntimeEvent;
+	type Fungibles = Assets;
+	type OnChargeAssetTransaction = AssetConversionAdapter<Balances, AssetConversion>;
+}
diff --git a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/payment.rs b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/payment.rs
new file mode 100644
index 0000000000000000000000000000000000000000..86b2c09e541acca51c68055954d06349d8dc56f6
--- /dev/null
+++ b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/payment.rs
@@ -0,0 +1,191 @@
+// 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.
+
+///! Traits and default implementation for paying transaction fees in assets.
+use super::*;
+use crate::Config;
+
+use codec::FullCodec;
+use frame_support::{
+	ensure,
+	traits::{fungible::Inspect, fungibles::SwapNative, tokens::Balance},
+	unsigned::TransactionValidityError,
+};
+use scale_info::TypeInfo;
+use sp_runtime::{
+	traits::{DispatchInfoOf, MaybeSerializeDeserialize, PostDispatchInfoOf, Zero},
+	transaction_validity::InvalidTransaction,
+	Saturating,
+};
+use sp_std::{fmt::Debug, marker::PhantomData};
+
+/// Handle withdrawing, refunding and depositing of transaction fees.
+pub trait OnChargeAssetTransaction<T: Config> {
+	/// The underlying integer type in which fees are calculated.
+	type Balance: Balance;
+	/// The type used to identify the assets used for transaction payment.
+	type AssetId: FullCodec + Copy + MaybeSerializeDeserialize + Debug + Default + Eq + TypeInfo;
+	/// The type used to store the intermediate values between pre- and post-dispatch.
+	type LiquidityInfo;
+
+	/// Secure the payment of the transaction fees before the transaction is executed.
+	///
+	/// Note: The `fee` already includes the `tip`.
+	fn withdraw_fee(
+		who: &T::AccountId,
+		call: &T::RuntimeCall,
+		dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
+		asset_id: Self::AssetId,
+		fee: Self::Balance,
+		tip: Self::Balance,
+	) -> Result<
+		(LiquidityInfoOf<T>, Self::LiquidityInfo, AssetBalanceOf<T>),
+		TransactionValidityError,
+	>;
+
+	/// Refund any overpaid fees and deposit the corrected amount.
+	/// The actual fee gets calculated once the transaction is executed.
+	///
+	/// Note: The `fee` already includes the `tip`.
+	///
+	/// Returns the fee and tip in the asset used for payment as (fee, tip).
+	fn correct_and_deposit_fee(
+		who: &T::AccountId,
+		dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
+		post_info: &PostDispatchInfoOf<T::RuntimeCall>,
+		corrected_fee: Self::Balance,
+		tip: Self::Balance,
+		fee_paid: LiquidityInfoOf<T>,
+		received_exchanged: Self::LiquidityInfo,
+		asset_id: Self::AssetId,
+		initial_asset_consumed: AssetBalanceOf<T>,
+	) -> Result<AssetBalanceOf<T>, TransactionValidityError>;
+}
+
+/// Implements the asset transaction for a balance to asset converter (implementing
+/// [`SwapNative`]).
+///
+/// The converter is given the complete fee in terms of the asset used for the transaction.
+pub struct AssetConversionAdapter<C, CON>(PhantomData<(C, CON)>);
+
+/// Default implementation for a runtime instantiating this pallet, an asset to native swapper.
+impl<T, C, CON> OnChargeAssetTransaction<T> for AssetConversionAdapter<C, CON>
+where
+	T: Config,
+	C: Inspect<<T as frame_system::Config>::AccountId>,
+	CON: SwapNative<T::RuntimeOrigin, T::AccountId, BalanceOf<T>, AssetBalanceOf<T>, AssetIdOf<T>>,
+	AssetIdOf<T>: FullCodec + Copy + MaybeSerializeDeserialize + Debug + Default + Eq + TypeInfo,
+	BalanceOf<T>: IsType<<C as Inspect<<T as frame_system::Config>::AccountId>>::Balance>,
+{
+	type Balance = BalanceOf<T>;
+	type AssetId = AssetIdOf<T>;
+	type LiquidityInfo = BalanceOf<T>;
+
+	/// Swap & withdraw the predicted fee from the transaction origin.
+	///
+	/// Note: The `fee` already includes the `tip`.
+	///
+	/// Returns the total amount in native currency received by exchanging the `asset_id` and the
+	/// amount in native currency used to pay the fee.
+	fn withdraw_fee(
+		who: &T::AccountId,
+		call: &T::RuntimeCall,
+		info: &DispatchInfoOf<T::RuntimeCall>,
+		asset_id: Self::AssetId,
+		fee: BalanceOf<T>,
+		tip: BalanceOf<T>,
+	) -> Result<
+		(LiquidityInfoOf<T>, Self::LiquidityInfo, AssetBalanceOf<T>),
+		TransactionValidityError,
+	> {
+		// convert the asset into native currency
+		let ed = C::minimum_balance();
+		let native_asset_required =
+			if C::balance(&who) >= ed.saturating_add(fee.into()) { fee } else { fee + ed.into() };
+
+		let asset_consumed = CON::swap_tokens_for_exact_native(
+			who.clone(),
+			asset_id,
+			native_asset_required,
+			None,
+			who.clone(),
+			true,
+		)
+		.map_err(|_| TransactionValidityError::from(InvalidTransaction::Payment))?;
+
+		ensure!(asset_consumed > Zero::zero(), InvalidTransaction::Payment);
+
+		// charge the fee in native currency
+		<T::OnChargeTransaction>::withdraw_fee(who, call, info, fee, tip)
+			.map(|r| (r, native_asset_required, asset_consumed))
+	}
+
+	/// Correct the fee and swap the refund back to asset.
+	///
+	/// Note: The `corrected_fee` already includes the `tip`.
+	/// Note: Is the ED wasn't needed, the `received_exchanged` will be equal to `fee_paid`, or
+	/// `fee_paid + ed` otherwise.
+	fn correct_and_deposit_fee(
+		who: &T::AccountId,
+		dispatch_info: &DispatchInfoOf<T::RuntimeCall>,
+		post_info: &PostDispatchInfoOf<T::RuntimeCall>,
+		corrected_fee: BalanceOf<T>,
+		tip: BalanceOf<T>,
+		fee_paid: LiquidityInfoOf<T>,
+		received_exchanged: Self::LiquidityInfo,
+		asset_id: Self::AssetId,
+		initial_asset_consumed: AssetBalanceOf<T>,
+	) -> Result<AssetBalanceOf<T>, TransactionValidityError> {
+		// Refund the native asset to the account that paid the fees (`who`).
+		// The `who` account will receive the "fee_paid - corrected_fee" refund.
+		<T::OnChargeTransaction>::correct_and_deposit_fee(
+			who,
+			dispatch_info,
+			post_info,
+			corrected_fee,
+			tip,
+			fee_paid,
+		)?;
+
+		// calculate the refund in native asset, to swap back to the desired `asset_id`
+		let swap_back = received_exchanged.saturating_sub(corrected_fee);
+		let mut asset_refund = Zero::zero();
+		if !swap_back.is_zero() {
+			// If this fails, the account might have dropped below the existential balance or there
+			// is not enough liquidity left in the pool. In that case we don't throw an error and
+			// the account will keep the native currency.
+			match CON::swap_exact_native_for_tokens(
+				who.clone(), // we already deposited the native to `who`
+				asset_id,    // we want asset_id back
+				swap_back,   // amount of the native asset to convert to `asset_id`
+				None,        // no minimum amount back
+				who.clone(), // we will refund to `who`
+				false,       // no need to keep alive
+			)
+			.ok()
+			{
+				Some(acquired) => asset_refund = acquired,
+				None => {
+					Pallet::<T>::deposit_event(Event::<T>::AssetRefundFailed {
+						native_amount_kept: swap_back,
+					});
+				},
+			}
+		}
+
+		let actual_paid = initial_asset_consumed.saturating_sub(asset_refund);
+		Ok(actual_paid)
+	}
+}
diff --git a/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs
new file mode 100644
index 0000000000000000000000000000000000000000..7cd22e305831b27bee8e14889275b544211255ff
--- /dev/null
+++ b/substrate/frame/transaction-payment/asset-conversion-tx-payment/src/tests.rs
@@ -0,0 +1,708 @@
+// 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 super::*;
+
+use frame_support::{
+	assert_ok,
+	dispatch::{DispatchInfo, PostDispatchInfo},
+	pallet_prelude::*,
+	traits::{fungible::Inspect, fungibles::Mutate},
+	weights::Weight,
+};
+use frame_system as system;
+use mock::{ExtrinsicBaseWeight, *};
+use pallet_asset_conversion::NativeOrAssetId;
+use pallet_balances::Call as BalancesCall;
+use sp_runtime::traits::StaticLookup;
+
+const CALL: &<Runtime as frame_system::Config>::RuntimeCall =
+	&RuntimeCall::Balances(BalancesCall::transfer_allow_death { dest: 2, value: 69 });
+
+pub struct ExtBuilder {
+	balance_factor: u64,
+	base_weight: Weight,
+	byte_fee: u64,
+	weight_to_fee: u64,
+}
+
+impl Default for ExtBuilder {
+	fn default() -> Self {
+		Self {
+			balance_factor: 1,
+			base_weight: Weight::from_parts(0, 0),
+			byte_fee: 1,
+			weight_to_fee: 1,
+		}
+	}
+}
+
+impl ExtBuilder {
+	pub fn base_weight(mut self, base_weight: Weight) -> Self {
+		self.base_weight = base_weight;
+		self
+	}
+	pub fn balance_factor(mut self, factor: u64) -> Self {
+		self.balance_factor = factor;
+		self
+	}
+	fn set_constants(&self) {
+		ExtrinsicBaseWeight::mutate(|v| *v = self.base_weight);
+		TRANSACTION_BYTE_FEE.with(|v| *v.borrow_mut() = self.byte_fee);
+		WEIGHT_TO_FEE.with(|v| *v.borrow_mut() = self.weight_to_fee);
+	}
+	pub fn build(self) -> sp_io::TestExternalities {
+		self.set_constants();
+		let mut t = frame_system::GenesisConfig::default().build_storage::<Runtime>().unwrap();
+		pallet_balances::GenesisConfig::<Runtime> {
+			balances: if self.balance_factor > 0 {
+				vec![
+					(1, 10 * self.balance_factor),
+					(2, 20 * self.balance_factor),
+					(3, 30 * self.balance_factor),
+					(4, 40 * self.balance_factor),
+					(5, 50 * self.balance_factor),
+					(6, 60 * self.balance_factor),
+				]
+			} else {
+				vec![]
+			},
+		}
+		.assimilate_storage(&mut t)
+		.unwrap();
+		t.into()
+	}
+}
+
+/// create a transaction info struct from weight. Handy to avoid building the whole struct.
+pub fn info_from_weight(w: Weight) -> DispatchInfo {
+	// pays_fee: Pays::Yes -- class: DispatchClass::Normal
+	DispatchInfo { weight: w, ..Default::default() }
+}
+
+fn post_info_from_weight(w: Weight) -> PostDispatchInfo {
+	PostDispatchInfo { actual_weight: Some(w), pays_fee: Default::default() }
+}
+
+fn info_from_pays(p: Pays) -> DispatchInfo {
+	DispatchInfo { pays_fee: p, ..Default::default() }
+}
+
+fn post_info_from_pays(p: Pays) -> PostDispatchInfo {
+	PostDispatchInfo { actual_weight: None, pays_fee: p }
+}
+
+fn default_post_info() -> PostDispatchInfo {
+	PostDispatchInfo { actual_weight: None, pays_fee: Default::default() }
+}
+
+fn setup_lp(asset_id: u32, balance_factor: u64) {
+	let lp_provider = 5;
+	assert_ok!(Balances::force_set_balance(
+		RuntimeOrigin::root(),
+		lp_provider,
+		10_000 * balance_factor
+	));
+	let lp_provider_account = <Runtime as system::Config>::Lookup::unlookup(lp_provider);
+	assert_ok!(Assets::mint_into(asset_id.into(), &lp_provider_account, 10_000 * balance_factor));
+
+	let token_1 = NativeOrAssetId::Native;
+	let token_2 = NativeOrAssetId::Asset(asset_id);
+	assert_ok!(AssetConversion::create_pool(RuntimeOrigin::signed(lp_provider), token_1, token_2));
+
+	assert_ok!(AssetConversion::add_liquidity(
+		RuntimeOrigin::signed(lp_provider),
+		token_1,
+		token_2,
+		1_000 * balance_factor,  // 1 desired
+		10_000 * balance_factor, // 2 desired
+		1,                       // 1 min
+		1,                       // 2 min
+		lp_provider_account,
+	));
+}
+
+const WEIGHT_5: Weight = Weight::from_parts(5, 0);
+const WEIGHT_50: Weight = Weight::from_parts(50, 0);
+const WEIGHT_100: Weight = Weight::from_parts(100, 0);
+
+#[test]
+fn transaction_payment_in_native_possible() {
+	let base_weight = 5;
+	let balance_factor = 100;
+	ExtBuilder::default()
+		.balance_factor(balance_factor)
+		.base_weight(Weight::from_parts(base_weight, 0))
+		.build()
+		.execute_with(|| {
+			let len = 10;
+			let pre = ChargeAssetTxPayment::<Runtime>::from(0, None)
+				.pre_dispatch(&1, CALL, &info_from_weight(WEIGHT_5), len)
+				.unwrap();
+			let initial_balance = 10 * balance_factor;
+			assert_eq!(Balances::free_balance(1), initial_balance - 5 - 5 - 10);
+
+			assert_ok!(ChargeAssetTxPayment::<Runtime>::post_dispatch(
+				Some(pre),
+				&info_from_weight(WEIGHT_5),
+				&default_post_info(),
+				len,
+				&Ok(())
+			));
+			assert_eq!(Balances::free_balance(1), initial_balance - 5 - 5 - 10);
+
+			let pre = ChargeAssetTxPayment::<Runtime>::from(5 /* tipped */, None)
+				.pre_dispatch(&2, CALL, &info_from_weight(WEIGHT_100), len)
+				.unwrap();
+			let initial_balance_for_2 = 20 * balance_factor;
+
+			assert_eq!(Balances::free_balance(2), initial_balance_for_2 - 5 - 10 - 100 - 5);
+			assert_ok!(ChargeAssetTxPayment::<Runtime>::post_dispatch(
+				Some(pre),
+				&info_from_weight(WEIGHT_100),
+				&post_info_from_weight(WEIGHT_50),
+				len,
+				&Ok(())
+			));
+			assert_eq!(Balances::free_balance(2), initial_balance_for_2 - 5 - 10 - 50 - 5);
+		});
+}
+
+#[test]
+fn transaction_payment_in_asset_possible() {
+	let base_weight = 5;
+	let balance_factor = 100;
+	ExtBuilder::default()
+		.balance_factor(balance_factor)
+		.base_weight(Weight::from_parts(base_weight, 0))
+		.build()
+		.execute_with(|| {
+			// create the asset
+			let asset_id = 1;
+			let min_balance = 2;
+			assert_ok!(Assets::force_create(
+				RuntimeOrigin::root(),
+				asset_id.into(),
+				42,   /* owner */
+				true, /* is_sufficient */
+				min_balance
+			));
+
+			// mint into the caller account
+			let caller = 1;
+			let beneficiary = <Runtime as system::Config>::Lookup::unlookup(caller);
+			let balance = 1000;
+
+			assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance));
+			assert_eq!(Assets::balance(asset_id, caller), balance);
+
+			let len = 10;
+			let tx_weight = 5;
+
+			setup_lp(asset_id, balance_factor);
+
+			let fee_in_native = base_weight + tx_weight + len as u64;
+			let input_quote = AssetConversion::quote_price_tokens_for_exact_tokens(
+				NativeOrAssetId::Asset(asset_id),
+				NativeOrAssetId::Native,
+				fee_in_native,
+				true,
+			);
+			assert_eq!(input_quote, Some(201));
+
+			let fee_in_asset = input_quote.unwrap();
+			assert_eq!(Assets::balance(asset_id, caller), balance);
+
+			let pre = ChargeAssetTxPayment::<Runtime>::from(0, Some(asset_id))
+				.pre_dispatch(&caller, CALL, &info_from_weight(WEIGHT_5), len)
+				.unwrap();
+			// assert that native balance is not used
+			assert_eq!(Balances::free_balance(caller), 10 * balance_factor);
+
+			// check that fee was charged in the given asset
+			assert_eq!(Assets::balance(asset_id, caller), balance - fee_in_asset);
+
+			assert_ok!(ChargeAssetTxPayment::<Runtime>::post_dispatch(
+				Some(pre),
+				&info_from_weight(WEIGHT_5), // estimated tx weight
+				&default_post_info(),        // weight actually used == estimated
+				len,
+				&Ok(())
+			));
+
+			assert_eq!(Assets::balance(asset_id, caller), balance - fee_in_asset);
+			assert_eq!(TipUnbalancedAmount::get(), 0);
+			assert_eq!(FeeUnbalancedAmount::get(), fee_in_native);
+		});
+}
+
+#[test]
+fn transaction_payment_in_asset_fails_if_no_pool_for_that_asset() {
+	let base_weight = 5;
+	let balance_factor = 100;
+	ExtBuilder::default()
+		.balance_factor(balance_factor)
+		.base_weight(Weight::from_parts(base_weight, 0))
+		.build()
+		.execute_with(|| {
+			// create the asset
+			let asset_id = 1;
+			let min_balance = 2;
+			assert_ok!(Assets::force_create(
+				RuntimeOrigin::root(),
+				asset_id.into(),
+				42,   /* owner */
+				true, /* is_sufficient */
+				min_balance
+			));
+
+			// mint into the caller account
+			let caller = 1;
+			let beneficiary = <Runtime as system::Config>::Lookup::unlookup(caller);
+			let balance = 1000;
+
+			assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance));
+			assert_eq!(Assets::balance(asset_id, caller), balance);
+
+			let len = 10;
+			let pre = ChargeAssetTxPayment::<Runtime>::from(0, Some(asset_id)).pre_dispatch(
+				&caller,
+				CALL,
+				&info_from_weight(WEIGHT_5),
+				len,
+			);
+
+			// As there is no pool in the dex set up for this asset, conversion should fail.
+			assert!(pre.is_err());
+		});
+}
+
+#[test]
+fn transaction_payment_without_fee() {
+	let base_weight = 5;
+	let balance_factor = 100;
+	ExtBuilder::default()
+		.balance_factor(balance_factor)
+		.base_weight(Weight::from_parts(base_weight, 0))
+		.build()
+		.execute_with(|| {
+			let caller = 1;
+
+			// create the asset
+			let asset_id = 1;
+			let balance = 1000;
+			let min_balance = 2;
+
+			assert_ok!(Assets::force_create(
+				RuntimeOrigin::root(),
+				asset_id.into(),
+				42,   /* owner */
+				true, /* is_sufficient */
+				min_balance,
+			));
+
+			setup_lp(asset_id, balance_factor);
+
+			// mint into the caller account
+			let beneficiary = <Runtime as system::Config>::Lookup::unlookup(caller);
+			assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance));
+			assert_eq!(Assets::balance(asset_id, caller), balance);
+
+			let weight = 5;
+			let len = 10;
+			let fee_in_native = base_weight + weight + len as u64;
+			let input_quote = AssetConversion::quote_price_tokens_for_exact_tokens(
+				NativeOrAssetId::Asset(asset_id),
+				NativeOrAssetId::Native,
+				fee_in_native,
+				true,
+			);
+			assert_eq!(input_quote, Some(201));
+
+			let fee_in_asset = input_quote.unwrap();
+			let pre = ChargeAssetTxPayment::<Runtime>::from(0, Some(asset_id))
+				.pre_dispatch(&caller, CALL, &info_from_weight(WEIGHT_5), len)
+				.unwrap();
+
+			// assert that native balance is not used
+			assert_eq!(Balances::free_balance(caller), 10 * balance_factor);
+			// check that fee was charged in the given asset
+			assert_eq!(Assets::balance(asset_id, caller), balance - fee_in_asset);
+
+			let refund = AssetConversion::quote_price_exact_tokens_for_tokens(
+				NativeOrAssetId::Native,
+				NativeOrAssetId::Asset(asset_id),
+				fee_in_native,
+				true,
+			)
+			.unwrap();
+			assert_eq!(refund, 199);
+
+			assert_ok!(ChargeAssetTxPayment::<Runtime>::post_dispatch(
+				Some(pre),
+				&info_from_weight(WEIGHT_5),
+				&post_info_from_pays(Pays::No),
+				len,
+				&Ok(())
+			));
+
+			// caller should get refunded
+			assert_eq!(Assets::balance(asset_id, caller), balance - fee_in_asset + refund);
+			assert_eq!(Balances::free_balance(caller), 10 * balance_factor);
+		});
+}
+
+#[test]
+fn asset_transaction_payment_with_tip_and_refund() {
+	let base_weight = 5;
+	let balance_factor = 100;
+	ExtBuilder::default()
+		.balance_factor(balance_factor)
+		.base_weight(Weight::from_parts(base_weight, 0))
+		.build()
+		.execute_with(|| {
+			// create the asset
+			let asset_id = 1;
+			let min_balance = 2;
+			assert_ok!(Assets::force_create(
+				RuntimeOrigin::root(),
+				asset_id.into(),
+				42,   /* owner */
+				true, /* is_sufficient */
+				min_balance,
+			));
+
+			setup_lp(asset_id, balance_factor);
+
+			// mint into the caller account
+			let caller = 2;
+			let beneficiary = <Runtime as system::Config>::Lookup::unlookup(caller);
+			let balance = 10000;
+
+			assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance));
+			assert_eq!(Assets::balance(asset_id, caller), balance);
+
+			let weight = 100;
+			let tip = 5;
+			let len = 10;
+			let fee_in_native = base_weight + weight + len as u64 + tip;
+			let input_quote = AssetConversion::quote_price_tokens_for_exact_tokens(
+				NativeOrAssetId::Asset(asset_id),
+				NativeOrAssetId::Native,
+				fee_in_native,
+				true,
+			);
+			assert_eq!(input_quote, Some(1206));
+
+			let fee_in_asset = input_quote.unwrap();
+			let pre = ChargeAssetTxPayment::<Runtime>::from(tip, Some(asset_id))
+				.pre_dispatch(&caller, CALL, &info_from_weight(WEIGHT_100), len)
+				.unwrap();
+			assert_eq!(Assets::balance(asset_id, caller), balance - fee_in_asset);
+
+			let final_weight = 50;
+			let expected_fee = fee_in_native - final_weight - tip;
+			let expected_token_refund = AssetConversion::quote_price_exact_tokens_for_tokens(
+				NativeOrAssetId::Native,
+				NativeOrAssetId::Asset(asset_id),
+				fee_in_native - expected_fee - tip,
+				true,
+			)
+			.unwrap();
+
+			assert_ok!(ChargeAssetTxPayment::<Runtime>::post_dispatch(
+				Some(pre),
+				&info_from_weight(WEIGHT_100),
+				&post_info_from_weight(WEIGHT_50),
+				len,
+				&Ok(())
+			));
+
+			assert_eq!(TipUnbalancedAmount::get(), tip);
+			assert_eq!(FeeUnbalancedAmount::get(), expected_fee);
+
+			// caller should get refunded
+			assert_eq!(
+				Assets::balance(asset_id, caller),
+				balance - fee_in_asset + expected_token_refund
+			);
+			assert_eq!(Balances::free_balance(caller), 20 * balance_factor);
+		});
+}
+
+#[test]
+fn payment_from_account_with_only_assets() {
+	let base_weight = 5;
+	let balance_factor = 100;
+	ExtBuilder::default()
+		.balance_factor(balance_factor)
+		.base_weight(Weight::from_parts(base_weight, 0))
+		.build()
+		.execute_with(|| {
+			// create the asset
+			let asset_id = 1;
+			let min_balance = 2;
+			assert_ok!(Assets::force_create(
+				RuntimeOrigin::root(),
+				asset_id.into(),
+				42,   /* owner */
+				true, /* is_sufficient */
+				min_balance,
+			));
+
+			setup_lp(asset_id, balance_factor);
+
+			// mint into the caller account
+			let caller = 333;
+			let beneficiary = <Runtime as system::Config>::Lookup::unlookup(caller);
+			let balance = 1000;
+
+			assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance));
+			assert_eq!(Assets::balance(asset_id, caller), balance);
+
+			// assert that native balance is not necessary
+			assert_eq!(Balances::free_balance(caller), 0);
+			let weight = 5;
+			let len = 10;
+
+			let fee_in_native = base_weight + weight + len as u64;
+			let ed = Balances::minimum_balance();
+			let fee_in_asset = AssetConversion::quote_price_tokens_for_exact_tokens(
+				NativeOrAssetId::Asset(asset_id),
+				NativeOrAssetId::Native,
+				fee_in_native + ed,
+				true,
+			)
+			.unwrap();
+			assert_eq!(fee_in_asset, 301);
+
+			let pre = ChargeAssetTxPayment::<Runtime>::from(0, Some(asset_id))
+				.pre_dispatch(&caller, CALL, &info_from_weight(WEIGHT_5), len)
+				.unwrap();
+			assert_eq!(Balances::free_balance(caller), ed);
+			// check that fee was charged in the given asset
+			assert_eq!(Assets::balance(asset_id, caller), balance - fee_in_asset);
+
+			let refund = AssetConversion::quote_price_exact_tokens_for_tokens(
+				NativeOrAssetId::Native,
+				NativeOrAssetId::Asset(asset_id),
+				ed,
+				true,
+			)
+			.unwrap();
+
+			assert_ok!(ChargeAssetTxPayment::<Runtime>::post_dispatch(
+				Some(pre),
+				&info_from_weight(WEIGHT_5),
+				&default_post_info(),
+				len,
+				&Ok(())
+			));
+			assert_eq!(Assets::balance(asset_id, caller), balance - fee_in_asset + refund);
+			assert_eq!(Balances::free_balance(caller), 0);
+
+			assert_eq!(TipUnbalancedAmount::get(), 0);
+			assert_eq!(FeeUnbalancedAmount::get(), fee_in_native);
+		});
+}
+
+#[test]
+fn converted_fee_is_never_zero_if_input_fee_is_not() {
+	let base_weight = 1;
+	let balance_factor = 100;
+	ExtBuilder::default()
+		.balance_factor(balance_factor)
+		.base_weight(Weight::from_parts(base_weight, 0))
+		.build()
+		.execute_with(|| {
+			// create the asset
+			let asset_id = 1;
+			let min_balance = 1;
+			assert_ok!(Assets::force_create(
+				RuntimeOrigin::root(),
+				asset_id.into(),
+				42,   /* owner */
+				true, /* is_sufficient */
+				min_balance
+			));
+
+			setup_lp(asset_id, balance_factor);
+
+			// mint into the caller account
+			let caller = 2;
+			let beneficiary = <Runtime as system::Config>::Lookup::unlookup(caller);
+			let balance = 1000;
+
+			assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance));
+			assert_eq!(Assets::balance(asset_id, caller), balance);
+
+			let weight = 1;
+			let len = 1;
+
+			// there will be no conversion when the fee is zero
+			{
+				let pre = ChargeAssetTxPayment::<Runtime>::from(0, Some(asset_id))
+					.pre_dispatch(&caller, CALL, &info_from_pays(Pays::No), len)
+					.unwrap();
+				// `Pays::No` implies there are no fees
+				assert_eq!(Assets::balance(asset_id, caller), balance);
+
+				assert_ok!(ChargeAssetTxPayment::<Runtime>::post_dispatch(
+					Some(pre),
+					&info_from_pays(Pays::No),
+					&post_info_from_pays(Pays::No),
+					len,
+					&Ok(())
+				));
+				assert_eq!(Assets::balance(asset_id, caller), balance);
+			}
+
+			// validate even a small fee gets converted to asset.
+			let fee_in_native = base_weight + weight + len as u64;
+			let fee_in_asset = AssetConversion::quote_price_tokens_for_exact_tokens(
+				NativeOrAssetId::Asset(asset_id),
+				NativeOrAssetId::Native,
+				fee_in_native,
+				true,
+			)
+			.unwrap();
+
+			let pre = ChargeAssetTxPayment::<Runtime>::from(0, Some(asset_id))
+				.pre_dispatch(&caller, CALL, &info_from_weight(Weight::from_parts(weight, 0)), len)
+				.unwrap();
+			assert_eq!(Assets::balance(asset_id, caller), balance - fee_in_asset);
+
+			assert_ok!(ChargeAssetTxPayment::<Runtime>::post_dispatch(
+				Some(pre),
+				&info_from_weight(Weight::from_parts(weight, 0)),
+				&default_post_info(),
+				len,
+				&Ok(())
+			));
+			assert_eq!(Assets::balance(asset_id, caller), balance - fee_in_asset);
+		});
+}
+
+#[test]
+fn post_dispatch_fee_is_zero_if_pre_dispatch_fee_is_zero() {
+	let base_weight = 1;
+	ExtBuilder::default()
+		.balance_factor(100)
+		.base_weight(Weight::from_parts(base_weight, 0))
+		.build()
+		.execute_with(|| {
+			// create the asset
+			let asset_id = 1;
+			let min_balance = 100;
+			assert_ok!(Assets::force_create(
+				RuntimeOrigin::root(),
+				asset_id.into(),
+				42,   /* owner */
+				true, /* is_sufficient */
+				min_balance
+			));
+
+			// mint into the caller account
+			let caller = 333;
+			let beneficiary = <Runtime as system::Config>::Lookup::unlookup(caller);
+			let balance = 1000;
+
+			assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance));
+			assert_eq!(Assets::balance(asset_id, caller), balance);
+
+			let weight = 1;
+			let len = 1;
+			let fee = base_weight + weight + len as u64;
+
+			// calculated fee is greater than 0
+			assert!(fee > 0);
+
+			let pre = ChargeAssetTxPayment::<Runtime>::from(0, Some(asset_id))
+				.pre_dispatch(&caller, CALL, &info_from_pays(Pays::No), len)
+				.unwrap();
+			// `Pays::No` implies no pre-dispatch fees
+
+			assert_eq!(Assets::balance(asset_id, caller), balance);
+
+			let (_tip, _who, initial_payment, _asset_id) = &pre;
+			let not_paying = match initial_payment {
+				&InitialPayment::Nothing => true,
+				_ => false,
+			};
+			assert!(not_paying, "initial payment should be Nothing if we pass Pays::No");
+
+			// `Pays::Yes` on post-dispatch does not mean we pay (we never charge more than the
+			// initial fee)
+			assert_ok!(ChargeAssetTxPayment::<Runtime>::post_dispatch(
+				Some(pre),
+				&info_from_pays(Pays::No),
+				&post_info_from_pays(Pays::Yes),
+				len,
+				&Ok(())
+			));
+			assert_eq!(Assets::balance(asset_id, caller), balance);
+		});
+}
+
+#[test]
+fn post_dispatch_fee_is_zero_if_unsigned_pre_dispatch_fee_is_zero() {
+	let base_weight = 1;
+	ExtBuilder::default()
+		.balance_factor(100)
+		.base_weight(Weight::from_parts(base_weight, 0))
+		.build()
+		.execute_with(|| {
+			// create the asset
+			let asset_id = 1;
+			let min_balance = 100;
+			assert_ok!(Assets::force_create(
+				RuntimeOrigin::root(),
+				asset_id.into(),
+				42,   /* owner */
+				true, /* is_sufficient */
+				min_balance
+			));
+
+			// mint into the caller account
+			let caller = 333;
+			let beneficiary = <Runtime as system::Config>::Lookup::unlookup(caller);
+			let balance = 1000;
+
+			assert_ok!(Assets::mint_into(asset_id.into(), &beneficiary, balance));
+			assert_eq!(Assets::balance(asset_id, caller), balance);
+
+			let weight = 1;
+			let len = 1;
+			ChargeAssetTxPayment::<Runtime>::pre_dispatch_unsigned(
+				CALL,
+				&info_from_weight(Weight::from_parts(weight, 0)),
+				len,
+			)
+			.unwrap();
+
+			assert_eq!(Assets::balance(asset_id, caller), balance);
+
+			// `Pays::Yes` on post-dispatch does not mean we pay (we never charge more than the
+			// initial fee)
+			assert_ok!(ChargeAssetTxPayment::<Runtime>::post_dispatch(
+				None,
+				&info_from_weight(Weight::from_parts(weight, 0)),
+				&post_info_from_pays(Pays::Yes),
+				len,
+				&Ok(())
+			));
+			assert_eq!(Assets::balance(asset_id, caller), balance);
+		});
+}
diff --git a/substrate/frame/transaction-payment/asset-tx-payment/src/lib.rs b/substrate/frame/transaction-payment/asset-tx-payment/src/lib.rs
index 4e83d8b489b7097027a80c2b2b0ae9300e0d9a15..86f3025cc313cba3ef5c92327d970f4afa4af31e 100644
--- a/substrate/frame/transaction-payment/asset-tx-payment/src/lib.rs
+++ b/substrate/frame/transaction-payment/asset-tx-payment/src/lib.rs
@@ -67,17 +67,17 @@ mod tests;
 mod payment;
 pub use payment::*;
 
-// Type aliases used for interaction with `OnChargeTransaction`.
+/// Type aliases used for interaction with `OnChargeTransaction`.
 pub(crate) type OnChargeTransactionOf<T> =
 	<T as pallet_transaction_payment::Config>::OnChargeTransaction;
-// Balance type alias.
+/// Balance type alias.
 pub(crate) type BalanceOf<T> = <OnChargeTransactionOf<T> as OnChargeTransaction<T>>::Balance;
-// Liquity info type alias.
+/// Liquidity info type alias.
 pub(crate) type LiquidityInfoOf<T> =
 	<OnChargeTransactionOf<T> as OnChargeTransaction<T>>::LiquidityInfo;
 
-// Type alias used for interaction with fungibles (assets).
-// Balance type alias.
+/// Type alias used for interaction with fungibles (assets).
+/// Balance type alias.
 pub(crate) type AssetBalanceOf<T> =
 	<<T as Config>::Fungibles as Inspect<<T as frame_system::Config>::AccountId>>::Balance;
 /// Asset id type alias.
@@ -85,25 +85,25 @@ pub(crate) type AssetIdOf<T> =
 	<<T as Config>::Fungibles as Inspect<<T as frame_system::Config>::AccountId>>::AssetId;
 
 // Type aliases used for interaction with `OnChargeAssetTransaction`.
-// Balance type alias.
+/// Balance type alias.
 pub(crate) type ChargeAssetBalanceOf<T> =
 	<<T as Config>::OnChargeAssetTransaction as OnChargeAssetTransaction<T>>::Balance;
-// Asset id type alias.
+/// Asset id type alias.
 pub(crate) type ChargeAssetIdOf<T> =
 	<<T as Config>::OnChargeAssetTransaction as OnChargeAssetTransaction<T>>::AssetId;
-// Liquity info type alias.
+/// Liquidity info type alias.
 pub(crate) type ChargeAssetLiquidityOf<T> =
 	<<T as Config>::OnChargeAssetTransaction as OnChargeAssetTransaction<T>>::LiquidityInfo;
 
 /// Used to pass the initial payment info from pre- to post-dispatch.
 #[derive(Encode, Decode, DefaultNoBound, TypeInfo)]
 pub enum InitialPayment<T: Config> {
-	/// No initial fee was payed.
+	/// No initial fee was paid.
 	#[default]
 	Nothing,
-	/// The initial fee was payed in the native currency.
+	/// The initial fee was paid in the native currency.
 	Native(LiquidityInfoOf<T>),
-	/// The initial fee was payed in an asset.
+	/// The initial fee was paid in an asset.
 	Asset(Credit<T::AccountId, T::Fungibles>),
 }
 
diff --git a/substrate/frame/transaction-payment/asset-tx-payment/src/payment.rs b/substrate/frame/transaction-payment/asset-tx-payment/src/payment.rs
index 49e78fb8bce010608e06cc1088df5ec8cee17b91..717114ab6bd03c786266d1b54fe25fa171c47d19 100644
--- a/substrate/frame/transaction-payment/asset-tx-payment/src/payment.rs
+++ b/substrate/frame/transaction-payment/asset-tx-payment/src/payment.rs
@@ -126,7 +126,7 @@ where
 			.max(min_converted_fee);
 		let can_withdraw =
 			<T::Fungibles as Inspect<T::AccountId>>::can_withdraw(asset_id, who, converted_fee);
-		if !matches!(can_withdraw, WithdrawConsequence::Success) {
+		if can_withdraw != WithdrawConsequence::Success {
 			return Err(InvalidTransaction::Payment.into())
 		}
 		<T::Fungibles as Balanced<T::AccountId>>::withdraw(