diff --git a/Cargo.lock b/Cargo.lock
index 1559ae2018065..48ac004a85970 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -840,20 +840,27 @@ version = "1.0.0"
 dependencies = [
  "assert_matches",
  "asset-hub-westend-runtime",
+ "cumulus-pallet-dmp-queue",
+ "cumulus-pallet-parachain-system",
  "frame-support",
  "frame-system",
  "integration-tests-common",
  "pallet-asset-conversion",
+ "pallet-asset-rate",
  "pallet-assets",
  "pallet-balances",
+ "pallet-treasury",
  "pallet-xcm",
  "parachains-common",
  "parity-scale-codec",
  "polkadot-core-primitives",
  "polkadot-parachain-primitives",
+ "polkadot-runtime-common",
  "polkadot-runtime-parachains",
  "sp-runtime",
  "staging-xcm",
+ "staging-xcm-builder",
+ "staging-xcm-executor",
  "xcm-emulator",
 ]
 
@@ -10567,6 +10574,7 @@ dependencies = [
 name = "pallet-treasury"
 version = "4.0.0-dev"
 dependencies = [
+ "docify",
  "frame-benchmarking",
  "frame-support",
  "frame-system",
@@ -12485,6 +12493,7 @@ dependencies = [
  "impl-trait-for-tuples",
  "libsecp256k1",
  "log",
+ "pallet-asset-rate",
  "pallet-authorship",
  "pallet-babe",
  "pallet-balances",
@@ -12519,6 +12528,7 @@ dependencies = [
  "sp-staking",
  "sp-std",
  "staging-xcm",
+ "staging-xcm-builder",
  "static_assertions",
 ]
 
@@ -13900,6 +13910,7 @@ dependencies = [
  "frame-try-runtime",
  "hex-literal",
  "log",
+ "pallet-asset-rate",
  "pallet-authority-discovery",
  "pallet-authorship",
  "pallet-babe",
@@ -19941,6 +19952,7 @@ dependencies = [
  "frame-try-runtime",
  "hex-literal",
  "log",
+ "pallet-asset-rate",
  "pallet-authority-discovery",
  "pallet-authorship",
  "pallet-babe",
diff --git a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/Cargo.toml b/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/Cargo.toml
index af9776cbcd999..bf141dafebf2c 100644
--- a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/Cargo.toml
+++ b/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/Cargo.toml
@@ -18,17 +18,24 @@ frame-system = { path = "../../../../../../substrate/frame/system", default-feat
 pallet-balances = { path = "../../../../../../substrate/frame/balances", default-features = false}
 pallet-assets = { path = "../../../../../../substrate/frame/assets", default-features = false}
 pallet-asset-conversion = { path = "../../../../../../substrate/frame/asset-conversion", default-features = false}
+pallet-treasury = { path = "../../../../../../substrate/frame/treasury", default-features = false}
+pallet-asset-rate = { path = "../../../../../../substrate/frame/asset-rate", default-features = false}
 
 # Polkadot
 polkadot-core-primitives = { path = "../../../../../../polkadot/core-primitives", default-features = false}
 polkadot-parachain-primitives = { path = "../../../../../../polkadot/parachain", default-features = false}
+polkadot-runtime-common = { path = "../../../../../../polkadot/runtime/common" }
 polkadot-runtime-parachains = { path = "../../../../../../polkadot/runtime/parachains" }
 xcm = { package = "staging-xcm", path = "../../../../../../polkadot/xcm", default-features = false}
+xcm-builder = { package = "staging-xcm-builder",  path = "../../../../../../polkadot/xcm/xcm-builder", default-features = false}
+xcm-executor = { package = "staging-xcm-executor",  path = "../../../../../../polkadot/xcm/xcm-executor", default-features = false}
 pallet-xcm = { path = "../../../../../../polkadot/xcm/pallet-xcm", default-features = false}
 
 # Cumulus
 parachains-common = { path = "../../../../common" }
 asset-hub-westend-runtime = { path = "../../../../runtimes/assets/asset-hub-westend" }
+cumulus-pallet-dmp-queue = { default-features = false, path = "../../../../../pallets/dmp-queue" }
+cumulus-pallet-parachain-system = { default-features = false, path = "../../../../../pallets/parachain-system" }
 
 # Local
 xcm-emulator = { path = "../../../../../xcm/xcm-emulator", default-features = false}
@@ -37,15 +44,21 @@ integration-tests-common = { path = "../../common", default-features = false}
 [features]
 runtime-benchmarks = [
 	"asset-hub-westend-runtime/runtime-benchmarks",
+	"cumulus-pallet-parachain-system/runtime-benchmarks",
 	"frame-support/runtime-benchmarks",
 	"frame-system/runtime-benchmarks",
 	"integration-tests-common/runtime-benchmarks",
 	"pallet-asset-conversion/runtime-benchmarks",
+	"pallet-asset-rate/runtime-benchmarks",
 	"pallet-assets/runtime-benchmarks",
 	"pallet-balances/runtime-benchmarks",
+	"pallet-treasury/runtime-benchmarks",
 	"pallet-xcm/runtime-benchmarks",
 	"parachains-common/runtime-benchmarks",
 	"polkadot-parachain-primitives/runtime-benchmarks",
+	"polkadot-runtime-common/runtime-benchmarks",
 	"polkadot-runtime-parachains/runtime-benchmarks",
 	"sp-runtime/runtime-benchmarks",
+	"xcm-builder/runtime-benchmarks",
+	"xcm-executor/runtime-benchmarks",
 ]
diff --git a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/tests/mod.rs b/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/tests/mod.rs
index b3841af0e6c38..0c9de89c5f98f 100644
--- a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/tests/mod.rs
+++ b/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/tests/mod.rs
@@ -18,3 +18,4 @@ mod send;
 mod set_xcm_versions;
 mod swap;
 mod teleport;
+mod treasury;
diff --git a/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/tests/treasury.rs b/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/tests/treasury.rs
new file mode 100644
index 0000000000000..cf06f58682da2
--- /dev/null
+++ b/cumulus/parachains/integration-tests/emulated/assets/asset-hub-westend/src/tests/treasury.rs
@@ -0,0 +1,126 @@
+// 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::*;
+use frame_support::traits::fungibles::{Create, Inspect, Mutate};
+use integration_tests_common::constants::accounts::{ALICE, BOB};
+use polkadot_runtime_common::impls::VersionedLocatableAsset;
+use xcm_executor::traits::ConvertLocation;
+
+#[test]
+fn create_and_claim_treasury_spend() {
+	const ASSET_ID: u32 = 1984;
+	const SPEND_AMOUNT: u128 = 1_000_000;
+	// treasury location from a sibling parachain.
+	let treasury_location: MultiLocation = MultiLocation::new(1, PalletInstance(37));
+	// treasury account on a sibling parachain.
+	let treasury_account =
+		asset_hub_westend_runtime::xcm_config::LocationToAccountId::convert_location(
+			&treasury_location,
+		)
+		.unwrap();
+	let asset_hub_location = MultiLocation::new(0, Parachain(AssetHubWestend::para_id().into()));
+	let root = <Westend as Chain>::RuntimeOrigin::root();
+	// asset kind to be spend from the treasury.
+	let asset_kind = VersionedLocatableAsset::V3 {
+		location: asset_hub_location,
+		asset_id: AssetId::Concrete((PalletInstance(50), GeneralIndex(ASSET_ID.into())).into()),
+	};
+	// treasury spend beneficiary.
+	let alice: AccountId = Westend::account_id_of(ALICE);
+	let bob: AccountId = Westend::account_id_of(BOB);
+	let bob_signed = <Westend as Chain>::RuntimeOrigin::signed(bob.clone());
+
+	AssetHubWestend::execute_with(|| {
+		type Assets = <AssetHubWestend as AssetHubWestendPallet>::Assets;
+
+		// create an asset class and mint some assets to the treasury account.
+		assert_ok!(<Assets as Create<_>>::create(
+			ASSET_ID,
+			treasury_account.clone(),
+			true,
+			SPEND_AMOUNT / 2
+		));
+		assert_ok!(<Assets as Mutate<_>>::mint_into(ASSET_ID, &treasury_account, SPEND_AMOUNT * 4));
+		// beneficiary has zero balance.
+		assert_eq!(<Assets as Inspect<_>>::balance(ASSET_ID, &alice,), 0u128,);
+	});
+
+	Westend::execute_with(|| {
+		type RuntimeEvent = <Westend as Chain>::RuntimeEvent;
+		type Treasury = <Westend as WestendPallet>::Treasury;
+		type AssetRate = <Westend as WestendPallet>::AssetRate;
+
+		// create a conversion rate from `asset_kind` to the native currency.
+		assert_ok!(AssetRate::create(root.clone(), Box::new(asset_kind.clone()), 2.into()));
+
+		// create and approve a treasury spend.
+		assert_ok!(Treasury::spend(
+			root,
+			Box::new(asset_kind),
+			SPEND_AMOUNT,
+			Box::new(MultiLocation::new(0, Into::<[u8; 32]>::into(alice.clone())).into()),
+			None,
+		));
+		// claim the spend.
+		assert_ok!(Treasury::payout(bob_signed.clone(), 0));
+
+		assert_expected_events!(
+			Westend,
+			vec![
+				RuntimeEvent::Treasury(pallet_treasury::Event::Paid { .. }) => {},
+			]
+		);
+	});
+
+	AssetHubWestend::execute_with(|| {
+		type RuntimeEvent = <AssetHubWestend as Chain>::RuntimeEvent;
+		type Assets = <AssetHubWestend as AssetHubWestendPallet>::Assets;
+
+		// assert events triggered by xcm pay program
+		// 1. treasury asset transferred to spend beneficiary
+		// 2. response to Relay Chain treasury pallet instance sent back
+		// 3. XCM program completed
+		assert_expected_events!(
+			AssetHubWestend,
+			vec![
+				RuntimeEvent::Assets(pallet_assets::Event::Transferred { asset_id: id, from, to, amount }) => {
+					id: id == &ASSET_ID,
+					from: from == &treasury_account,
+					to: to == &alice,
+					amount: amount == &SPEND_AMOUNT,
+				},
+				RuntimeEvent::ParachainSystem(cumulus_pallet_parachain_system::Event::UpwardMessageSent { .. }) => {},
+				RuntimeEvent::DmpQueue(cumulus_pallet_dmp_queue::Event::ExecutedDownward { outcome: Outcome::Complete(..) ,.. }) => {},
+			]
+		);
+		// beneficiary received the assets from the treasury.
+		assert_eq!(<Assets as Inspect<_>>::balance(ASSET_ID, &alice,), SPEND_AMOUNT,);
+	});
+
+	Westend::execute_with(|| {
+		type RuntimeEvent = <Westend as Chain>::RuntimeEvent;
+		type Treasury = <Westend as WestendPallet>::Treasury;
+
+		// check the payment status to ensure the response from the AssetHub was received.
+		assert_ok!(Treasury::check_status(bob_signed, 0));
+		assert_expected_events!(
+			Westend,
+			vec![
+				RuntimeEvent::Treasury(pallet_treasury::Event::SpendProcessed { .. }) => {},
+			]
+		);
+	});
+}
diff --git a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs
index 2c9581c3a189a..068f0238f4978 100644
--- a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs
+++ b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs
@@ -46,6 +46,8 @@ decl_test_relay_chains! {
 			XcmPallet: westend_runtime::XcmPallet,
 			Sudo: westend_runtime::Sudo,
 			Balances: westend_runtime::Balances,
+			Treasury: westend_runtime::Treasury,
+			AssetRate: westend_runtime::AssetRate,
 		}
 	},
 	#[api_version(7)]
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 6981c290c98ce..a0921c50dc59b 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
@@ -38,11 +38,12 @@ use xcm::latest::prelude::*;
 use xcm_builder::{
 	AccountId32Aliases, AllowExplicitUnpaidExecutionFrom, AllowKnownQueryResponses,
 	AllowSubscriptionsFrom, AllowTopLevelPaidExecutionFrom, CurrencyAdapter,
-	DenyReserveTransferToRelayChain, DenyThenTry, EnsureXcmOrigin, FungiblesAdapter, IsConcrete,
-	LocalMint, NativeAsset, NoChecking, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative,
-	SiblingParachainAsNative, SiblingParachainConvertsVia, SignedAccountId32AsNative,
-	SignedToAccountId32, SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId,
-	UsingComponents, WeightInfoBounds, WithComputedOrigin, WithUniqueTopic,
+	DenyReserveTransferToRelayChain, DenyThenTry, DescribeFamily, DescribePalletTerminal,
+	EnsureXcmOrigin, FungiblesAdapter, HashedDescription, IsConcrete, LocalMint, NativeAsset,
+	NoChecking, ParentAsSuperuser, ParentIsPreset, RelayChainAsNative, SiblingParachainAsNative,
+	SiblingParachainConvertsVia, SignedAccountId32AsNative, SignedToAccountId32,
+	SovereignSignedViaLocation, TakeWeightCredit, TrailingSetTopicAsId, UsingComponents,
+	WeightInfoBounds, WithComputedOrigin, WithUniqueTopic,
 };
 use xcm_executor::{traits::WithOriginFilter, XcmExecutor};
 
@@ -75,6 +76,9 @@ pub type LocationToAccountId = (
 	SiblingParachainConvertsVia<Sibling, AccountId>,
 	// Straight up local `AccountId32` origins just alias directly to `AccountId`.
 	AccountId32Aliases<RelayNetwork, AccountId>,
+	// Foreign chain account alias into local accounts according to a hash of their standard
+	// description.
+	HashedDescription<AccountId, DescribeFamily<DescribePalletTerminal>>,
 );
 
 /// Means for transacting the native currency on this chain.
@@ -222,6 +226,9 @@ match_types! {
 		MultiLocation { parents: 1, interior: Here } |
 		MultiLocation { parents: 1, interior: X1(Plurality { .. }) }
 	};
+	pub type TreasuryPallet: impl Contains<MultiLocation> = {
+		MultiLocation { parents: 1, interior: X1(PalletInstance(37)) }
+	};
 }
 
 /// A call filter for the XCM Transact instruction. This is a temporary measure until we properly
@@ -449,8 +456,9 @@ pub type Barrier = TrailingSetTopicAsId<
 					// If the message is one that immediately attemps to pay for execution, then
 					// allow it.
 					AllowTopLevelPaidExecutionFrom<Everything>,
-					// Parent and its pluralities (i.e. governance bodies) get free execution.
-					AllowExplicitUnpaidExecutionFrom<ParentOrParentsPlurality>,
+					// Parent, its pluralities (i.e. governance bodies) and treasury pallet get
+					// free execution.
+					AllowExplicitUnpaidExecutionFrom<(ParentOrParentsPlurality, TreasuryPallet)>,
 					// Subscriptions for version tracking are OK.
 					AllowSubscriptionsFrom<Everything>,
 				),
diff --git a/polkadot/runtime/common/Cargo.toml b/polkadot/runtime/common/Cargo.toml
index 17617bf4ada3f..2d1aad6a575e7 100644
--- a/polkadot/runtime/common/Cargo.toml
+++ b/polkadot/runtime/common/Cargo.toml
@@ -38,6 +38,7 @@ pallet-timestamp = { path = "../../../substrate/frame/timestamp", default-featur
 pallet-vesting = { path = "../../../substrate/frame/vesting", default-features = false }
 pallet-transaction-payment = { path = "../../../substrate/frame/transaction-payment", default-features = false }
 pallet-treasury = { path = "../../../substrate/frame/treasury", default-features = false }
+pallet-asset-rate = { path = "../../../substrate/frame/asset-rate", default-features = false }
 pallet-election-provider-multi-phase = { path = "../../../substrate/frame/election-provider-multi-phase", default-features = false }
 frame-election-provider-support = { path = "../../../substrate/frame/election-provider-support", default-features = false }
 
@@ -50,6 +51,7 @@ runtime-parachains = { package = "polkadot-runtime-parachains", path = "../parac
 
 slot-range-helper = { path = "slot_range_helper", default-features = false }
 xcm = { package = "staging-xcm", path = "../../xcm", default-features = false }
+xcm-builder = { package = "staging-xcm-builder", path = "../../xcm/xcm-builder", default-features = false }
 
 [dev-dependencies]
 hex-literal = "0.4.1"
@@ -74,6 +76,7 @@ std = [
 	"inherents/std",
 	"libsecp256k1/std",
 	"log/std",
+	"pallet-asset-rate/std",
 	"pallet-authorship/std",
 	"pallet-balances/std",
 	"pallet-election-provider-multi-phase/std",
@@ -100,6 +103,7 @@ std = [
 	"sp-session/std",
 	"sp-staking/std",
 	"sp-std/std",
+	"xcm-builder/std",
 	"xcm/std",
 ]
 runtime-benchmarks = [
@@ -109,6 +113,7 @@ runtime-benchmarks = [
 	"frame-system/runtime-benchmarks",
 	"libsecp256k1/hmac",
 	"libsecp256k1/static-context",
+	"pallet-asset-rate/runtime-benchmarks",
 	"pallet-babe/runtime-benchmarks",
 	"pallet-balances/runtime-benchmarks",
 	"pallet-election-provider-multi-phase/runtime-benchmarks",
@@ -121,12 +126,14 @@ runtime-benchmarks = [
 	"runtime-parachains/runtime-benchmarks",
 	"sp-runtime/runtime-benchmarks",
 	"sp-staking/runtime-benchmarks",
+	"xcm-builder/runtime-benchmarks",
 ]
 try-runtime = [
 	"frame-election-provider-support/try-runtime",
 	"frame-support-test/try-runtime",
 	"frame-support/try-runtime",
 	"frame-system/try-runtime",
+	"pallet-asset-rate/try-runtime",
 	"pallet-authorship/try-runtime",
 	"pallet-babe?/try-runtime",
 	"pallet-balances/try-runtime",
diff --git a/polkadot/runtime/common/src/impls.rs b/polkadot/runtime/common/src/impls.rs
index 0d0dee2e9ad91..590593745ed04 100644
--- a/polkadot/runtime/common/src/impls.rs
+++ b/polkadot/runtime/common/src/impls.rs
@@ -18,8 +18,10 @@
 
 use crate::NegativeImbalance;
 use frame_support::traits::{Currency, Imbalance, OnUnbalanced};
+use parity_scale_codec::{Decode, Encode, MaxEncodedLen};
 use primitives::Balance;
-use sp_runtime::Perquintill;
+use sp_runtime::{traits::TryConvert, Perquintill, RuntimeDebug};
+use xcm::VersionedMultiLocation;
 
 /// Logic for the author to get a portion of fees.
 pub struct ToAuthor<R>(sp_std::marker::PhantomData<R>);
@@ -98,13 +100,104 @@ pub fn era_payout(
 	(staking_payout, rest)
 }
 
+/// Versioned locatable asset type which contains both an XCM `location` and `asset_id` to identify
+/// an asset which exists on some chain.
+#[derive(
+	Encode, Decode, Eq, PartialEq, Clone, RuntimeDebug, scale_info::TypeInfo, MaxEncodedLen,
+)]
+pub enum VersionedLocatableAsset {
+	#[codec(index = 3)]
+	V3 {
+		/// The (relative) location in which the asset ID is meaningful.
+		location: xcm::v3::MultiLocation,
+		/// The asset's ID.
+		asset_id: xcm::v3::AssetId,
+	},
+}
+
+/// Converts the [`VersionedLocatableAsset`] to the [`xcm_builder::LocatableAssetId`].
+pub struct LocatableAssetConverter;
+impl TryConvert<VersionedLocatableAsset, xcm_builder::LocatableAssetId>
+	for LocatableAssetConverter
+{
+	fn try_convert(
+		asset: VersionedLocatableAsset,
+	) -> Result<xcm_builder::LocatableAssetId, VersionedLocatableAsset> {
+		match asset {
+			VersionedLocatableAsset::V3 { location, asset_id } =>
+				Ok(xcm_builder::LocatableAssetId { asset_id, location }),
+		}
+	}
+}
+
+/// Converts the [`VersionedMultiLocation`] to the [`xcm::latest::MultiLocation`].
+pub struct VersionedMultiLocationConverter;
+impl TryConvert<&VersionedMultiLocation, xcm::latest::MultiLocation>
+	for VersionedMultiLocationConverter
+{
+	fn try_convert(
+		location: &VersionedMultiLocation,
+	) -> Result<xcm::latest::MultiLocation, &VersionedMultiLocation> {
+		let latest = match location.clone() {
+			VersionedMultiLocation::V2(l) => l.try_into().map_err(|_| location)?,
+			VersionedMultiLocation::V3(l) => l,
+		};
+		Ok(latest)
+	}
+}
+
+#[cfg(feature = "runtime-benchmarks")]
+pub mod benchmarks {
+	use super::VersionedLocatableAsset;
+	use pallet_asset_rate::AssetKindFactory;
+	use pallet_treasury::ArgumentsFactory as TreasuryArgumentsFactory;
+	use xcm::prelude::*;
+
+	/// Provides a factory method for the [`VersionedLocatableAsset`].
+	/// The location of the asset is determined as a Parachain with an ID equal to the passed seed.
+	pub struct AssetRateArguments;
+	impl AssetKindFactory<VersionedLocatableAsset> for AssetRateArguments {
+		fn create_asset_kind(seed: u32) -> VersionedLocatableAsset {
+			VersionedLocatableAsset::V3 {
+				location: xcm::v3::MultiLocation::new(0, X1(Parachain(seed))),
+				asset_id: xcm::v3::MultiLocation::new(
+					0,
+					X2(PalletInstance(seed.try_into().unwrap()), GeneralIndex(seed.into())),
+				)
+				.into(),
+			}
+		}
+	}
+
+	/// Provide factory methods for the [`VersionedLocatableAsset`] and the `Beneficiary` of the
+	/// [`VersionedMultiLocation`]. The location of the asset is determined as a Parachain with an
+	/// ID equal to the passed seed.
+	pub struct TreasuryArguments;
+	impl TreasuryArgumentsFactory<VersionedLocatableAsset, VersionedMultiLocation>
+		for TreasuryArguments
+	{
+		fn create_asset_kind(seed: u32) -> VersionedLocatableAsset {
+			AssetRateArguments::create_asset_kind(seed)
+		}
+		fn create_beneficiary(seed: [u8; 32]) -> VersionedMultiLocation {
+			VersionedMultiLocation::V3(xcm::v3::MultiLocation::new(
+				0,
+				X1(AccountId32 { network: None, id: seed }),
+			))
+		}
+	}
+}
+
 #[cfg(test)]
 mod tests {
 	use super::*;
 	use frame_support::{
 		dispatch::DispatchClass,
 		parameter_types,
-		traits::{ConstU32, FindAuthor},
+		traits::{
+			tokens::{PayFromAccount, UnityAssetBalanceConversion},
+			ConstU32, FindAuthor,
+		},
 		weights::Weight,
 		PalletId,
 	};
@@ -189,6 +282,7 @@ mod tests {
 	parameter_types! {
 		pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry");
 		pub const MaxApprovals: u32 = 100;
+		pub TreasuryAccount: AccountId = Treasury::account_id();
 	}
 
 	impl pallet_treasury::Config for Test {
@@ -208,6 +302,14 @@ mod tests {
 		type MaxApprovals = MaxApprovals;
 		type WeightInfo = ();
 		type SpendOrigin = frame_support::traits::NeverEnsureOrigin<u64>;
+		type AssetKind = ();
+		type Beneficiary = Self::AccountId;
+		type BeneficiaryLookup = IdentityLookup<Self::AccountId>;
+		type Paymaster = PayFromAccount<Balances, TreasuryAccount>;
+		type BalanceConverter = UnityAssetBalanceConversion;
+		type PayoutPeriod = ConstU64<0>;
+		#[cfg(feature = "runtime-benchmarks")]
+		type BenchmarkHelper = ();
 	}
 
 	pub struct OneAuthor;
diff --git a/polkadot/runtime/rococo/Cargo.toml b/polkadot/runtime/rococo/Cargo.toml
index ab9b2f11acfbe..0b8a8624bb673 100644
--- a/polkadot/runtime/rococo/Cargo.toml
+++ b/polkadot/runtime/rococo/Cargo.toml
@@ -52,6 +52,7 @@ pallet-collective = { path = "../../../substrate/frame/collective", default-feat
 pallet-conviction-voting = { path = "../../../substrate/frame/conviction-voting", default-features = false }
 pallet-democracy = { path = "../../../substrate/frame/democracy", default-features = false }
 pallet-elections-phragmen = { path = "../../../substrate/frame/elections-phragmen", default-features = false }
+pallet-asset-rate = {  path = "../../../substrate/frame/asset-rate", default-features = false }
 frame-executive = { path = "../../../substrate/frame/executive", default-features = false }
 pallet-grandpa = { path = "../../../substrate/frame/grandpa", default-features = false }
 pallet-identity = { path = "../../../substrate/frame/identity", default-features = false }
@@ -72,7 +73,7 @@ pallet-scheduler = { path = "../../../substrate/frame/scheduler", default-featur
 pallet-session = { path = "../../../substrate/frame/session", default-features = false }
 pallet-society = { path = "../../../substrate/frame/society", default-features = false }
 pallet-sudo = { path = "../../../substrate/frame/sudo", default-features = false }
-frame-support = { path = "../../../substrate/frame/support", default-features = false }
+frame-support = { path = "../../../substrate/frame/support", default-features = false, features = ["tuples-96"] }
 pallet-staking = { path = "../../../substrate/frame/staking", default-features = false }
 frame-system = { path = "../../../substrate/frame/system", default-features = false }
 frame-system-rpc-runtime-api = { path = "../../../substrate/frame/system/rpc/runtime-api", default-features = false }
@@ -131,6 +132,7 @@ std = [
 	"inherents/std",
 	"log/std",
 	"offchain-primitives/std",
+	"pallet-asset-rate/std",
 	"pallet-authority-discovery/std",
 	"pallet-authorship/std",
 	"pallet-babe/std",
@@ -207,6 +209,7 @@ runtime-benchmarks = [
 	"frame-support/runtime-benchmarks",
 	"frame-system-benchmarking/runtime-benchmarks",
 	"frame-system/runtime-benchmarks",
+	"pallet-asset-rate/runtime-benchmarks",
 	"pallet-babe/runtime-benchmarks",
 	"pallet-balances/runtime-benchmarks",
 	"pallet-bounties/runtime-benchmarks",
@@ -258,6 +261,7 @@ try-runtime = [
 	"frame-system/try-runtime",
 	"frame-try-runtime",
 	"frame-try-runtime/try-runtime",
+	"pallet-asset-rate/try-runtime",
 	"pallet-authority-discovery/try-runtime",
 	"pallet-authorship/try-runtime",
 	"pallet-babe/try-runtime",
diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs
index 4bdcc1237394e..fd3e656f69573 100644
--- a/polkadot/runtime/rococo/src/lib.rs
+++ b/polkadot/runtime/rococo/src/lib.rs
@@ -30,7 +30,10 @@ use primitives::{
 	ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, PARACHAIN_KEY_TYPE_ID,
 };
 use runtime_common::{
-	assigned_slots, auctions, claims, crowdloan, impl_runtime_weights, impls::ToAuthor,
+	assigned_slots, auctions, claims, crowdloan, impl_runtime_weights,
+	impls::{
+		LocatableAssetConverter, ToAuthor, VersionedLocatableAsset, VersionedMultiLocationConverter,
+	},
 	paras_registrar, paras_sudo_wrapper, prod_or_fast, slots, BlockHashCount, BlockLength,
 	SlowAdjustingFeeUpdate,
 };
@@ -79,7 +82,8 @@ use sp_runtime::{
 	create_runtime_str, generic, impl_opaque_keys,
 	traits::{
 		AccountIdLookup, BlakeTwo256, Block as BlockT, ConstU32, ConvertInto,
-		Extrinsic as ExtrinsicT, Keccak256, OpaqueKeys, SaturatedConversion, Verify,
+		Extrinsic as ExtrinsicT, IdentityLookup, Keccak256, OpaqueKeys, SaturatedConversion,
+		Verify,
 	},
 	transaction_validity::{TransactionPriority, TransactionSource, TransactionValidity},
 	ApplyExtrinsicResult, FixedU128, KeyTypeId, Perbill, Percent, Permill, RuntimeDebug,
@@ -88,7 +92,11 @@ use sp_staking::SessionIndex;
 #[cfg(any(feature = "std", test))]
 use sp_version::NativeVersion;
 use sp_version::RuntimeVersion;
-use xcm::latest::Junction;
+use xcm::{
+	latest::{InteriorMultiLocation, Junction, Junction::PalletInstance},
+	VersionedMultiLocation,
+};
+use xcm_builder::PayOverXcm;
 
 pub use frame_system::Call as SystemCall;
 pub use pallet_balances::Call as BalancesCall;
@@ -385,6 +393,10 @@ parameter_types! {
 	pub const SpendPeriod: BlockNumber = 6 * DAYS;
 	pub const Burn: Permill = Permill::from_perthousand(2);
 	pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry");
+	pub const PayoutSpendPeriod: BlockNumber = 30 * DAYS;
+	// The asset's interior location for the paying account. This is the Treasury
+	// pallet instance (which sits at index 18).
+	pub TreasuryInteriorLocation: InteriorMultiLocation = PalletInstance(18).into();
 
 	pub const TipCountdown: BlockNumber = 1 * DAYS;
 	pub const TipFindersFee: Percent = Percent::from_percent(20);
@@ -394,6 +406,7 @@ parameter_types! {
 	pub const MaxAuthorities: u32 = 100_000;
 	pub const MaxKeys: u32 = 10_000;
 	pub const MaxPeerInHeartbeats: u32 = 10_000;
+	pub const MaxBalance: Balance = Balance::max_value();
 }
 
 impl pallet_treasury::Config for Runtime {
@@ -413,6 +426,23 @@ impl pallet_treasury::Config for Runtime {
 	type WeightInfo = weights::pallet_treasury::WeightInfo<Runtime>;
 	type SpendFunds = Bounties;
 	type SpendOrigin = TreasurySpender;
+	type AssetKind = VersionedLocatableAsset;
+	type Beneficiary = VersionedMultiLocation;
+	type BeneficiaryLookup = IdentityLookup<Self::Beneficiary>;
+	type Paymaster = PayOverXcm<
+		TreasuryInteriorLocation,
+		crate::xcm_config::XcmRouter,
+		crate::XcmPallet,
+		ConstU32<{ 6 * HOURS }>,
+		Self::Beneficiary,
+		Self::AssetKind,
+		LocatableAssetConverter,
+		VersionedMultiLocationConverter,
+	>;
+	type BalanceConverter = AssetRate;
+	type PayoutPeriod = PayoutSpendPeriod;
+	#[cfg(feature = "runtime-benchmarks")]
+	type BenchmarkHelper = runtime_common::impls::benchmarks::TreasuryArguments;
 }
 
 parameter_types! {
@@ -1202,6 +1232,18 @@ impl pallet_sudo::Config for Runtime {
 	type WeightInfo = weights::pallet_sudo::WeightInfo<Runtime>;
 }
 
+impl pallet_asset_rate::Config for Runtime {
+	type WeightInfo = weights::pallet_asset_rate::WeightInfo<Runtime>;
+	type RuntimeEvent = RuntimeEvent;
+	type CreateOrigin = EnsureRoot<AccountId>;
+	type RemoveOrigin = EnsureRoot<AccountId>;
+	type UpdateOrigin = EnsureRoot<AccountId>;
+	type Currency = Balances;
+	type AssetKind = <Runtime as pallet_treasury::Config>::AssetKind;
+	#[cfg(feature = "runtime-benchmarks")]
+	type BenchmarkHelper = runtime_common::impls::benchmarks::AssetRateArguments;
+}
+
 construct_runtime! {
 	pub enum Runtime
 	{
@@ -1279,6 +1321,9 @@ construct_runtime! {
 		// Preimage registrar.
 		Preimage: pallet_preimage::{Pallet, Call, Storage, Event<T>, HoldReason} = 32,
 
+		// Asset rate.
+		AssetRate: pallet_asset_rate::{Pallet, Call, Storage, Event<T>} = 39,
+
 		// Bounties modules.
 		Bounties: pallet_bounties::{Pallet, Call, Storage, Event<T>} = 35,
 		ChildBounties: pallet_child_bounties = 40,
@@ -1465,6 +1510,7 @@ mod benches {
 		[pallet_treasury, Treasury]
 		[pallet_utility, Utility]
 		[pallet_vesting, Vesting]
+		[pallet_asset_rate, AssetRate]
 		[pallet_whitelist, Whitelist]
 		// XCM
 		[pallet_xcm, XcmPallet]
diff --git a/polkadot/runtime/rococo/src/weights/mod.rs b/polkadot/runtime/rococo/src/weights/mod.rs
index e0c1c4f413515..9c563a67d98b7 100644
--- a/polkadot/runtime/rococo/src/weights/mod.rs
+++ b/polkadot/runtime/rococo/src/weights/mod.rs
@@ -16,6 +16,7 @@
 //! A list of the different weight modules for our runtime.
 
 pub mod frame_system;
+pub mod pallet_asset_rate;
 pub mod pallet_balances;
 pub mod pallet_balances_nis_counterpart_balances;
 pub mod pallet_bounties;
diff --git a/polkadot/runtime/rococo/src/weights/pallet_asset_rate.rs b/polkadot/runtime/rococo/src/weights/pallet_asset_rate.rs
new file mode 100644
index 0000000000000..da2d1958cefcf
--- /dev/null
+++ b/polkadot/runtime/rococo/src/weights/pallet_asset_rate.rs
@@ -0,0 +1,86 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// This file is part of Polkadot.
+
+// Polkadot 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.
+
+// Polkadot 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 Polkadot.  If not, see <http://www.gnu.org/licenses/>.
+
+//! Autogenerated weights for `pallet_asset_rate`
+//!
+//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
+//! DATE: 2023-07-03, STEPS: `50`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! WORST CASE MAP SIZE: `1000000`
+//! HOSTNAME: `cob`, CPU: `<UNKNOWN>`
+//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: Some("rococo-dev"), DB CACHE: 1024
+
+// Executed Command:
+// ./target/debug/polkadot
+// benchmark
+// pallet
+// --chain=rococo-dev
+// --steps=50
+// --repeat=2
+// --pallet=pallet_asset_rate
+// --extrinsic=*
+// --wasm-execution=compiled
+// --heap-pages=4096
+// --output=./runtime/rococo/src/weights/
+// --header=./file_header.txt
+
+#![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 `pallet_asset_rate`.
+pub struct WeightInfo<T>(PhantomData<T>);
+impl<T: frame_system::Config> pallet_asset_rate::WeightInfo for WeightInfo<T> {
+	/// Storage: AssetRate ConversionRateToNative (r:1 w:1)
+	/// Proof: AssetRate ConversionRateToNative (max_values: None, max_size: Some(1237), added: 3712, mode: MaxEncodedLen)
+	fn create() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `42`
+		//  Estimated: `4702`
+		// Minimum execution time: 143_000_000 picoseconds.
+		Weight::from_parts(155_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 4702))
+			.saturating_add(T::DbWeight::get().reads(1))
+			.saturating_add(T::DbWeight::get().writes(1))
+	}
+	/// Storage: AssetRate ConversionRateToNative (r:1 w:1)
+	/// Proof: AssetRate ConversionRateToNative (max_values: None, max_size: Some(1237), added: 3712, mode: MaxEncodedLen)
+	fn update() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `110`
+		//  Estimated: `4702`
+		// Minimum execution time: 156_000_000 picoseconds.
+		Weight::from_parts(172_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 4702))
+			.saturating_add(T::DbWeight::get().reads(1))
+			.saturating_add(T::DbWeight::get().writes(1))
+	}
+	/// Storage: AssetRate ConversionRateToNative (r:1 w:1)
+	/// Proof: AssetRate ConversionRateToNative (max_values: None, max_size: Some(1237), added: 3712, mode: MaxEncodedLen)
+	fn remove() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `110`
+		//  Estimated: `4702`
+		// Minimum execution time: 150_000_000 picoseconds.
+		Weight::from_parts(160_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 4702))
+			.saturating_add(T::DbWeight::get().reads(1))
+			.saturating_add(T::DbWeight::get().writes(1))
+	}
+}
diff --git a/polkadot/runtime/rococo/src/weights/pallet_treasury.rs b/polkadot/runtime/rococo/src/weights/pallet_treasury.rs
index 041d976d82570..144e9d5b87238 100644
--- a/polkadot/runtime/rococo/src/weights/pallet_treasury.rs
+++ b/polkadot/runtime/rococo/src/weights/pallet_treasury.rs
@@ -17,24 +17,24 @@
 //! Autogenerated weights for `pallet_treasury`
 //!
 //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
-//! DATE: 2023-05-26, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! DATE: 2023-07-07, STEPS: `50`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]`
 //! WORST CASE MAP SIZE: `1000000`
-//! HOSTNAME: `bm5`, CPU: `Intel(R) Core(TM) i7-7700K CPU @ 4.20GHz`
-//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("rococo-dev"), DB CACHE: 1024
+//! HOSTNAME: `cob`, CPU: `<UNKNOWN>`
+//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: Some("rococo-dev"), DB CACHE: 1024
 
 // Executed Command:
-// ./target/production/polkadot
+// ./target/debug/polkadot
 // benchmark
 // pallet
 // --chain=rococo-dev
 // --steps=50
-// --repeat=20
+// --repeat=2
 // --pallet=pallet_treasury
 // --extrinsic=*
-// --execution=wasm
 // --wasm-execution=compiled
-// --header=./file_header.txt
+// --heap-pages=4096
 // --output=./runtime/rococo/src/weights/
+// --header=./file_header.txt
 
 #![cfg_attr(rustfmt, rustfmt_skip)]
 #![allow(unused_parens)]
@@ -47,13 +47,21 @@ use core::marker::PhantomData;
 /// Weight functions for `pallet_treasury`.
 pub struct WeightInfo<T>(PhantomData<T>);
 impl<T: frame_system::Config> pallet_treasury::WeightInfo for WeightInfo<T> {
-	fn spend() -> Weight {
+	/// Storage: Treasury ProposalCount (r:1 w:1)
+	/// Proof: Treasury ProposalCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
+	/// Storage: Treasury Approvals (r:1 w:1)
+	/// Proof: Treasury Approvals (max_values: Some(1), max_size: Some(402), added: 897, mode: MaxEncodedLen)
+	/// Storage: Treasury Proposals (r:0 w:1)
+	/// Proof: Treasury Proposals (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen)
+	fn spend_local() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `0`
-		//  Estimated: `0`
-		// Minimum execution time: 204_000 picoseconds.
-		Weight::from_parts(233_000, 0)
-			.saturating_add(Weight::from_parts(0, 0))
+		//  Measured:  `42`
+		//  Estimated: `1887`
+		// Minimum execution time: 177_000_000 picoseconds.
+		Weight::from_parts(191_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 1887))
+			.saturating_add(T::DbWeight::get().reads(2))
+			.saturating_add(T::DbWeight::get().writes(3))
 	}
 	/// Storage: Treasury ProposalCount (r:1 w:1)
 	/// Proof: Treasury ProposalCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
@@ -61,10 +69,10 @@ impl<T: frame_system::Config> pallet_treasury::WeightInfo for WeightInfo<T> {
 	/// Proof: Treasury Proposals (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen)
 	fn propose_spend() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `107`
+		//  Measured:  `143`
 		//  Estimated: `1489`
-		// Minimum execution time: 27_592_000 picoseconds.
-		Weight::from_parts(27_960_000, 0)
+		// Minimum execution time: 354_000_000 picoseconds.
+		Weight::from_parts(376_000_000, 0)
 			.saturating_add(Weight::from_parts(0, 1489))
 			.saturating_add(T::DbWeight::get().reads(1))
 			.saturating_add(T::DbWeight::get().writes(2))
@@ -75,10 +83,10 @@ impl<T: frame_system::Config> pallet_treasury::WeightInfo for WeightInfo<T> {
 	/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
 	fn reject_proposal() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `265`
+		//  Measured:  `301`
 		//  Estimated: `3593`
-		// Minimum execution time: 40_336_000 picoseconds.
-		Weight::from_parts(41_085_000, 0)
+		// Minimum execution time: 547_000_000 picoseconds.
+		Weight::from_parts(550_000_000, 0)
 			.saturating_add(Weight::from_parts(0, 3593))
 			.saturating_add(T::DbWeight::get().reads(2))
 			.saturating_add(T::DbWeight::get().writes(2))
@@ -90,13 +98,13 @@ impl<T: frame_system::Config> pallet_treasury::WeightInfo for WeightInfo<T> {
 	/// The range of component `p` is `[0, 99]`.
 	fn approve_proposal(p: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `433 + p * (8 ±0)`
+		//  Measured:  `470 + p * (8 ±0)`
 		//  Estimated: `3573`
-		// Minimum execution time: 9_938_000 picoseconds.
-		Weight::from_parts(12_061_206, 0)
+		// Minimum execution time: 104_000_000 picoseconds.
+		Weight::from_parts(121_184_402, 0)
 			.saturating_add(Weight::from_parts(0, 3573))
-			// Standard Error: 801
-			.saturating_add(Weight::from_parts(26_602, 0).saturating_mul(p.into()))
+			// Standard Error: 42_854
+			.saturating_add(Weight::from_parts(153_112, 0).saturating_mul(p.into()))
 			.saturating_add(T::DbWeight::get().reads(2))
 			.saturating_add(T::DbWeight::get().writes(1))
 	}
@@ -104,10 +112,10 @@ impl<T: frame_system::Config> pallet_treasury::WeightInfo for WeightInfo<T> {
 	/// Proof: Treasury Approvals (max_values: Some(1), max_size: Some(402), added: 897, mode: MaxEncodedLen)
 	fn remove_approval() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `90`
+		//  Measured:  `127`
 		//  Estimated: `1887`
-		// Minimum execution time: 7_421_000 picoseconds.
-		Weight::from_parts(7_620_000, 0)
+		// Minimum execution time: 80_000_000 picoseconds.
+		Weight::from_parts(82_000_000, 0)
 			.saturating_add(Weight::from_parts(0, 1887))
 			.saturating_add(T::DbWeight::get().reads(1))
 			.saturating_add(T::DbWeight::get().writes(1))
@@ -118,26 +126,98 @@ impl<T: frame_system::Config> pallet_treasury::WeightInfo for WeightInfo<T> {
 	/// Proof: Balances InactiveIssuance (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen)
 	/// Storage: Treasury Approvals (r:1 w:1)
 	/// Proof: Treasury Approvals (max_values: Some(1), max_size: Some(402), added: 897, mode: MaxEncodedLen)
-	/// Storage: Treasury Proposals (r:100 w:100)
+	/// Storage: Treasury Proposals (r:99 w:99)
 	/// Proof: Treasury Proposals (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen)
-	/// Storage: System Account (r:201 w:201)
+	/// Storage: System Account (r:199 w:199)
 	/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
 	/// Storage: Bounties BountyApprovals (r:1 w:1)
 	/// Proof: Bounties BountyApprovals (max_values: Some(1), max_size: Some(402), added: 897, mode: MaxEncodedLen)
-	/// The range of component `p` is `[0, 100]`.
+	/// The range of component `p` is `[0, 99]`.
 	fn on_initialize_proposals(p: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `296 + p * (251 ±0)`
+		//  Measured:  `331 + p * (251 ±0)`
 		//  Estimated: `3593 + p * (5206 ±0)`
-		// Minimum execution time: 62_706_000 picoseconds.
-		Weight::from_parts(61_351_470, 0)
+		// Minimum execution time: 887_000_000 picoseconds.
+		Weight::from_parts(828_616_021, 0)
 			.saturating_add(Weight::from_parts(0, 3593))
-			// Standard Error: 32_787
-			.saturating_add(Weight::from_parts(37_873_920, 0).saturating_mul(p.into()))
+			// Standard Error: 695_351
+			.saturating_add(Weight::from_parts(566_114_524, 0).saturating_mul(p.into()))
 			.saturating_add(T::DbWeight::get().reads(5))
 			.saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(p.into())))
 			.saturating_add(T::DbWeight::get().writes(5))
 			.saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(p.into())))
 			.saturating_add(Weight::from_parts(0, 5206).saturating_mul(p.into()))
 	}
+	/// Storage: AssetRate ConversionRateToNative (r:1 w:0)
+	/// Proof: AssetRate ConversionRateToNative (max_values: None, max_size: Some(1237), added: 3712, mode: MaxEncodedLen)
+	/// Storage: Treasury SpendCount (r:1 w:1)
+	/// Proof: Treasury SpendCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
+	/// Storage: Treasury Spends (r:0 w:1)
+	/// Proof: Treasury Spends (max_values: None, max_size: Some(1848), added: 4323, mode: MaxEncodedLen)
+	fn spend() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `114`
+		//  Estimated: `4702`
+		// Minimum execution time: 208_000_000 picoseconds.
+		Weight::from_parts(222_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 4702))
+			.saturating_add(T::DbWeight::get().reads(2))
+			.saturating_add(T::DbWeight::get().writes(2))
+	}
+	/// Storage: Treasury Spends (r:1 w:1)
+	/// Proof: Treasury Spends (max_values: None, max_size: Some(1848), added: 4323, mode: MaxEncodedLen)
+	/// Storage: XcmPallet QueryCounter (r:1 w:1)
+	/// Proof Skipped: XcmPallet QueryCounter (max_values: Some(1), max_size: None, mode: Measured)
+	/// Storage: Configuration ActiveConfig (r:1 w:0)
+	/// Proof Skipped: Configuration ActiveConfig (max_values: Some(1), max_size: None, mode: Measured)
+	/// Storage: Dmp DeliveryFeeFactor (r:1 w:0)
+	/// Proof Skipped: Dmp DeliveryFeeFactor (max_values: None, max_size: None, mode: Measured)
+	/// Storage: XcmPallet SupportedVersion (r:1 w:0)
+	/// Proof Skipped: XcmPallet SupportedVersion (max_values: None, max_size: None, mode: Measured)
+	/// Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1)
+	/// Proof Skipped: XcmPallet VersionDiscoveryQueue (max_values: Some(1), max_size: None, mode: Measured)
+	/// Storage: XcmPallet SafeXcmVersion (r:1 w:0)
+	/// Proof Skipped: XcmPallet SafeXcmVersion (max_values: Some(1), max_size: None, mode: Measured)
+	/// Storage: Dmp DownwardMessageQueues (r:1 w:1)
+	/// Proof Skipped: Dmp DownwardMessageQueues (max_values: None, max_size: None, mode: Measured)
+	/// Storage: Dmp DownwardMessageQueueHeads (r:1 w:1)
+	/// Proof Skipped: Dmp DownwardMessageQueueHeads (max_values: None, max_size: None, mode: Measured)
+	/// Storage: XcmPallet Queries (r:0 w:1)
+	/// Proof Skipped: XcmPallet Queries (max_values: None, max_size: None, mode: Measured)
+	fn payout() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `737`
+		//  Estimated: `5313`
+		// Minimum execution time: 551_000_000 picoseconds.
+		Weight::from_parts(569_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 5313))
+			.saturating_add(T::DbWeight::get().reads(9))
+			.saturating_add(T::DbWeight::get().writes(6))
+	}
+	/// Storage: Treasury Spends (r:1 w:1)
+	/// Proof: Treasury Spends (max_values: None, max_size: Some(1848), added: 4323, mode: MaxEncodedLen)
+	/// Storage: XcmPallet Queries (r:1 w:1)
+	/// Proof Skipped: XcmPallet Queries (max_values: None, max_size: None, mode: Measured)
+	fn check_status() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `442`
+		//  Estimated: `5313`
+		// Minimum execution time: 245_000_000 picoseconds.
+		Weight::from_parts(281_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 5313))
+			.saturating_add(T::DbWeight::get().reads(2))
+			.saturating_add(T::DbWeight::get().writes(2))
+	}
+	/// Storage: Treasury Spends (r:1 w:1)
+	/// Proof: Treasury Spends (max_values: None, max_size: Some(1848), added: 4323, mode: MaxEncodedLen)
+	fn void_spend() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `172`
+		//  Estimated: `5313`
+		// Minimum execution time: 147_000_000 picoseconds.
+		Weight::from_parts(160_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 5313))
+			.saturating_add(T::DbWeight::get().reads(1))
+			.saturating_add(T::DbWeight::get().writes(1))
+	}
 }
diff --git a/polkadot/runtime/westend/Cargo.toml b/polkadot/runtime/westend/Cargo.toml
index c9721935c32b4..58a6cc21eecdb 100644
--- a/polkadot/runtime/westend/Cargo.toml
+++ b/polkadot/runtime/westend/Cargo.toml
@@ -41,10 +41,11 @@ sp-npos-elections = { path = "../../../substrate/primitives/npos-elections", def
 
 frame-election-provider-support = { path = "../../../substrate/frame/election-provider-support", default-features = false }
 frame-executive = { path = "../../../substrate/frame/executive", default-features = false }
-frame-support = { path = "../../../substrate/frame/support", default-features = false }
+frame-support = { path = "../../../substrate/frame/support", default-features = false, features = ["tuples-96"] }
 frame-system = { path = "../../../substrate/frame/system", default-features = false }
 frame-system-rpc-runtime-api = { path = "../../../substrate/frame/system/rpc/runtime-api", default-features = false }
 westend-runtime-constants = { package = "westend-runtime-constants", path = "constants", default-features = false }
+pallet-asset-rate = {  path = "../../../substrate/frame/asset-rate", default-features = false }
 pallet-authority-discovery = { path = "../../../substrate/frame/authority-discovery", default-features = false }
 pallet-authorship = { path = "../../../substrate/frame/authorship", default-features = false }
 pallet-babe = { path = "../../../substrate/frame/babe", default-features = false }
@@ -143,6 +144,7 @@ std = [
 	"inherents/std",
 	"log/std",
 	"offchain-primitives/std",
+	"pallet-asset-rate/std",
 	"pallet-authority-discovery/std",
 	"pallet-authorship/std",
 	"pallet-babe/std",
@@ -228,6 +230,7 @@ runtime-benchmarks = [
 	"frame-system-benchmarking/runtime-benchmarks",
 	"frame-system/runtime-benchmarks",
 	"hex-literal",
+	"pallet-asset-rate/runtime-benchmarks",
 	"pallet-babe/runtime-benchmarks",
 	"pallet-bags-list/runtime-benchmarks",
 	"pallet-balances/runtime-benchmarks",
@@ -283,6 +286,7 @@ try-runtime = [
 	"frame-system/try-runtime",
 	"frame-try-runtime",
 	"frame-try-runtime/try-runtime",
+	"pallet-asset-rate/try-runtime",
 	"pallet-authority-discovery/try-runtime",
 	"pallet-authorship/try-runtime",
 	"pallet-babe/try-runtime",
diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs
index 6085b6e37455b..d61acf36b4cb8 100644
--- a/polkadot/runtime/westend/src/lib.rs
+++ b/polkadot/runtime/westend/src/lib.rs
@@ -53,9 +53,14 @@ use primitives::{
 	ValidatorSignature, PARACHAIN_KEY_TYPE_ID,
 };
 use runtime_common::{
-	assigned_slots, auctions, crowdloan, elections::OnChainAccuracy, impl_runtime_weights,
-	impls::ToAuthor, paras_registrar, paras_sudo_wrapper, prod_or_fast, slots, BalanceToU256,
-	BlockHashCount, BlockLength, CurrencyToVote, SlowAdjustingFeeUpdate, U256ToBalance,
+	assigned_slots, auctions, crowdloan,
+	elections::OnChainAccuracy,
+	impl_runtime_weights,
+	impls::{
+		LocatableAssetConverter, ToAuthor, VersionedLocatableAsset, VersionedMultiLocationConverter,
+	},
+	paras_registrar, paras_sudo_wrapper, prod_or_fast, slots, BalanceToU256, BlockHashCount,
+	BlockLength, CurrencyToVote, SlowAdjustingFeeUpdate, U256ToBalance,
 };
 use runtime_parachains::{
 	assigner_parachains as parachains_assigner_parachains,
@@ -77,7 +82,7 @@ use sp_runtime::{
 	generic, impl_opaque_keys,
 	traits::{
 		AccountIdLookup, BlakeTwo256, Block as BlockT, ConvertInto, Extrinsic as ExtrinsicT,
-		Keccak256, OpaqueKeys, SaturatedConversion, Verify,
+		IdentityLookup, Keccak256, OpaqueKeys, SaturatedConversion, Verify,
 	},
 	transaction_validity::{TransactionPriority, TransactionSource, TransactionValidity},
 	ApplyExtrinsicResult, FixedU128, KeyTypeId, Perbill, Percent, Permill,
@@ -87,7 +92,11 @@ use sp_std::{collections::btree_map::BTreeMap, prelude::*};
 #[cfg(any(feature = "std", test))]
 use sp_version::NativeVersion;
 use sp_version::RuntimeVersion;
-use xcm::latest::Junction;
+use xcm::{
+	latest::{InteriorMultiLocation, Junction, Junction::PalletInstance},
+	VersionedMultiLocation,
+};
+use xcm_builder::PayOverXcm;
 
 pub use frame_system::Call as SystemCall;
 pub use pallet_balances::Call as BalancesCall;
@@ -698,6 +707,10 @@ parameter_types! {
 	pub const SpendPeriod: BlockNumber = 6 * DAYS;
 	pub const Burn: Permill = Permill::from_perthousand(2);
 	pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry");
+	pub const PayoutSpendPeriod: BlockNumber = 30 * DAYS;
+	// The asset's interior location for the paying account. This is the Treasury
+	// pallet instance (which sits at index 37).
+	pub TreasuryInteriorLocation: InteriorMultiLocation = PalletInstance(37).into();
 
 	pub const TipCountdown: BlockNumber = 1 * DAYS;
 	pub const TipFindersFee: Percent = Percent::from_percent(20);
@@ -707,6 +720,7 @@ parameter_types! {
 	pub const MaxAuthorities: u32 = 100_000;
 	pub const MaxKeys: u32 = 10_000;
 	pub const MaxPeerInHeartbeats: u32 = 10_000;
+	pub const MaxBalance: Balance = Balance::max_value();
 }
 
 impl pallet_treasury::Config for Runtime {
@@ -726,6 +740,23 @@ impl pallet_treasury::Config for Runtime {
 	type WeightInfo = weights::pallet_treasury::WeightInfo<Runtime>;
 	type SpendFunds = ();
 	type SpendOrigin = TreasurySpender;
+	type AssetKind = VersionedLocatableAsset;
+	type Beneficiary = VersionedMultiLocation;
+	type BeneficiaryLookup = IdentityLookup<Self::Beneficiary>;
+	type Paymaster = PayOverXcm<
+		TreasuryInteriorLocation,
+		crate::xcm_config::XcmRouter,
+		crate::XcmPallet,
+		ConstU32<{ 6 * HOURS }>,
+		Self::Beneficiary,
+		Self::AssetKind,
+		LocatableAssetConverter,
+		VersionedMultiLocationConverter,
+	>;
+	type BalanceConverter = AssetRate;
+	type PayoutPeriod = PayoutSpendPeriod;
+	#[cfg(feature = "runtime-benchmarks")]
+	type BenchmarkHelper = runtime_common::impls::benchmarks::TreasuryArguments;
 }
 
 impl pallet_offences::Config for Runtime {
@@ -1331,6 +1362,18 @@ parameter_types! {
 	pub const MigrationMaxKeyLen: u32 = 512;
 }
 
+impl pallet_asset_rate::Config for Runtime {
+	type WeightInfo = weights::pallet_asset_rate::WeightInfo<Runtime>;
+	type RuntimeEvent = RuntimeEvent;
+	type CreateOrigin = EnsureRoot<AccountId>;
+	type RemoveOrigin = EnsureRoot<AccountId>;
+	type UpdateOrigin = EnsureRoot<AccountId>;
+	type Currency = Balances;
+	type AssetKind = <Runtime as pallet_treasury::Config>::AssetKind;
+	#[cfg(feature = "runtime-benchmarks")]
+	type BenchmarkHelper = runtime_common::impls::benchmarks::AssetRateArguments;
+}
+
 construct_runtime! {
 	pub enum Runtime
 	{
@@ -1443,6 +1486,9 @@ construct_runtime! {
 
 		// Generalized message queue
 		MessageQueue: pallet_message_queue::{Pallet, Call, Storage, Event<T>} = 100,
+
+		// Asset rate.
+		AssetRate: pallet_asset_rate::{Pallet, Call, Storage, Event<T>} = 101,
 	}
 }
 
@@ -1574,6 +1620,7 @@ mod benches {
 		[pallet_utility, Utility]
 		[pallet_vesting, Vesting]
 		[pallet_whitelist, Whitelist]
+		[pallet_asset_rate, AssetRate]
 		// XCM
 		[pallet_xcm, XcmPallet]
 		// NOTE: Make sure you point to the individual modules below.
diff --git a/polkadot/runtime/westend/src/weights/mod.rs b/polkadot/runtime/westend/src/weights/mod.rs
index faa94bcac5862..9ae6798d70b6e 100644
--- a/polkadot/runtime/westend/src/weights/mod.rs
+++ b/polkadot/runtime/westend/src/weights/mod.rs
@@ -17,6 +17,7 @@
 
 pub mod frame_election_provider_support;
 pub mod frame_system;
+pub mod pallet_asset_rate;
 pub mod pallet_bags_list;
 pub mod pallet_balances;
 pub mod pallet_conviction_voting;
diff --git a/polkadot/runtime/westend/src/weights/pallet_asset_rate.rs b/polkadot/runtime/westend/src/weights/pallet_asset_rate.rs
new file mode 100644
index 0000000000000..810dd01a17026
--- /dev/null
+++ b/polkadot/runtime/westend/src/weights/pallet_asset_rate.rs
@@ -0,0 +1,86 @@
+// Copyright (C) Parity Technologies (UK) Ltd.
+// This file is part of Polkadot.
+
+// Polkadot 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.
+
+// Polkadot 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 Polkadot.  If not, see <http://www.gnu.org/licenses/>.
+
+//! Autogenerated weights for `pallet_asset_rate`
+//!
+//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
+//! DATE: 2023-07-04, STEPS: `50`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! WORST CASE MAP SIZE: `1000000`
+//! HOSTNAME: `cob`, CPU: `<UNKNOWN>`
+//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: Some("polkadot-dev"), DB CACHE: 1024
+
+// Executed Command:
+// ./target/debug/polkadot
+// benchmark
+// pallet
+// --chain=polkadot-dev
+// --steps=50
+// --repeat=2
+// --pallet=pallet_asset_rate
+// --extrinsic=*
+// --wasm-execution=compiled
+// --heap-pages=4096
+// --output=./runtime/polkadot/src/weights/
+// --header=./file_header.txt
+
+#![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 `pallet_asset_rate`.
+pub struct WeightInfo<T>(PhantomData<T>);
+impl<T: frame_system::Config> pallet_asset_rate::WeightInfo for WeightInfo<T> {
+	/// Storage: AssetRate ConversionRateToNative (r:1 w:1)
+	/// Proof: AssetRate ConversionRateToNative (max_values: None, max_size: Some(1237), added: 3712, mode: MaxEncodedLen)
+	fn create() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `42`
+		//  Estimated: `4702`
+		// Minimum execution time: 67_000_000 picoseconds.
+		Weight::from_parts(69_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 4702))
+			.saturating_add(T::DbWeight::get().reads(1))
+			.saturating_add(T::DbWeight::get().writes(1))
+	}
+	/// Storage: AssetRate ConversionRateToNative (r:1 w:1)
+	/// Proof: AssetRate ConversionRateToNative (max_values: None, max_size: Some(1237), added: 3712, mode: MaxEncodedLen)
+	fn update() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `110`
+		//  Estimated: `4702`
+		// Minimum execution time: 69_000_000 picoseconds.
+		Weight::from_parts(71_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 4702))
+			.saturating_add(T::DbWeight::get().reads(1))
+			.saturating_add(T::DbWeight::get().writes(1))
+	}
+	/// Storage: AssetRate ConversionRateToNative (r:1 w:1)
+	/// Proof: AssetRate ConversionRateToNative (max_values: None, max_size: Some(1237), added: 3712, mode: MaxEncodedLen)
+	fn remove() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `110`
+		//  Estimated: `4702`
+		// Minimum execution time: 70_000_000 picoseconds.
+		Weight::from_parts(90_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 4702))
+			.saturating_add(T::DbWeight::get().reads(1))
+			.saturating_add(T::DbWeight::get().writes(1))
+	}
+}
diff --git a/polkadot/runtime/westend/src/weights/pallet_treasury.rs b/polkadot/runtime/westend/src/weights/pallet_treasury.rs
index e2eb6abfc7bbb..144e9d5b87238 100644
--- a/polkadot/runtime/westend/src/weights/pallet_treasury.rs
+++ b/polkadot/runtime/westend/src/weights/pallet_treasury.rs
@@ -17,25 +17,24 @@
 //! Autogenerated weights for `pallet_treasury`
 //!
 //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
-//! DATE: 2023-07-13, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! DATE: 2023-07-07, STEPS: `50`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]`
 //! WORST CASE MAP SIZE: `1000000`
-//! HOSTNAME: `runner-o7yfgx5n-project-163-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
-//! EXECUTION: ``, WASM-EXECUTION: `Compiled`, CHAIN: `Some("westend-dev")`, DB CACHE: 1024
+//! HOSTNAME: `cob`, CPU: `<UNKNOWN>`
+//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: Some("rococo-dev"), DB CACHE: 1024
 
 // Executed Command:
-// target/production/polkadot
+// ./target/debug/polkadot
 // benchmark
 // pallet
+// --chain=rococo-dev
 // --steps=50
-// --repeat=20
+// --repeat=2
+// --pallet=pallet_treasury
 // --extrinsic=*
 // --wasm-execution=compiled
 // --heap-pages=4096
-// --json-file=/builds/parity/mirrors/polkadot/.git/.artifacts/bench.json
-// --pallet=pallet_treasury
-// --chain=westend-dev
+// --output=./runtime/rococo/src/weights/
 // --header=./file_header.txt
-// --output=./runtime/westend/src/weights/
 
 #![cfg_attr(rustfmt, rustfmt_skip)]
 #![allow(unused_parens)]
@@ -48,103 +47,177 @@ use core::marker::PhantomData;
 /// Weight functions for `pallet_treasury`.
 pub struct WeightInfo<T>(PhantomData<T>);
 impl<T: frame_system::Config> pallet_treasury::WeightInfo for WeightInfo<T> {
-	/// Storage: `Treasury::ProposalCount` (r:1 w:1)
-	/// Proof: `Treasury::ProposalCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
-	/// Storage: `Treasury::Approvals` (r:1 w:1)
-	/// Proof: `Treasury::Approvals` (`max_values`: Some(1), `max_size`: Some(402), added: 897, mode: `MaxEncodedLen`)
-	/// Storage: `Treasury::Proposals` (r:0 w:1)
-	/// Proof: `Treasury::Proposals` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`)
-	fn spend() -> Weight {
+	/// Storage: Treasury ProposalCount (r:1 w:1)
+	/// Proof: Treasury ProposalCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
+	/// Storage: Treasury Approvals (r:1 w:1)
+	/// Proof: Treasury Approvals (max_values: Some(1), max_size: Some(402), added: 897, mode: MaxEncodedLen)
+	/// Storage: Treasury Proposals (r:0 w:1)
+	/// Proof: Treasury Proposals (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen)
+	fn spend_local() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `6`
+		//  Measured:  `42`
 		//  Estimated: `1887`
-		// Minimum execution time: 13_644_000 picoseconds.
-		Weight::from_parts(13_988_000, 0)
+		// Minimum execution time: 177_000_000 picoseconds.
+		Weight::from_parts(191_000_000, 0)
 			.saturating_add(Weight::from_parts(0, 1887))
 			.saturating_add(T::DbWeight::get().reads(2))
 			.saturating_add(T::DbWeight::get().writes(3))
 	}
-	/// Storage: `Treasury::ProposalCount` (r:1 w:1)
-	/// Proof: `Treasury::ProposalCount` (`max_values`: Some(1), `max_size`: Some(4), added: 499, mode: `MaxEncodedLen`)
-	/// Storage: `Treasury::Proposals` (r:0 w:1)
-	/// Proof: `Treasury::Proposals` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`)
+	/// Storage: Treasury ProposalCount (r:1 w:1)
+	/// Proof: Treasury ProposalCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
+	/// Storage: Treasury Proposals (r:0 w:1)
+	/// Proof: Treasury Proposals (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen)
 	fn propose_spend() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `107`
+		//  Measured:  `143`
 		//  Estimated: `1489`
-		// Minimum execution time: 26_304_000 picoseconds.
-		Weight::from_parts(26_850_000, 0)
+		// Minimum execution time: 354_000_000 picoseconds.
+		Weight::from_parts(376_000_000, 0)
 			.saturating_add(Weight::from_parts(0, 1489))
 			.saturating_add(T::DbWeight::get().reads(1))
 			.saturating_add(T::DbWeight::get().writes(2))
 	}
-	/// Storage: `Treasury::Proposals` (r:1 w:1)
-	/// Proof: `Treasury::Proposals` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`)
-	/// Storage: `System::Account` (r:1 w:1)
-	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	/// Storage: Treasury Proposals (r:1 w:1)
+	/// Proof: Treasury Proposals (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen)
+	/// Storage: System Account (r:1 w:1)
+	/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
 	fn reject_proposal() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `265`
+		//  Measured:  `301`
 		//  Estimated: `3593`
-		// Minimum execution time: 40_318_000 picoseconds.
-		Weight::from_parts(41_598_000, 0)
+		// Minimum execution time: 547_000_000 picoseconds.
+		Weight::from_parts(550_000_000, 0)
 			.saturating_add(Weight::from_parts(0, 3593))
 			.saturating_add(T::DbWeight::get().reads(2))
 			.saturating_add(T::DbWeight::get().writes(2))
 	}
-	/// Storage: `Treasury::Proposals` (r:1 w:0)
-	/// Proof: `Treasury::Proposals` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`)
-	/// Storage: `Treasury::Approvals` (r:1 w:1)
-	/// Proof: `Treasury::Approvals` (`max_values`: Some(1), `max_size`: Some(402), added: 897, mode: `MaxEncodedLen`)
+	/// Storage: Treasury Proposals (r:1 w:0)
+	/// Proof: Treasury Proposals (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen)
+	/// Storage: Treasury Approvals (r:1 w:1)
+	/// Proof: Treasury Approvals (max_values: Some(1), max_size: Some(402), added: 897, mode: MaxEncodedLen)
 	/// The range of component `p` is `[0, 99]`.
 	fn approve_proposal(p: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `433 + p * (8 ±0)`
+		//  Measured:  `470 + p * (8 ±0)`
 		//  Estimated: `3573`
-		// Minimum execution time: 8_250_000 picoseconds.
-		Weight::from_parts(10_937_873, 0)
+		// Minimum execution time: 104_000_000 picoseconds.
+		Weight::from_parts(121_184_402, 0)
 			.saturating_add(Weight::from_parts(0, 3573))
-			// Standard Error: 1_239
-			.saturating_add(Weight::from_parts(82_426, 0).saturating_mul(p.into()))
+			// Standard Error: 42_854
+			.saturating_add(Weight::from_parts(153_112, 0).saturating_mul(p.into()))
 			.saturating_add(T::DbWeight::get().reads(2))
 			.saturating_add(T::DbWeight::get().writes(1))
 	}
-	/// Storage: `Treasury::Approvals` (r:1 w:1)
-	/// Proof: `Treasury::Approvals` (`max_values`: Some(1), `max_size`: Some(402), added: 897, mode: `MaxEncodedLen`)
+	/// Storage: Treasury Approvals (r:1 w:1)
+	/// Proof: Treasury Approvals (max_values: Some(1), max_size: Some(402), added: 897, mode: MaxEncodedLen)
 	fn remove_approval() -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `90`
+		//  Measured:  `127`
 		//  Estimated: `1887`
-		// Minimum execution time: 6_170_000 picoseconds.
-		Weight::from_parts(6_366_000, 0)
+		// Minimum execution time: 80_000_000 picoseconds.
+		Weight::from_parts(82_000_000, 0)
 			.saturating_add(Weight::from_parts(0, 1887))
 			.saturating_add(T::DbWeight::get().reads(1))
 			.saturating_add(T::DbWeight::get().writes(1))
 	}
-	/// Storage: `Treasury::Deactivated` (r:1 w:1)
-	/// Proof: `Treasury::Deactivated` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
-	/// Storage: `Balances::InactiveIssuance` (r:1 w:1)
-	/// Proof: `Balances::InactiveIssuance` (`max_values`: Some(1), `max_size`: Some(16), added: 511, mode: `MaxEncodedLen`)
-	/// Storage: `Treasury::Approvals` (r:1 w:1)
-	/// Proof: `Treasury::Approvals` (`max_values`: Some(1), `max_size`: Some(402), added: 897, mode: `MaxEncodedLen`)
-	/// Storage: `Treasury::Proposals` (r:100 w:100)
-	/// Proof: `Treasury::Proposals` (`max_values`: None, `max_size`: Some(108), added: 2583, mode: `MaxEncodedLen`)
-	/// Storage: `System::Account` (r:200 w:200)
-	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
-	/// The range of component `p` is `[0, 100]`.
+	/// Storage: Treasury Deactivated (r:1 w:1)
+	/// Proof: Treasury Deactivated (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen)
+	/// Storage: Balances InactiveIssuance (r:1 w:1)
+	/// Proof: Balances InactiveIssuance (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen)
+	/// Storage: Treasury Approvals (r:1 w:1)
+	/// Proof: Treasury Approvals (max_values: Some(1), max_size: Some(402), added: 897, mode: MaxEncodedLen)
+	/// Storage: Treasury Proposals (r:99 w:99)
+	/// Proof: Treasury Proposals (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen)
+	/// Storage: System Account (r:199 w:199)
+	/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
+	/// Storage: Bounties BountyApprovals (r:1 w:1)
+	/// Proof: Bounties BountyApprovals (max_values: Some(1), max_size: Some(402), added: 897, mode: MaxEncodedLen)
+	/// The range of component `p` is `[0, 99]`.
 	fn on_initialize_proposals(p: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `175 + p * (251 ±0)`
-		//  Estimated: `1887 + p * (5206 ±0)`
-		// Minimum execution time: 39_691_000 picoseconds.
-		Weight::from_parts(29_703_313, 0)
-			.saturating_add(Weight::from_parts(0, 1887))
-			// Standard Error: 18_540
-			.saturating_add(Weight::from_parts(42_601_290, 0).saturating_mul(p.into()))
-			.saturating_add(T::DbWeight::get().reads(3))
+		//  Measured:  `331 + p * (251 ±0)`
+		//  Estimated: `3593 + p * (5206 ±0)`
+		// Minimum execution time: 887_000_000 picoseconds.
+		Weight::from_parts(828_616_021, 0)
+			.saturating_add(Weight::from_parts(0, 3593))
+			// Standard Error: 695_351
+			.saturating_add(Weight::from_parts(566_114_524, 0).saturating_mul(p.into()))
+			.saturating_add(T::DbWeight::get().reads(5))
 			.saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(p.into())))
-			.saturating_add(T::DbWeight::get().writes(3))
+			.saturating_add(T::DbWeight::get().writes(5))
 			.saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(p.into())))
 			.saturating_add(Weight::from_parts(0, 5206).saturating_mul(p.into()))
 	}
+	/// Storage: AssetRate ConversionRateToNative (r:1 w:0)
+	/// Proof: AssetRate ConversionRateToNative (max_values: None, max_size: Some(1237), added: 3712, mode: MaxEncodedLen)
+	/// Storage: Treasury SpendCount (r:1 w:1)
+	/// Proof: Treasury SpendCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
+	/// Storage: Treasury Spends (r:0 w:1)
+	/// Proof: Treasury Spends (max_values: None, max_size: Some(1848), added: 4323, mode: MaxEncodedLen)
+	fn spend() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `114`
+		//  Estimated: `4702`
+		// Minimum execution time: 208_000_000 picoseconds.
+		Weight::from_parts(222_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 4702))
+			.saturating_add(T::DbWeight::get().reads(2))
+			.saturating_add(T::DbWeight::get().writes(2))
+	}
+	/// Storage: Treasury Spends (r:1 w:1)
+	/// Proof: Treasury Spends (max_values: None, max_size: Some(1848), added: 4323, mode: MaxEncodedLen)
+	/// Storage: XcmPallet QueryCounter (r:1 w:1)
+	/// Proof Skipped: XcmPallet QueryCounter (max_values: Some(1), max_size: None, mode: Measured)
+	/// Storage: Configuration ActiveConfig (r:1 w:0)
+	/// Proof Skipped: Configuration ActiveConfig (max_values: Some(1), max_size: None, mode: Measured)
+	/// Storage: Dmp DeliveryFeeFactor (r:1 w:0)
+	/// Proof Skipped: Dmp DeliveryFeeFactor (max_values: None, max_size: None, mode: Measured)
+	/// Storage: XcmPallet SupportedVersion (r:1 w:0)
+	/// Proof Skipped: XcmPallet SupportedVersion (max_values: None, max_size: None, mode: Measured)
+	/// Storage: XcmPallet VersionDiscoveryQueue (r:1 w:1)
+	/// Proof Skipped: XcmPallet VersionDiscoveryQueue (max_values: Some(1), max_size: None, mode: Measured)
+	/// Storage: XcmPallet SafeXcmVersion (r:1 w:0)
+	/// Proof Skipped: XcmPallet SafeXcmVersion (max_values: Some(1), max_size: None, mode: Measured)
+	/// Storage: Dmp DownwardMessageQueues (r:1 w:1)
+	/// Proof Skipped: Dmp DownwardMessageQueues (max_values: None, max_size: None, mode: Measured)
+	/// Storage: Dmp DownwardMessageQueueHeads (r:1 w:1)
+	/// Proof Skipped: Dmp DownwardMessageQueueHeads (max_values: None, max_size: None, mode: Measured)
+	/// Storage: XcmPallet Queries (r:0 w:1)
+	/// Proof Skipped: XcmPallet Queries (max_values: None, max_size: None, mode: Measured)
+	fn payout() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `737`
+		//  Estimated: `5313`
+		// Minimum execution time: 551_000_000 picoseconds.
+		Weight::from_parts(569_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 5313))
+			.saturating_add(T::DbWeight::get().reads(9))
+			.saturating_add(T::DbWeight::get().writes(6))
+	}
+	/// Storage: Treasury Spends (r:1 w:1)
+	/// Proof: Treasury Spends (max_values: None, max_size: Some(1848), added: 4323, mode: MaxEncodedLen)
+	/// Storage: XcmPallet Queries (r:1 w:1)
+	/// Proof Skipped: XcmPallet Queries (max_values: None, max_size: None, mode: Measured)
+	fn check_status() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `442`
+		//  Estimated: `5313`
+		// Minimum execution time: 245_000_000 picoseconds.
+		Weight::from_parts(281_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 5313))
+			.saturating_add(T::DbWeight::get().reads(2))
+			.saturating_add(T::DbWeight::get().writes(2))
+	}
+	/// Storage: Treasury Spends (r:1 w:1)
+	/// Proof: Treasury Spends (max_values: None, max_size: Some(1848), added: 4323, mode: MaxEncodedLen)
+	fn void_spend() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `172`
+		//  Estimated: `5313`
+		// Minimum execution time: 147_000_000 picoseconds.
+		Weight::from_parts(160_000_000, 0)
+			.saturating_add(Weight::from_parts(0, 5313))
+			.saturating_add(T::DbWeight::get().reads(1))
+			.saturating_add(T::DbWeight::get().writes(1))
+	}
 }
diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs
index f018639b732e3..9e3b8153e2b5c 100644
--- a/substrate/bin/node/runtime/src/lib.rs
+++ b/substrate/bin/node/runtime/src/lib.rs
@@ -37,7 +37,7 @@ use frame_support::{
 	parameter_types,
 	traits::{
 		fungible::{Balanced, Credit, HoldConsideration, ItemOf},
-		tokens::{nonfungibles_v2::Inspect, GetSalary, PayFromAccount},
+		tokens::{nonfungibles_v2::Inspect, pay::PayAssetFromAccount, GetSalary, PayFromAccount},
 		AsEnsureOriginWithArg, ConstBool, ConstU128, ConstU16, ConstU32, Contains, Currency,
 		EitherOfDiverse, EqualPrivilegeOnly, Imbalance, InsideBoth, InstanceFilter,
 		KeyOwnerProofSystem, LinearStoragePrice, LockIdentifier, Nothing, OnUnbalanced,
@@ -1186,6 +1186,7 @@ parameter_types! {
 	pub const MaximumReasonLength: u32 = 300;
 	pub const MaxApprovals: u32 = 100;
 	pub const MaxBalance: Balance = Balance::max_value();
+	pub const SpendPayoutPeriod: BlockNumber = 30 * DAYS;
 }
 
 impl pallet_treasury::Config for Runtime {
@@ -1211,6 +1212,14 @@ impl pallet_treasury::Config for Runtime {
 	type WeightInfo = pallet_treasury::weights::SubstrateWeight<Runtime>;
 	type MaxApprovals = MaxApprovals;
 	type SpendOrigin = EnsureWithSuccess<EnsureRoot<AccountId>, AccountId, MaxBalance>;
+	type AssetKind = u32;
+	type Beneficiary = AccountId;
+	type BeneficiaryLookup = Indices;
+	type Paymaster = PayAssetFromAccount<Assets, TreasuryAccount>;
+	type BalanceConverter = AssetRate;
+	type PayoutPeriod = SpendPayoutPeriod;
+	#[cfg(feature = "runtime-benchmarks")]
+	type BenchmarkHelper = ();
 }
 
 impl pallet_asset_rate::Config for Runtime {
diff --git a/substrate/frame/asset-rate/src/lib.rs b/substrate/frame/asset-rate/src/lib.rs
index c3dc551f876d0..d4afca8b73c4b 100644
--- a/substrate/frame/asset-rate/src/lib.rs
+++ b/substrate/frame/asset-rate/src/lib.rs
@@ -240,4 +240,9 @@ where
 			.ok_or(pallet::Error::<T>::UnknownAssetKind.into())?;
 		Ok(rate.saturating_mul_int(balance))
 	}
+	/// Set a conversion rate to `1` for the `asset_id`.
+	#[cfg(feature = "runtime-benchmarks")]
+	fn ensure_successful(asset_id: AssetKindOf<T>) {
+		pallet::ConversionRateToNative::<T>::set(asset_id.clone(), Some(1.into()));
+	}
 }
diff --git a/substrate/frame/bounties/src/tests.rs b/substrate/frame/bounties/src/tests.rs
index a6fb89bb86012..4083b05b629cd 100644
--- a/substrate/frame/bounties/src/tests.rs
+++ b/substrate/frame/bounties/src/tests.rs
@@ -24,7 +24,10 @@ use crate as pallet_bounties;
 
 use frame_support::{
 	assert_noop, assert_ok, parameter_types,
-	traits::{ConstU32, ConstU64, OnInitialize},
+	traits::{
+		tokens::{PayFromAccount, UnityAssetBalanceConversion},
+		ConstU32, ConstU64, OnInitialize,
+	},
 	PalletId,
 };
 
@@ -104,6 +107,8 @@ parameter_types! {
 	pub const TreasuryPalletId2: PalletId = PalletId(*b"py/trsr2");
 	pub static SpendLimit: Balance = u64::MAX;
 	pub static SpendLimit1: Balance = u64::MAX;
+	pub TreasuryAccount: u128 = Treasury::account_id();
+	pub TreasuryInstance1Account: u128 = Treasury1::account_id();
 }
 
 impl pallet_treasury::Config for Test {
@@ -123,6 +128,14 @@ impl pallet_treasury::Config for Test {
 	type SpendFunds = Bounties;
 	type MaxApprovals = ConstU32<100>;
 	type SpendOrigin = frame_system::EnsureRootWithSuccess<Self::AccountId, SpendLimit>;
+	type AssetKind = ();
+	type Beneficiary = Self::AccountId;
+	type BeneficiaryLookup = IdentityLookup<Self::Beneficiary>;
+	type Paymaster = PayFromAccount<Balances, TreasuryAccount>;
+	type BalanceConverter = UnityAssetBalanceConversion;
+	type PayoutPeriod = ConstU64<10>;
+	#[cfg(feature = "runtime-benchmarks")]
+	type BenchmarkHelper = ();
 }
 
 impl pallet_treasury::Config<Instance1> for Test {
@@ -142,6 +155,14 @@ impl pallet_treasury::Config<Instance1> for Test {
 	type SpendFunds = Bounties1;
 	type MaxApprovals = ConstU32<100>;
 	type SpendOrigin = frame_system::EnsureRootWithSuccess<Self::AccountId, SpendLimit1>;
+	type AssetKind = ();
+	type Beneficiary = Self::AccountId;
+	type BeneficiaryLookup = IdentityLookup<Self::Beneficiary>;
+	type Paymaster = PayFromAccount<Balances, TreasuryInstance1Account>;
+	type BalanceConverter = UnityAssetBalanceConversion;
+	type PayoutPeriod = ConstU64<10>;
+	#[cfg(feature = "runtime-benchmarks")]
+	type BenchmarkHelper = ();
 }
 
 parameter_types! {
diff --git a/substrate/frame/child-bounties/src/tests.rs b/substrate/frame/child-bounties/src/tests.rs
index 24a6410f29f78..1fa3d944f3de1 100644
--- a/substrate/frame/child-bounties/src/tests.rs
+++ b/substrate/frame/child-bounties/src/tests.rs
@@ -24,7 +24,10 @@ use crate as pallet_child_bounties;
 
 use frame_support::{
 	assert_noop, assert_ok, parameter_types,
-	traits::{ConstU32, ConstU64, OnInitialize},
+	traits::{
+		tokens::{PayFromAccount, UnityAssetBalanceConversion},
+		ConstU32, ConstU64, OnInitialize,
+	},
 	weights::Weight,
 	PalletId,
 };
@@ -104,6 +107,7 @@ parameter_types! {
 	pub const ProposalBond: Permill = Permill::from_percent(5);
 	pub const Burn: Permill = Permill::from_percent(50);
 	pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry");
+	pub TreasuryAccount: u128 = Treasury::account_id();
 	pub const SpendLimit: Balance = u64::MAX;
 }
 
@@ -124,6 +128,14 @@ impl pallet_treasury::Config for Test {
 	type SpendFunds = Bounties;
 	type MaxApprovals = ConstU32<100>;
 	type SpendOrigin = frame_system::EnsureRootWithSuccess<Self::AccountId, SpendLimit>;
+	type AssetKind = ();
+	type Beneficiary = Self::AccountId;
+	type BeneficiaryLookup = IdentityLookup<Self::Beneficiary>;
+	type Paymaster = PayFromAccount<Balances, TreasuryAccount>;
+	type BalanceConverter = UnityAssetBalanceConversion;
+	type PayoutPeriod = ConstU64<10>;
+	#[cfg(feature = "runtime-benchmarks")]
+	type BenchmarkHelper = ();
 }
 parameter_types! {
 	// This will be 50% of the bounty fee.
diff --git a/substrate/frame/support/src/traits/tokens.rs b/substrate/frame/support/src/traits/tokens.rs
index 253b49c6671f8..3635311e64357 100644
--- a/substrate/frame/support/src/traits/tokens.rs
+++ b/substrate/frame/support/src/traits/tokens.rs
@@ -31,6 +31,7 @@ pub mod pay;
 pub use misc::{
 	AssetId, Balance, BalanceStatus, ConversionFromAssetBalance, ConversionToAssetBalance,
 	ConvertRank, DepositConsequence, ExistenceRequirement, Fortitude, GetSalary, Locker, Precision,
-	Preservation, Provenance, Restriction, WithdrawConsequence, WithdrawReasons,
+	Preservation, Provenance, Restriction, UnityAssetBalanceConversion, WithdrawConsequence,
+	WithdrawReasons,
 };
 pub use pay::{Pay, PayFromAccount, PaymentStatus};
diff --git a/substrate/frame/support/src/traits/tokens/misc.rs b/substrate/frame/support/src/traits/tokens/misc.rs
index e8587be101794..fd497bc4eda6f 100644
--- a/substrate/frame/support/src/traits/tokens/misc.rs
+++ b/substrate/frame/support/src/traits/tokens/misc.rs
@@ -277,6 +277,26 @@ pub trait ConversionFromAssetBalance<AssetBalance, AssetId, OutBalance> {
 		balance: AssetBalance,
 		asset_id: AssetId,
 	) -> Result<OutBalance, Self::Error>;
+	/// Ensures that a conversion for the `asset_id` will be successful if done immediately after
+	/// this call.
+	#[cfg(feature = "runtime-benchmarks")]
+	fn ensure_successful(asset_id: AssetId);
+}
+
+/// Implements [`ConversionFromAssetBalance`], enabling a 1:1 conversion of the asset balance
+/// value to the balance.
+pub struct UnityAssetBalanceConversion;
+impl<AssetBalance, AssetId, OutBalance>
+	ConversionFromAssetBalance<AssetBalance, AssetId, OutBalance> for UnityAssetBalanceConversion
+where
+	AssetBalance: Into<OutBalance>,
+{
+	type Error = ();
+	fn from_asset_balance(balance: AssetBalance, _: AssetId) -> Result<OutBalance, Self::Error> {
+		Ok(balance.into())
+	}
+	#[cfg(feature = "runtime-benchmarks")]
+	fn ensure_successful(_: AssetId) {}
 }
 
 /// Trait to handle NFT locking mechanism to ensure interactions with the asset can be implemented
diff --git a/substrate/frame/support/src/traits/tokens/pay.rs b/substrate/frame/support/src/traits/tokens/pay.rs
index 78f8e7b873480..18af7e5e54838 100644
--- a/substrate/frame/support/src/traits/tokens/pay.rs
+++ b/substrate/frame/support/src/traits/tokens/pay.rs
@@ -23,7 +23,7 @@ use sp_core::{RuntimeDebug, TypedGet};
 use sp_runtime::DispatchError;
 use sp_std::fmt::Debug;
 
-use super::{fungible, Balance, Preservation::Expendable};
+use super::{fungible, fungibles, Balance, Preservation::Expendable};
 
 /// Can be implemented by `PayFromAccount` using a `fungible` impl, but can also be implemented with
 /// XCM/MultiAsset and made generic over assets.
@@ -107,3 +107,36 @@ impl<A: TypedGet, F: fungible::Mutate<A::Type>> Pay for PayFromAccount<F, A> {
 	#[cfg(feature = "runtime-benchmarks")]
 	fn ensure_concluded(_: Self::Id) {}
 }
+
+/// Simple implementation of `Pay` for assets which makes a payment from a "pot" - i.e. a single
+/// account.
+pub struct PayAssetFromAccount<F, A>(sp_std::marker::PhantomData<(F, A)>);
+impl<A, F> frame_support::traits::tokens::Pay for PayAssetFromAccount<F, A>
+where
+	A: TypedGet,
+	F: fungibles::Mutate<A::Type> + fungibles::Create<A::Type>,
+{
+	type Balance = F::Balance;
+	type Beneficiary = A::Type;
+	type AssetKind = F::AssetId;
+	type Id = ();
+	type Error = DispatchError;
+	fn pay(
+		who: &Self::Beneficiary,
+		asset: Self::AssetKind,
+		amount: Self::Balance,
+	) -> Result<Self::Id, Self::Error> {
+		<F as fungibles::Mutate<_>>::transfer(asset, &A::get(), who, amount, Expendable)?;
+		Ok(())
+	}
+	fn check_payment(_: ()) -> PaymentStatus {
+		PaymentStatus::Success
+	}
+	#[cfg(feature = "runtime-benchmarks")]
+	fn ensure_successful(_: &Self::Beneficiary, asset: Self::AssetKind, amount: Self::Balance) {
+		<F as fungibles::Create<_>>::create(asset.clone(), A::get(), true, amount).unwrap();
+		<F as fungibles::Mutate<_>>::mint_into(asset, &A::get(), amount).unwrap();
+	}
+	#[cfg(feature = "runtime-benchmarks")]
+	fn ensure_concluded(_: Self::Id) {}
+}
diff --git a/substrate/frame/tips/src/tests.rs b/substrate/frame/tips/src/tests.rs
index 20d4b2c1a4c11..8fe111afc26a4 100644
--- a/substrate/frame/tips/src/tests.rs
+++ b/substrate/frame/tips/src/tests.rs
@@ -29,7 +29,10 @@ use sp_storage::Storage;
 use frame_support::{
 	assert_noop, assert_ok, parameter_types,
 	storage::StoragePrefixedMap,
-	traits::{ConstU32, ConstU64, SortedMembers, StorageVersion},
+	traits::{
+		tokens::{PayFromAccount, UnityAssetBalanceConversion},
+		ConstU32, ConstU64, SortedMembers, StorageVersion,
+	},
 	PalletId,
 };
 
@@ -123,7 +126,10 @@ parameter_types! {
 	pub const Burn: Permill = Permill::from_percent(50);
 	pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry");
 	pub const TreasuryPalletId2: PalletId = PalletId(*b"py/trsr2");
+	pub TreasuryAccount: u128 = Treasury::account_id();
+	pub TreasuryInstance1Account: u128 = Treasury1::account_id();
 }
+
 impl pallet_treasury::Config for Test {
 	type PalletId = TreasuryPalletId;
 	type Currency = pallet_balances::Pallet<Test>;
@@ -141,6 +147,14 @@ impl pallet_treasury::Config for Test {
 	type SpendFunds = ();
 	type MaxApprovals = ConstU32<100>;
 	type SpendOrigin = frame_support::traits::NeverEnsureOrigin<u64>;
+	type AssetKind = ();
+	type Beneficiary = Self::AccountId;
+	type BeneficiaryLookup = IdentityLookup<Self::Beneficiary>;
+	type Paymaster = PayFromAccount<Balances, TreasuryAccount>;
+	type BalanceConverter = UnityAssetBalanceConversion;
+	type PayoutPeriod = ConstU64<10>;
+	#[cfg(feature = "runtime-benchmarks")]
+	type BenchmarkHelper = ();
 }
 
 impl pallet_treasury::Config<Instance1> for Test {
@@ -160,6 +174,14 @@ impl pallet_treasury::Config<Instance1> for Test {
 	type SpendFunds = ();
 	type MaxApprovals = ConstU32<100>;
 	type SpendOrigin = frame_support::traits::NeverEnsureOrigin<u64>;
+	type AssetKind = ();
+	type Beneficiary = Self::AccountId;
+	type BeneficiaryLookup = IdentityLookup<Self::Beneficiary>;
+	type Paymaster = PayFromAccount<Balances, TreasuryInstance1Account>;
+	type BalanceConverter = UnityAssetBalanceConversion;
+	type PayoutPeriod = ConstU64<10>;
+	#[cfg(feature = "runtime-benchmarks")]
+	type BenchmarkHelper = ();
 }
 
 parameter_types! {
diff --git a/substrate/frame/treasury/Cargo.toml b/substrate/frame/treasury/Cargo.toml
index 785564cd9888d..f7f7a6ae89c56 100644
--- a/substrate/frame/treasury/Cargo.toml
+++ b/substrate/frame/treasury/Cargo.toml
@@ -17,6 +17,7 @@ codec = { package = "parity-scale-codec", version = "3.6.1", default-features =
 	"derive",
 	"max-encoded-len",
 ] }
+docify = "0.2.0"
 impl-trait-for-tuples = "0.2.2"
 scale-info = { version = "2.5.0", default-features = false, features = ["derive"] }
 serde = { version = "1.0.188", features = ["derive"], optional = true }
@@ -26,11 +27,12 @@ frame-system = { path = "../system", default-features = false}
 pallet-balances = { path = "../balances", default-features = false}
 sp-runtime = { path = "../../primitives/runtime", default-features = false}
 sp-std = { path = "../../primitives/std", default-features = false}
+sp-core = { path = "../../primitives/core", default-features = false, optional = true}
 
 [dev-dependencies]
-sp-core = { path = "../../primitives/core" }
 sp-io = { path = "../../primitives/io" }
 pallet-utility = { path = "../utility" }
+sp-core = { path = "../../primitives/core", default-features = false }
 
 [features]
 default = [ "std" ]
@@ -43,12 +45,13 @@ std = [
 	"pallet-utility/std",
 	"scale-info/std",
 	"serde",
-	"sp-core/std",
+	"sp-core?/std",
 	"sp-io/std",
 	"sp-runtime/std",
 	"sp-std/std",
 ]
 runtime-benchmarks = [
+	"dep:sp-core",
 	"frame-benchmarking/runtime-benchmarks",
 	"frame-support/runtime-benchmarks",
 	"frame-system/runtime-benchmarks",
diff --git a/substrate/frame/treasury/src/benchmarking.rs b/substrate/frame/treasury/src/benchmarking.rs
index 24c290ddb665e..f5f73ea8ddabd 100644
--- a/substrate/frame/treasury/src/benchmarking.rs
+++ b/substrate/frame/treasury/src/benchmarking.rs
@@ -21,12 +21,41 @@
 
 use super::{Pallet as Treasury, *};
 
-use frame_benchmarking::v1::{account, benchmarks_instance_pallet, BenchmarkError};
+use frame_benchmarking::{
+	v1::{account, BenchmarkError},
+	v2::*,
+};
 use frame_support::{
 	ensure,
-	traits::{EnsureOrigin, OnInitialize, UnfilteredDispatchable},
+	traits::{
+		tokens::{ConversionFromAssetBalance, PaymentStatus},
+		EnsureOrigin, OnInitialize,
+	},
 };
 use frame_system::RawOrigin;
+use sp_core::crypto::FromEntropy;
+
+/// Trait describing factory functions for dispatchables' parameters.
+pub trait ArgumentsFactory<AssetKind, Beneficiary> {
+	/// Factory function for an asset kind.
+	fn create_asset_kind(seed: u32) -> AssetKind;
+	/// Factory function for a beneficiary.
+	fn create_beneficiary(seed: [u8; 32]) -> Beneficiary;
+}
+
+/// Implementation that expects the parameters implement the [`FromEntropy`] trait.
+impl<AssetKind, Beneficiary> ArgumentsFactory<AssetKind, Beneficiary> for ()
+where
+	AssetKind: FromEntropy,
+	Beneficiary: FromEntropy,
+{
+	fn create_asset_kind(seed: u32) -> AssetKind {
+		AssetKind::from_entropy(&mut seed.encode().as_slice()).unwrap()
+	}
+	fn create_beneficiary(seed: [u8; 32]) -> Beneficiary {
+		Beneficiary::from_entropy(&mut seed.as_slice()).unwrap()
+	}
+}
 
 const SEED: u32 = 0;
 
@@ -66,81 +95,245 @@ fn assert_last_event<T: Config<I>, I: 'static>(generic_event: <T as Config<I>>::
 	frame_system::Pallet::<T>::assert_last_event(generic_event.into());
 }
 
-benchmarks_instance_pallet! {
+// Create the arguments for the `spend` dispatchable.
+fn create_spend_arguments<T: Config<I>, I: 'static>(
+	seed: u32,
+) -> (T::AssetKind, AssetBalanceOf<T, I>, T::Beneficiary, BeneficiaryLookupOf<T, I>) {
+	let asset_kind = T::BenchmarkHelper::create_asset_kind(seed);
+	let beneficiary = T::BenchmarkHelper::create_beneficiary([seed.try_into().unwrap(); 32]);
+	let beneficiary_lookup = T::BeneficiaryLookup::unlookup(beneficiary.clone());
+	(asset_kind, 100u32.into(), beneficiary, beneficiary_lookup)
+}
+
+#[instance_benchmarks]
+mod benchmarks {
+	use super::*;
+
 	// This benchmark is short-circuited if `SpendOrigin` cannot provide
 	// a successful origin, in which case `spend` is un-callable and can use weight=0.
-	spend {
+	#[benchmark]
+	fn spend_local() -> Result<(), BenchmarkError> {
 		let (_, value, beneficiary_lookup) = setup_proposal::<T, _>(SEED);
-		let origin = T::SpendOrigin::try_successful_origin();
+		let origin =
+			T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
 		let beneficiary = T::Lookup::lookup(beneficiary_lookup.clone()).unwrap();
-		let call = Call::<T, I>::spend { amount: value, beneficiary: beneficiary_lookup };
-	}: {
-		if let Ok(origin) = origin.clone() {
-			call.dispatch_bypass_filter(origin)?;
-		}
-	}
-	verify {
-		if origin.is_ok() {
-			assert_last_event::<T, I>(Event::SpendApproved { proposal_index: 0, amount: value, beneficiary }.into())
-		}
+
+		#[extrinsic_call]
+		_(origin as T::RuntimeOrigin, value, beneficiary_lookup);
+
+		assert_last_event::<T, I>(
+			Event::SpendApproved { proposal_index: 0, amount: value, beneficiary }.into(),
+		);
+		Ok(())
 	}
 
-	propose_spend {
+	#[benchmark]
+	fn propose_spend() -> Result<(), BenchmarkError> {
 		let (caller, value, beneficiary_lookup) = setup_proposal::<T, _>(SEED);
 		// Whitelist caller account from further DB operations.
 		let caller_key = frame_system::Account::<T>::hashed_key_for(&caller);
 		frame_benchmarking::benchmarking::add_to_whitelist(caller_key.into());
-	}: _(RawOrigin::Signed(caller), value, beneficiary_lookup)
 
-	reject_proposal {
+		#[extrinsic_call]
+		_(RawOrigin::Signed(caller), value, beneficiary_lookup);
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn reject_proposal() -> Result<(), BenchmarkError> {
 		let (caller, value, beneficiary_lookup) = setup_proposal::<T, _>(SEED);
 		#[allow(deprecated)]
 		Treasury::<T, _>::propose_spend(
 			RawOrigin::Signed(caller).into(),
 			value,
-			beneficiary_lookup
+			beneficiary_lookup,
 		)?;
 		let proposal_id = Treasury::<T, _>::proposal_count() - 1;
 		let reject_origin =
 			T::RejectOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
-	}: _<T::RuntimeOrigin>(reject_origin, proposal_id)
 
-	approve_proposal {
-		let p in 0 .. T::MaxApprovals::get() - 1;
+		#[extrinsic_call]
+		_(reject_origin as T::RuntimeOrigin, proposal_id);
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn approve_proposal(
+		p: Linear<0, { T::MaxApprovals::get() - 1 }>,
+	) -> Result<(), BenchmarkError> {
 		create_approved_proposals::<T, _>(p)?;
 		let (caller, value, beneficiary_lookup) = setup_proposal::<T, _>(SEED);
 		#[allow(deprecated)]
 		Treasury::<T, _>::propose_spend(
 			RawOrigin::Signed(caller).into(),
 			value,
-			beneficiary_lookup
+			beneficiary_lookup,
 		)?;
 		let proposal_id = Treasury::<T, _>::proposal_count() - 1;
 		let approve_origin =
 			T::ApproveOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
-	}: _<T::RuntimeOrigin>(approve_origin, proposal_id)
 
-	remove_approval {
+		#[extrinsic_call]
+		_(approve_origin as T::RuntimeOrigin, proposal_id);
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn remove_approval() -> Result<(), BenchmarkError> {
 		let (caller, value, beneficiary_lookup) = setup_proposal::<T, _>(SEED);
 		#[allow(deprecated)]
 		Treasury::<T, _>::propose_spend(
 			RawOrigin::Signed(caller).into(),
 			value,
-			beneficiary_lookup
+			beneficiary_lookup,
 		)?;
 		let proposal_id = Treasury::<T, _>::proposal_count() - 1;
 		#[allow(deprecated)]
 		Treasury::<T, I>::approve_proposal(RawOrigin::Root.into(), proposal_id)?;
 		let reject_origin =
 			T::RejectOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
-	}: _<T::RuntimeOrigin>(reject_origin, proposal_id)
 
-	on_initialize_proposals {
-		let p in 0 .. T::MaxApprovals::get();
+		#[extrinsic_call]
+		_(reject_origin as T::RuntimeOrigin, proposal_id);
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn on_initialize_proposals(
+		p: Linear<0, { T::MaxApprovals::get() - 1 }>,
+	) -> Result<(), BenchmarkError> {
 		setup_pot_account::<T, _>();
 		create_approved_proposals::<T, _>(p)?;
-	}: {
-		Treasury::<T, _>::on_initialize(frame_system::pallet_prelude::BlockNumberFor::<T>::zero());
+
+		#[block]
+		{
+			Treasury::<T, _>::on_initialize(0u32.into());
+		}
+
+		Ok(())
+	}
+
+	#[benchmark]
+	fn spend() -> Result<(), BenchmarkError> {
+		let origin =
+			T::SpendOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
+		let (asset_kind, amount, beneficiary, beneficiary_lookup) =
+			create_spend_arguments::<T, _>(SEED);
+		T::BalanceConverter::ensure_successful(asset_kind.clone());
+
+		#[extrinsic_call]
+		_(
+			origin as T::RuntimeOrigin,
+			Box::new(asset_kind.clone()),
+			amount,
+			Box::new(beneficiary_lookup),
+			None,
+		);
+
+		let valid_from = frame_system::Pallet::<T>::block_number();
+		let expire_at = valid_from.saturating_add(T::PayoutPeriod::get());
+		assert_last_event::<T, I>(
+			Event::AssetSpendApproved {
+				index: 0,
+				asset_kind,
+				amount,
+				beneficiary,
+				valid_from,
+				expire_at,
+			}
+			.into(),
+		);
+		Ok(())
+	}
+
+	#[benchmark]
+	fn payout() -> Result<(), BenchmarkError> {
+		let origin = T::SpendOrigin::try_successful_origin().map_err(|_| "No origin")?;
+		let (asset_kind, amount, beneficiary, beneficiary_lookup) =
+			create_spend_arguments::<T, _>(SEED);
+		T::BalanceConverter::ensure_successful(asset_kind.clone());
+		Treasury::<T, _>::spend(
+			origin,
+			Box::new(asset_kind.clone()),
+			amount,
+			Box::new(beneficiary_lookup),
+			None,
+		)?;
+		T::Paymaster::ensure_successful(&beneficiary, asset_kind, amount);
+		let caller: T::AccountId = account("caller", 0, SEED);
+
+		#[extrinsic_call]
+		_(RawOrigin::Signed(caller.clone()), 0u32);
+
+		let id = match Spends::<T, I>::get(0).unwrap().status {
+			PaymentState::Attempted { id, .. } => {
+				assert_ne!(T::Paymaster::check_payment(id), PaymentStatus::Failure);
+				id
+			},
+			_ => panic!("No payout attempt made"),
+		};
+		assert_last_event::<T, I>(Event::Paid { index: 0, payment_id: id }.into());
+		assert!(Treasury::<T, _>::payout(RawOrigin::Signed(caller).into(), 0u32).is_err());
+		Ok(())
+	}
+
+	#[benchmark]
+	fn check_status() -> Result<(), BenchmarkError> {
+		let origin = T::SpendOrigin::try_successful_origin().map_err(|_| "No origin")?;
+		let (asset_kind, amount, beneficiary, beneficiary_lookup) =
+			create_spend_arguments::<T, _>(SEED);
+		T::BalanceConverter::ensure_successful(asset_kind.clone());
+		Treasury::<T, _>::spend(
+			origin,
+			Box::new(asset_kind.clone()),
+			amount,
+			Box::new(beneficiary_lookup),
+			None,
+		)?;
+		T::Paymaster::ensure_successful(&beneficiary, asset_kind, amount);
+		let caller: T::AccountId = account("caller", 0, SEED);
+		Treasury::<T, _>::payout(RawOrigin::Signed(caller.clone()).into(), 0u32)?;
+		match Spends::<T, I>::get(0).unwrap().status {
+			PaymentState::Attempted { id, .. } => {
+				T::Paymaster::ensure_concluded(id);
+			},
+			_ => panic!("No payout attempt made"),
+		};
+
+		#[extrinsic_call]
+		_(RawOrigin::Signed(caller.clone()), 0u32);
+
+		if let Some(s) = Spends::<T, I>::get(0) {
+			assert!(!matches!(s.status, PaymentState::Attempted { .. }));
+		}
+		Ok(())
+	}
+
+	#[benchmark]
+	fn void_spend() -> Result<(), BenchmarkError> {
+		let origin = T::SpendOrigin::try_successful_origin().map_err(|_| "No origin")?;
+		let (asset_kind, amount, _, beneficiary_lookup) = create_spend_arguments::<T, _>(SEED);
+		T::BalanceConverter::ensure_successful(asset_kind.clone());
+		Treasury::<T, _>::spend(
+			origin,
+			Box::new(asset_kind.clone()),
+			amount,
+			Box::new(beneficiary_lookup),
+			None,
+		)?;
+		assert!(Spends::<T, I>::get(0).is_some());
+		let origin =
+			T::RejectOrigin::try_successful_origin().map_err(|_| BenchmarkError::Weightless)?;
+
+		#[extrinsic_call]
+		_(origin as T::RuntimeOrigin, 0u32);
+
+		assert!(Spends::<T, I>::get(0).is_none());
+		Ok(())
 	}
 
 	impl_benchmark_test_suite!(Treasury, crate::tests::new_test_ext(), crate::tests::Test);
diff --git a/substrate/frame/treasury/src/lib.rs b/substrate/frame/treasury/src/lib.rs
index 730fae2a4e92c..b2b3a8801c156 100644
--- a/substrate/frame/treasury/src/lib.rs
+++ b/substrate/frame/treasury/src/lib.rs
@@ -15,46 +15,60 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+//! > Made with *Substrate*, for *Polkadot*.
+//!
+//! [![github]](https://github.com/paritytech/substrate/frame/fast-unstake) -
+//! [![polkadot]](https://polkadot.network)
+//!
+//! [polkadot]: https://img.shields.io/badge/polkadot-E6007A?style=for-the-badge&logo=polkadot&logoColor=white
+//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github
+//!
 //! # Treasury Pallet
 //!
 //! The Treasury pallet provides a "pot" of funds that can be managed by stakeholders in the system
 //! and a structure for making spending proposals from this pot.
 //!
-//! - [`Config`]
-//! - [`Call`]
-//!
 //! ## Overview
 //!
 //! The Treasury Pallet itself provides the pot to store funds, and a means for stakeholders to
-//! propose, approve, and deny expenditures. The chain will need to provide a method (e.g.
-//! inflation, fees) for collecting funds.
+//! propose and claim expenditures (aka spends). The chain will need to provide a method to approve
+//! spends (e.g. public referendum) and a method for collecting funds (e.g. inflation, fees).
 //!
-//! By way of example, the Council could vote to fund the Treasury with a portion of the block
+//! By way of example, stakeholders could vote to fund the Treasury with a portion of the block
 //! reward and use the funds to pay developers.
 //!
-//!
 //! ### Terminology
 //!
 //! - **Proposal:** A suggestion to allocate funds from the pot to a beneficiary.
 //! - **Beneficiary:** An account who will receive the funds from a proposal iff the proposal is
 //!   approved.
-//! - **Deposit:** Funds that a proposer must lock when making a proposal. The deposit will be
-//!   returned or slashed if the proposal is approved or rejected respectively.
 //! - **Pot:** Unspent funds accumulated by the treasury pallet.
+//! - **Spend** An approved proposal for transferring a specific amount of funds to a designated
+//!   beneficiary.
 //!
-//! ## Interface
+//! ### Example
 //!
-//! ### Dispatchable Functions
+//! 1. Multiple local spends approved by spend origins and received by a beneficiary.
+#![doc = docify::embed!("src/tests.rs", spend_local_origin_works)]
 //!
-//! General spending/proposal protocol:
-//! - `propose_spend` - Make a spending proposal and stake the required deposit.
-//! - `reject_proposal` - Reject a proposal, slashing the deposit.
-//! - `approve_proposal` - Accept the proposal, returning the deposit.
-//! - `remove_approval` - Remove an approval, the deposit will no longer be returned.
+//! 2. Approve a spend of some asset kind and claim it.
+#![doc = docify::embed!("src/tests.rs", spend_payout_works)]
 //!
-//! ## GenesisConfig
+//! ## Pallet API
 //!
-//! The Treasury pallet depends on the [`GenesisConfig`].
+//! See the [`pallet`] module for more information about the interfaces this pallet exposes,
+//! including its configuration trait, dispatchables, storage items, events and errors.
+//!
+//! ## Low Level / Implementation Details
+//!
+//! Spends can be initiated using either the `spend_local` or `spend` dispatchable. The
+//! `spend_local` dispatchable enables the creation of spends using the native currency of the
+//! chain, utilizing the funds stored in the pot. These spends are automatically paid out every
+//! [`pallet::Config::SpendPeriod`]. On the other hand, the `spend` dispatchable allows spending of
+//! any asset kind managed by the treasury, with payment facilitated by a designated
+//! [`pallet::Config::Paymaster`]. To claim these spends, the `payout` dispatchable should be called
+//! within some temporal bounds, starting from the moment they become valid and within one
+//! [`pallet::Config::PayoutPeriod`].
 
 #![cfg_attr(not(feature = "std"), no_std)]
 
@@ -62,6 +76,8 @@ mod benchmarking;
 #[cfg(test)]
 mod tests;
 pub mod weights;
+#[cfg(feature = "runtime-benchmarks")]
+pub use benchmarking::ArgumentsFactory;
 
 use codec::{Decode, Encode, MaxEncodedLen};
 use scale_info::TypeInfo;
@@ -75,7 +91,7 @@ use sp_std::{collections::btree_map::BTreeMap, prelude::*};
 use frame_support::{
 	print,
 	traits::{
-		Currency, ExistenceRequirement::KeepAlive, Get, Imbalance, OnUnbalanced,
+		tokens::Pay, Currency, ExistenceRequirement::KeepAlive, Get, Imbalance, OnUnbalanced,
 		ReservableCurrency, WithdrawReasons,
 	},
 	weights::Weight,
@@ -87,6 +103,7 @@ pub use weights::WeightInfo;
 
 pub type BalanceOf<T, I = ()> =
 	<<T as Config<I>>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
+pub type AssetBalanceOf<T, I> = <<T as Config<I>>::Paymaster as Pay>::Balance;
 pub type PositiveImbalanceOf<T, I = ()> = <<T as Config<I>>::Currency as Currency<
 	<T as frame_system::Config>::AccountId,
 >>::PositiveImbalance;
@@ -94,6 +111,7 @@ pub type NegativeImbalanceOf<T, I = ()> = <<T as Config<I>>::Currency as Currenc
 	<T as frame_system::Config>::AccountId,
 >>::NegativeImbalance;
 type AccountIdLookupOf<T> = <<T as frame_system::Config>::Lookup as StaticLookup>::Source;
+type BeneficiaryLookupOf<T, I> = <<T as Config<I>>::BeneficiaryLookup as StaticLookup>::Source;
 
 /// A trait to allow the Treasury Pallet to spend it's funds for other purposes.
 /// There is an expectation that the implementer of this trait will correctly manage
@@ -133,10 +151,47 @@ pub struct Proposal<AccountId, Balance> {
 	bond: Balance,
 }
 
+/// The state of the payment claim.
+#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, MaxEncodedLen, RuntimeDebug, TypeInfo)]
+pub enum PaymentState<Id> {
+	/// Pending claim.
+	Pending,
+	/// Payment attempted with a payment identifier.
+	Attempted { id: Id },
+	/// Payment failed.
+	Failed,
+}
+
+/// Info regarding an approved treasury spend.
+#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
+#[derive(Encode, Decode, Clone, PartialEq, Eq, MaxEncodedLen, RuntimeDebug, TypeInfo)]
+pub struct SpendStatus<AssetKind, AssetBalance, Beneficiary, BlockNumber, PaymentId> {
+	// The kind of asset to be spent.
+	asset_kind: AssetKind,
+	/// The asset amount of the spend.
+	amount: AssetBalance,
+	/// The beneficiary of the spend.
+	beneficiary: Beneficiary,
+	/// The block number from which the spend can be claimed.
+	valid_from: BlockNumber,
+	/// The block number by which the spend has to be claimed.
+	expire_at: BlockNumber,
+	/// The status of the payout/claim.
+	status: PaymentState<PaymentId>,
+}
+
+/// Index of an approved treasury spend.
+pub type SpendIndex = u32;
+
 #[frame_support::pallet]
 pub mod pallet {
 	use super::*;
-	use frame_support::{dispatch_context::with_context, pallet_prelude::*};
+	use frame_support::{
+		dispatch_context::with_context,
+		pallet_prelude::*,
+		traits::tokens::{ConversionFromAssetBalance, PaymentStatus},
+	};
 	use frame_system::pallet_prelude::*;
 
 	#[pallet::pallet]
@@ -201,9 +256,38 @@ pub mod pallet {
 		type MaxApprovals: Get<u32>;
 
 		/// The origin required for approving spends from the treasury outside of the proposal
-		/// process. The `Success` value is the maximum amount that this origin is allowed to
-		/// spend at a time.
+		/// process. The `Success` value is the maximum amount in a native asset that this origin
+		/// is allowed to spend at a time.
 		type SpendOrigin: EnsureOrigin<Self::RuntimeOrigin, Success = BalanceOf<Self, I>>;
+
+		/// Type parameter representing the asset kinds to be spent from the treasury.
+		type AssetKind: Parameter + MaxEncodedLen;
+
+		/// Type parameter used to identify the beneficiaries eligible to receive treasury spends.
+		type Beneficiary: Parameter + MaxEncodedLen;
+
+		/// Converting trait to take a source type and convert to [`Self::Beneficiary`].
+		type BeneficiaryLookup: StaticLookup<Target = Self::Beneficiary>;
+
+		/// Type for processing spends of [Self::AssetKind] in favor of [`Self::Beneficiary`].
+		type Paymaster: Pay<Beneficiary = Self::Beneficiary, AssetKind = Self::AssetKind>;
+
+		/// Type for converting the balance of an [Self::AssetKind] to the balance of the native
+		/// asset, solely for the purpose of asserting the result against the maximum allowed spend
+		/// amount of the [`Self::SpendOrigin`].
+		type BalanceConverter: ConversionFromAssetBalance<
+			<Self::Paymaster as Pay>::Balance,
+			Self::AssetKind,
+			BalanceOf<Self, I>,
+		>;
+
+		/// The period during which an approved treasury spend has to be claimed.
+		#[pallet::constant]
+		type PayoutPeriod: Get<BlockNumberFor<Self>>;
+
+		/// Helper type for benchmarks.
+		#[cfg(feature = "runtime-benchmarks")]
+		type BenchmarkHelper: ArgumentsFactory<Self::AssetKind, Self::Beneficiary>;
 	}
 
 	/// Number of proposals that have been made.
@@ -233,6 +317,27 @@ pub mod pallet {
 	pub type Approvals<T: Config<I>, I: 'static = ()> =
 		StorageValue<_, BoundedVec<ProposalIndex, T::MaxApprovals>, ValueQuery>;
 
+	/// The count of spends that have been made.
+	#[pallet::storage]
+	pub(crate) type SpendCount<T, I = ()> = StorageValue<_, SpendIndex, ValueQuery>;
+
+	/// Spends that have been approved and being processed.
+	// Hasher: Twox safe since `SpendIndex` is an internal count based index.
+	#[pallet::storage]
+	pub type Spends<T: Config<I>, I: 'static = ()> = StorageMap<
+		_,
+		Twox64Concat,
+		SpendIndex,
+		SpendStatus<
+			T::AssetKind,
+			AssetBalanceOf<T, I>,
+			T::Beneficiary,
+			BlockNumberFor<T>,
+			<T::Paymaster as Pay>::Id,
+		>,
+		OptionQuery,
+	>;
+
 	#[pallet::genesis_config]
 	#[derive(frame_support::DefaultNoBound)]
 	pub struct GenesisConfig<T: Config<I>, I: 'static = ()> {
@@ -277,6 +382,24 @@ pub mod pallet {
 		},
 		/// The inactive funds of the pallet have been updated.
 		UpdatedInactive { reactivated: BalanceOf<T, I>, deactivated: BalanceOf<T, I> },
+		/// A new asset spend proposal has been approved.
+		AssetSpendApproved {
+			index: SpendIndex,
+			asset_kind: T::AssetKind,
+			amount: AssetBalanceOf<T, I>,
+			beneficiary: T::Beneficiary,
+			valid_from: BlockNumberFor<T>,
+			expire_at: BlockNumberFor<T>,
+		},
+		/// An approved spend was voided.
+		AssetSpendVoided { index: SpendIndex },
+		/// A payment happened.
+		Paid { index: SpendIndex, payment_id: <T::Paymaster as Pay>::Id },
+		/// A payment failed and can be retried.
+		PaymentFailed { index: SpendIndex, payment_id: <T::Paymaster as Pay>::Id },
+		/// A spend was processed and removed from the storage. It might have been successfully
+		/// paid or it may have expired.
+		SpendProcessed { index: SpendIndex },
 	}
 
 	/// Error for the treasury pallet.
@@ -284,7 +407,7 @@ pub mod pallet {
 	pub enum Error<T, I = ()> {
 		/// Proposer's balance is too low.
 		InsufficientProposersBalance,
-		/// No proposal or bounty at that index.
+		/// No proposal, bounty or spend at that index.
 		InvalidIndex,
 		/// Too many approvals in the queue.
 		TooManyApprovals,
@@ -293,6 +416,20 @@ pub mod pallet {
 		InsufficientPermission,
 		/// Proposal has not been approved.
 		ProposalNotApproved,
+		/// The balance of the asset kind is not convertible to the balance of the native asset.
+		FailedToConvertBalance,
+		/// The spend has expired and cannot be claimed.
+		SpendExpired,
+		/// The spend is not yet eligible for payout.
+		EarlyPayout,
+		/// The payment has already been attempted.
+		AlreadyAttempted,
+		/// There was some issue with the mechanism of payment.
+		PayoutError,
+		/// The payout was not yet attempted/claimed.
+		NotAttempted,
+		/// The payment has neither failed nor succeeded yet.
+		Inconclusive,
 	}
 
 	#[pallet::hooks]
@@ -328,12 +465,22 @@ pub mod pallet {
 
 	#[pallet::call]
 	impl<T: Config<I>, I: 'static> Pallet<T, I> {
-		/// Put forward a suggestion for spending. A deposit proportional to the value
-		/// is reserved and slashed if the proposal is rejected. It is returned once the
-		/// proposal is awarded.
+		/// Put forward a suggestion for spending.
 		///
-		/// ## Complexity
+		/// ## Dispatch Origin
+		///
+		/// Must be signed.
+		///
+		/// ## Details
+		/// A deposit proportional to the value is reserved and slashed if the proposal is rejected.
+		/// It is returned once the proposal is awarded.
+		///
+		/// ### Complexity
 		/// - O(1)
+		///
+		/// ## Events
+		///
+		/// Emits [`Event::Proposed`] if successful.
 		#[pallet::call_index(0)]
 		#[pallet::weight(T::WeightInfo::propose_spend())]
 		#[allow(deprecated)]
@@ -360,12 +507,21 @@ pub mod pallet {
 			Ok(())
 		}
 
-		/// Reject a proposed spend. The original deposit will be slashed.
+		/// Reject a proposed spend.
 		///
-		/// May only be called from `T::RejectOrigin`.
+		/// ## Dispatch Origin
 		///
-		/// ## Complexity
+		/// Must be [`Config::RejectOrigin`].
+		///
+		/// ## Details
+		/// The original deposit will be slashed.
+		///
+		/// ### Complexity
 		/// - O(1)
+		///
+		/// ## Events
+		///
+		/// Emits [`Event::Rejected`] if successful.
 		#[pallet::call_index(1)]
 		#[pallet::weight((T::WeightInfo::reject_proposal(), DispatchClass::Operational))]
 		#[allow(deprecated)]
@@ -391,13 +547,23 @@ pub mod pallet {
 			Ok(())
 		}
 
-		/// Approve a proposal. At a later time, the proposal will be allocated to the beneficiary
-		/// and the original deposit will be returned.
+		/// Approve a proposal.
 		///
-		/// May only be called from `T::ApproveOrigin`.
+		/// ## Dispatch Origin
 		///
-		/// ## Complexity
+		/// Must be [`Config::ApproveOrigin`].
+		///
+		/// ## Details
+		///
+		/// At a later time, the proposal will be allocated to the beneficiary and the original
+		/// deposit will be returned.
+		///
+		/// ### Complexity
 		///  - O(1).
+		///
+		/// ## Events
+		///
+		/// No events are emitted from this dispatch.
 		#[pallet::call_index(2)]
 		#[pallet::weight((T::WeightInfo::approve_proposal(T::MaxApprovals::get()), DispatchClass::Operational))]
 		#[allow(deprecated)]
@@ -418,15 +584,24 @@ pub mod pallet {
 
 		/// Propose and approve a spend of treasury funds.
 		///
-		/// - `origin`: Must be `SpendOrigin` with the `Success` value being at least `amount`.
-		/// - `amount`: The amount to be transferred from the treasury to the `beneficiary`.
-		/// - `beneficiary`: The destination account for the transfer.
+		/// ## Dispatch Origin
 		///
+		/// Must be [`Config::SpendOrigin`] with the `Success` value being at least `amount`.
+		///
+		/// ### Details
 		/// NOTE: For record-keeping purposes, the proposer is deemed to be equivalent to the
 		/// beneficiary.
+		///
+		/// ### Parameters
+		/// - `amount`: The amount to be transferred from the treasury to the `beneficiary`.
+		/// - `beneficiary`: The destination account for the transfer.
+		///
+		/// ## Events
+		///
+		/// Emits [`Event::SpendApproved`] if successful.
 		#[pallet::call_index(3)]
-		#[pallet::weight(T::WeightInfo::spend())]
-		pub fn spend(
+		#[pallet::weight(T::WeightInfo::spend_local())]
+		pub fn spend_local(
 			origin: OriginFor<T>,
 			#[pallet::compact] amount: BalanceOf<T, I>,
 			beneficiary: AccountIdLookupOf<T>,
@@ -472,18 +647,26 @@ pub mod pallet {
 		}
 
 		/// Force a previously approved proposal to be removed from the approval queue.
+		///
+		/// ## Dispatch Origin
+		///
+		/// Must be [`Config::RejectOrigin`].
+		///
+		/// ## Details
+		///
 		/// The original deposit will no longer be returned.
 		///
-		/// May only be called from `T::RejectOrigin`.
+		/// ### Parameters
 		/// - `proposal_id`: The index of a proposal
 		///
-		/// ## Complexity
+		/// ### Complexity
 		/// - O(A) where `A` is the number of approvals
 		///
-		/// Errors:
-		/// - `ProposalNotApproved`: The `proposal_id` supplied was not found in the approval queue,
-		/// i.e., the proposal has not been approved. This could also mean the proposal does not
-		/// exist altogether, thus there is no way it would have been approved in the first place.
+		/// ### Errors
+		/// - [`Error::ProposalNotApproved`]: The `proposal_id` supplied was not found in the
+		///   approval queue, i.e., the proposal has not been approved. This could also mean the
+		///   proposal does not exist altogether, thus there is no way it would have been approved
+		///   in the first place.
 		#[pallet::call_index(4)]
 		#[pallet::weight((T::WeightInfo::remove_approval(), DispatchClass::Operational))]
 		pub fn remove_approval(
@@ -503,6 +686,229 @@ pub mod pallet {
 
 			Ok(())
 		}
+
+		/// Propose and approve a spend of treasury funds.
+		///
+		/// ## Dispatch Origin
+		///
+		/// Must be [`Config::SpendOrigin`] with the `Success` value being at least
+		/// `amount` of `asset_kind` in the native asset. The amount of `asset_kind` is converted
+		/// for assertion using the [`Config::BalanceConverter`].
+		///
+		/// ## Details
+		///
+		/// Create an approved spend for transferring a specific `amount` of `asset_kind` to a
+		/// designated beneficiary. The spend must be claimed using the `payout` dispatchable within
+		/// the [`Config::PayoutPeriod`].
+		///
+		/// ### Parameters
+		/// - `asset_kind`: An indicator of the specific asset class to be spent.
+		/// - `amount`: The amount to be transferred from the treasury to the `beneficiary`.
+		/// - `beneficiary`: The beneficiary of the spend.
+		/// - `valid_from`: The block number from which the spend can be claimed. It can refer to
+		///   the past if the resulting spend has not yet expired according to the
+		///   [`Config::PayoutPeriod`]. If `None`, the spend can be claimed immediately after
+		///   approval.
+		///
+		/// ## Events
+		///
+		/// Emits [`Event::AssetSpendApproved`] if successful.
+		#[pallet::call_index(5)]
+		#[pallet::weight(T::WeightInfo::spend())]
+		pub fn spend(
+			origin: OriginFor<T>,
+			asset_kind: Box<T::AssetKind>,
+			#[pallet::compact] amount: AssetBalanceOf<T, I>,
+			beneficiary: Box<BeneficiaryLookupOf<T, I>>,
+			valid_from: Option<BlockNumberFor<T>>,
+		) -> DispatchResult {
+			let max_amount = T::SpendOrigin::ensure_origin(origin)?;
+			let beneficiary = T::BeneficiaryLookup::lookup(*beneficiary)?;
+
+			let now = frame_system::Pallet::<T>::block_number();
+			let valid_from = valid_from.unwrap_or(now);
+			let expire_at = valid_from.saturating_add(T::PayoutPeriod::get());
+			ensure!(expire_at > now, Error::<T, I>::SpendExpired);
+
+			let native_amount =
+				T::BalanceConverter::from_asset_balance(amount, *asset_kind.clone())
+					.map_err(|_| Error::<T, I>::FailedToConvertBalance)?;
+
+			ensure!(native_amount <= max_amount, Error::<T, I>::InsufficientPermission);
+
+			with_context::<SpendContext<BalanceOf<T, I>>, _>(|v| {
+				let context = v.or_default();
+				// We group based on `max_amount`, to distinguish between different kind of
+				// origins. (assumes that all origins have different `max_amount`)
+				//
+				// Worst case is that we reject some "valid" request.
+				let spend = context.spend_in_context.entry(max_amount).or_default();
+
+				// Ensure that we don't overflow nor use more than `max_amount`
+				if spend.checked_add(&native_amount).map(|s| s > max_amount).unwrap_or(true) {
+					Err(Error::<T, I>::InsufficientPermission)
+				} else {
+					*spend = spend.saturating_add(native_amount);
+					Ok(())
+				}
+			})
+			.unwrap_or(Ok(()))?;
+
+			let index = SpendCount::<T, I>::get();
+			Spends::<T, I>::insert(
+				index,
+				SpendStatus {
+					asset_kind: *asset_kind.clone(),
+					amount,
+					beneficiary: beneficiary.clone(),
+					valid_from,
+					expire_at,
+					status: PaymentState::Pending,
+				},
+			);
+			SpendCount::<T, I>::put(index + 1);
+
+			Self::deposit_event(Event::AssetSpendApproved {
+				index,
+				asset_kind: *asset_kind,
+				amount,
+				beneficiary,
+				valid_from,
+				expire_at,
+			});
+			Ok(())
+		}
+
+		/// Claim a spend.
+		///
+		/// ## Dispatch Origin
+		///
+		/// Must be signed.
+		///
+		/// ## Details
+		///
+		/// Spends must be claimed within some temporal bounds. A spend may be claimed within one
+		/// [`Config::PayoutPeriod`] from the `valid_from` block.
+		/// In case of a payout failure, the spend status must be updated with the `check_status`
+		/// dispatchable before retrying with the current function.
+		///
+		/// ### Parameters
+		/// - `index`: The spend index.
+		///
+		/// ## Events
+		///
+		/// Emits [`Event::Paid`] if successful.
+		#[pallet::call_index(6)]
+		#[pallet::weight(T::WeightInfo::payout())]
+		pub fn payout(origin: OriginFor<T>, index: SpendIndex) -> DispatchResult {
+			ensure_signed(origin)?;
+			let mut spend = Spends::<T, I>::get(index).ok_or(Error::<T, I>::InvalidIndex)?;
+			let now = frame_system::Pallet::<T>::block_number();
+			ensure!(now >= spend.valid_from, Error::<T, I>::EarlyPayout);
+			ensure!(spend.expire_at > now, Error::<T, I>::SpendExpired);
+			ensure!(
+				matches!(spend.status, PaymentState::Pending | PaymentState::Failed),
+				Error::<T, I>::AlreadyAttempted
+			);
+
+			let id = T::Paymaster::pay(&spend.beneficiary, spend.asset_kind.clone(), spend.amount)
+				.map_err(|_| Error::<T, I>::PayoutError)?;
+
+			spend.status = PaymentState::Attempted { id };
+			Spends::<T, I>::insert(index, spend);
+
+			Self::deposit_event(Event::<T, I>::Paid { index, payment_id: id });
+
+			Ok(())
+		}
+
+		/// Check the status of the spend and remove it from the storage if processed.
+		///
+		/// ## Dispatch Origin
+		///
+		/// Must be signed.
+		///
+		/// ## Details
+		///
+		/// The status check is a prerequisite for retrying a failed payout.
+		/// If a spend has either succeeded or expired, it is removed from the storage by this
+		/// function. In such instances, transaction fees are refunded.
+		///
+		/// ### Parameters
+		/// - `index`: The spend index.
+		///
+		/// ## Events
+		///
+		/// Emits [`Event::PaymentFailed`] if the spend payout has failed.
+		/// Emits [`Event::SpendProcessed`] if the spend payout has succeed.
+		#[pallet::call_index(7)]
+		#[pallet::weight(T::WeightInfo::check_status())]
+		pub fn check_status(origin: OriginFor<T>, index: SpendIndex) -> DispatchResultWithPostInfo {
+			use PaymentState as State;
+			use PaymentStatus as Status;
+
+			ensure_signed(origin)?;
+			let mut spend = Spends::<T, I>::get(index).ok_or(Error::<T, I>::InvalidIndex)?;
+			let now = frame_system::Pallet::<T>::block_number();
+
+			if now > spend.expire_at && !matches!(spend.status, State::Attempted { .. }) {
+				// spend has expired and no further status update is expected.
+				Spends::<T, I>::remove(index);
+				Self::deposit_event(Event::<T, I>::SpendProcessed { index });
+				return Ok(Pays::No.into())
+			}
+
+			let payment_id = match spend.status {
+				State::Attempted { id } => id,
+				_ => return Err(Error::<T, I>::NotAttempted.into()),
+			};
+
+			match T::Paymaster::check_payment(payment_id) {
+				Status::Failure => {
+					spend.status = PaymentState::Failed;
+					Spends::<T, I>::insert(index, spend);
+					Self::deposit_event(Event::<T, I>::PaymentFailed { index, payment_id });
+				},
+				Status::Success | Status::Unknown => {
+					Spends::<T, I>::remove(index);
+					Self::deposit_event(Event::<T, I>::SpendProcessed { index });
+					return Ok(Pays::No.into())
+				},
+				Status::InProgress => return Err(Error::<T, I>::Inconclusive.into()),
+			}
+			return Ok(Pays::Yes.into())
+		}
+
+		/// Void previously approved spend.
+		///
+		/// ## Dispatch Origin
+		///
+		/// Must be [`Config::RejectOrigin`].
+		///
+		/// ## Details
+		///
+		/// A spend void is only possible if the payout has not been attempted yet.
+		///
+		/// ### Parameters
+		/// - `index`: The spend index.
+		///
+		/// ## Events
+		///
+		/// Emits [`Event::AssetSpendVoided`] if successful.
+		#[pallet::call_index(8)]
+		#[pallet::weight(T::WeightInfo::void_spend())]
+		pub fn void_spend(origin: OriginFor<T>, index: SpendIndex) -> DispatchResult {
+			T::RejectOrigin::ensure_origin(origin)?;
+			let spend = Spends::<T, I>::get(index).ok_or(Error::<T, I>::InvalidIndex)?;
+			ensure!(
+				matches!(spend.status, PaymentState::Pending | PaymentState::Failed),
+				Error::<T, I>::AlreadyAttempted
+			);
+
+			Spends::<T, I>::remove(index);
+			Self::deposit_event(Event::<T, I>::AssetSpendVoided { index });
+			Ok(())
+		}
 	}
 }
 
diff --git a/substrate/frame/treasury/src/tests.rs b/substrate/frame/treasury/src/tests.rs
index ba45d5f6ff16f..4bb00547d9f28 100644
--- a/substrate/frame/treasury/src/tests.rs
+++ b/substrate/frame/treasury/src/tests.rs
@@ -19,6 +19,7 @@
 
 #![cfg(test)]
 
+use core::{cell::RefCell, marker::PhantomData};
 use sp_core::H256;
 use sp_runtime::{
 	traits::{BadOrigin, BlakeTwo256, Dispatchable, IdentityLookup},
@@ -26,8 +27,13 @@ use sp_runtime::{
 };
 
 use frame_support::{
-	assert_err_ignore_postinfo, assert_noop, assert_ok, parameter_types,
-	traits::{ConstU32, ConstU64, OnInitialize},
+	assert_err_ignore_postinfo, assert_noop, assert_ok,
+	pallet_prelude::Pays,
+	parameter_types,
+	traits::{
+		tokens::{ConversionFromAssetBalance, PaymentStatus},
+		ConstU32, ConstU64, OnInitialize,
+	},
 	PalletId,
 };
 
@@ -96,10 +102,64 @@ impl pallet_utility::Config for Test {
 	type WeightInfo = ();
 }
 
+thread_local! {
+	pub static PAID: RefCell<BTreeMap<(u128, u32), u64>> = RefCell::new(BTreeMap::new());
+	pub static STATUS: RefCell<BTreeMap<u64, PaymentStatus>> = RefCell::new(BTreeMap::new());
+	pub static LAST_ID: RefCell<u64> = RefCell::new(0u64);
+}
+
+/// paid balance for a given account and asset ids
+fn paid(who: u128, asset_id: u32) -> u64 {
+	PAID.with(|p| p.borrow().get(&(who, asset_id)).cloned().unwrap_or(0))
+}
+
+/// reduce paid balance for a given account and asset ids
+fn unpay(who: u128, asset_id: u32, amount: u64) {
+	PAID.with(|p| p.borrow_mut().entry((who, asset_id)).or_default().saturating_reduce(amount))
+}
+
+/// set status for a given payment id
+fn set_status(id: u64, s: PaymentStatus) {
+	STATUS.with(|m| m.borrow_mut().insert(id, s));
+}
+
+pub struct TestPay;
+impl Pay for TestPay {
+	type Beneficiary = u128;
+	type Balance = u64;
+	type Id = u64;
+	type AssetKind = u32;
+	type Error = ();
+
+	fn pay(
+		who: &Self::Beneficiary,
+		asset_kind: Self::AssetKind,
+		amount: Self::Balance,
+	) -> Result<Self::Id, Self::Error> {
+		PAID.with(|paid| *paid.borrow_mut().entry((*who, asset_kind)).or_default() += amount);
+		Ok(LAST_ID.with(|lid| {
+			let x = *lid.borrow();
+			lid.replace(x + 1);
+			x
+		}))
+	}
+	fn check_payment(id: Self::Id) -> PaymentStatus {
+		STATUS.with(|s| s.borrow().get(&id).cloned().unwrap_or(PaymentStatus::Unknown))
+	}
+	#[cfg(feature = "runtime-benchmarks")]
+	fn ensure_successful(_: &Self::Beneficiary, _: Self::AssetKind, _: Self::Balance) {}
+	#[cfg(feature = "runtime-benchmarks")]
+	fn ensure_concluded(id: Self::Id) {
+		set_status(id, PaymentStatus::Failure)
+	}
+}
+
 parameter_types! {
 	pub const ProposalBond: Permill = Permill::from_percent(5);
 	pub const Burn: Permill = Permill::from_percent(50);
 	pub const TreasuryPalletId: PalletId = PalletId(*b"py/trsry");
+	pub TreasuryAccount: u128 = Treasury::account_id();
+	pub const SpendPayoutPeriod: u64 = 5;
 }
 pub struct TestSpendOrigin;
 impl frame_support::traits::EnsureOrigin<RuntimeOrigin> for TestSpendOrigin {
@@ -120,6 +180,16 @@ impl frame_support::traits::EnsureOrigin<RuntimeOrigin> for TestSpendOrigin {
 	}
 }
 
+pub struct MulBy<N>(PhantomData<N>);
+impl<N: Get<u64>> ConversionFromAssetBalance<u64, u32, u64> for MulBy<N> {
+	type Error = ();
+	fn from_asset_balance(balance: u64, _asset_id: u32) -> Result<u64, Self::Error> {
+		return balance.checked_mul(N::get()).ok_or(())
+	}
+	#[cfg(feature = "runtime-benchmarks")]
+	fn ensure_successful(_: u32) {}
+}
+
 impl Config for Test {
 	type PalletId = TreasuryPalletId;
 	type Currency = pallet_balances::Pallet<Test>;
@@ -137,6 +207,14 @@ impl Config for Test {
 	type SpendFunds = ();
 	type MaxApprovals = ConstU32<100>;
 	type SpendOrigin = TestSpendOrigin;
+	type AssetKind = u32;
+	type Beneficiary = u128;
+	type BeneficiaryLookup = IdentityLookup<Self::Beneficiary>;
+	type Paymaster = TestPay;
+	type BalanceConverter = MulBy<ConstU64<2>>;
+	type PayoutPeriod = SpendPayoutPeriod;
+	#[cfg(feature = "runtime-benchmarks")]
+	type BenchmarkHelper = ();
 }
 
 pub fn new_test_ext() -> sp_io::TestExternalities {
@@ -151,6 +229,14 @@ pub fn new_test_ext() -> sp_io::TestExternalities {
 	t.into()
 }
 
+fn get_payment_id(i: SpendIndex) -> Option<u64> {
+	let spend = Spends::<Test, _>::get(i).expect("no spend");
+	match spend.status {
+		PaymentState::Attempted { id } => Some(id),
+		_ => None,
+	}
+}
+
 #[test]
 fn genesis_config_works() {
 	new_test_ext().execute_with(|| {
@@ -160,46 +246,49 @@ fn genesis_config_works() {
 }
 
 #[test]
-fn spend_origin_permissioning_works() {
+fn spend_local_origin_permissioning_works() {
 	new_test_ext().execute_with(|| {
-		assert_noop!(Treasury::spend(RuntimeOrigin::signed(1), 1, 1), BadOrigin);
+		assert_noop!(Treasury::spend_local(RuntimeOrigin::signed(1), 1, 1), BadOrigin);
 		assert_noop!(
-			Treasury::spend(RuntimeOrigin::signed(10), 6, 1),
+			Treasury::spend_local(RuntimeOrigin::signed(10), 6, 1),
 			Error::<Test>::InsufficientPermission
 		);
 		assert_noop!(
-			Treasury::spend(RuntimeOrigin::signed(11), 11, 1),
+			Treasury::spend_local(RuntimeOrigin::signed(11), 11, 1),
 			Error::<Test>::InsufficientPermission
 		);
 		assert_noop!(
-			Treasury::spend(RuntimeOrigin::signed(12), 21, 1),
+			Treasury::spend_local(RuntimeOrigin::signed(12), 21, 1),
 			Error::<Test>::InsufficientPermission
 		);
 		assert_noop!(
-			Treasury::spend(RuntimeOrigin::signed(13), 51, 1),
+			Treasury::spend_local(RuntimeOrigin::signed(13), 51, 1),
 			Error::<Test>::InsufficientPermission
 		);
 	});
 }
 
+#[docify::export]
 #[test]
-fn spend_origin_works() {
+fn spend_local_origin_works() {
 	new_test_ext().execute_with(|| {
 		// Check that accumulate works when we have Some value in Dummy already.
 		Balances::make_free_balance_be(&Treasury::account_id(), 101);
-		assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), 5, 6));
-		assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), 5, 6));
-		assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), 5, 6));
-		assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), 5, 6));
-		assert_ok!(Treasury::spend(RuntimeOrigin::signed(11), 10, 6));
-		assert_ok!(Treasury::spend(RuntimeOrigin::signed(12), 20, 6));
-		assert_ok!(Treasury::spend(RuntimeOrigin::signed(13), 50, 6));
-
+		// approve spend of some amount to beneficiary `6`.
+		assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(10), 5, 6));
+		assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(10), 5, 6));
+		assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(10), 5, 6));
+		assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(10), 5, 6));
+		assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(11), 10, 6));
+		assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(12), 20, 6));
+		assert_ok!(Treasury::spend_local(RuntimeOrigin::signed(13), 50, 6));
+		// free balance of `6` is zero, spend period has not passed.
 		<Treasury as OnInitialize<u64>>::on_initialize(1);
 		assert_eq!(Balances::free_balance(6), 0);
-
+		// free balance of `6` is `100`, spend period has passed.
 		<Treasury as OnInitialize<u64>>::on_initialize(2);
 		assert_eq!(Balances::free_balance(6), 100);
+		// `100` spent, `1` burned.
 		assert_eq!(Treasury::pot(), 0);
 	});
 }
@@ -578,14 +667,49 @@ fn remove_already_removed_approval_fails() {
 	});
 }
 
+#[test]
+fn spending_local_in_batch_respects_max_total() {
+	new_test_ext().execute_with(|| {
+		// Respect the `max_total` for the given origin.
+		assert_ok!(RuntimeCall::from(UtilityCall::batch_all {
+			calls: vec![
+				RuntimeCall::from(TreasuryCall::spend_local { amount: 2, beneficiary: 100 }),
+				RuntimeCall::from(TreasuryCall::spend_local { amount: 2, beneficiary: 101 })
+			]
+		})
+		.dispatch(RuntimeOrigin::signed(10)));
+
+		assert_err_ignore_postinfo!(
+			RuntimeCall::from(UtilityCall::batch_all {
+				calls: vec![
+					RuntimeCall::from(TreasuryCall::spend_local { amount: 2, beneficiary: 100 }),
+					RuntimeCall::from(TreasuryCall::spend_local { amount: 4, beneficiary: 101 })
+				]
+			})
+			.dispatch(RuntimeOrigin::signed(10)),
+			Error::<Test, _>::InsufficientPermission
+		);
+	})
+}
+
 #[test]
 fn spending_in_batch_respects_max_total() {
 	new_test_ext().execute_with(|| {
 		// Respect the `max_total` for the given origin.
 		assert_ok!(RuntimeCall::from(UtilityCall::batch_all {
 			calls: vec![
-				RuntimeCall::from(TreasuryCall::spend { amount: 2, beneficiary: 100 }),
-				RuntimeCall::from(TreasuryCall::spend { amount: 2, beneficiary: 101 })
+				RuntimeCall::from(TreasuryCall::spend {
+					asset_kind: Box::new(1),
+					amount: 1,
+					beneficiary: Box::new(100),
+					valid_from: None,
+				}),
+				RuntimeCall::from(TreasuryCall::spend {
+					asset_kind: Box::new(1),
+					amount: 1,
+					beneficiary: Box::new(101),
+					valid_from: None,
+				})
 			]
 		})
 		.dispatch(RuntimeOrigin::signed(10)));
@@ -593,8 +717,18 @@ fn spending_in_batch_respects_max_total() {
 		assert_err_ignore_postinfo!(
 			RuntimeCall::from(UtilityCall::batch_all {
 				calls: vec![
-					RuntimeCall::from(TreasuryCall::spend { amount: 2, beneficiary: 100 }),
-					RuntimeCall::from(TreasuryCall::spend { amount: 4, beneficiary: 101 })
+					RuntimeCall::from(TreasuryCall::spend {
+						asset_kind: Box::new(1),
+						amount: 2,
+						beneficiary: Box::new(100),
+						valid_from: None,
+					}),
+					RuntimeCall::from(TreasuryCall::spend {
+						asset_kind: Box::new(1),
+						amount: 2,
+						beneficiary: Box::new(101),
+						valid_from: None,
+					})
 				]
 			})
 			.dispatch(RuntimeOrigin::signed(10)),
@@ -602,3 +736,251 @@ fn spending_in_batch_respects_max_total() {
 		);
 	})
 }
+
+#[test]
+fn spend_origin_works() {
+	new_test_ext().execute_with(|| {
+		assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 1, Box::new(6), None));
+		assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 2, Box::new(6), None));
+		assert_noop!(
+			Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 3, Box::new(6), None),
+			Error::<Test, _>::InsufficientPermission
+		);
+		assert_ok!(Treasury::spend(RuntimeOrigin::signed(11), Box::new(1), 5, Box::new(6), None));
+		assert_noop!(
+			Treasury::spend(RuntimeOrigin::signed(11), Box::new(1), 6, Box::new(6), None),
+			Error::<Test, _>::InsufficientPermission
+		);
+		assert_ok!(Treasury::spend(RuntimeOrigin::signed(12), Box::new(1), 10, Box::new(6), None));
+		assert_noop!(
+			Treasury::spend(RuntimeOrigin::signed(12), Box::new(1), 11, Box::new(6), None),
+			Error::<Test, _>::InsufficientPermission
+		);
+
+		assert_eq!(SpendCount::<Test, _>::get(), 4);
+		assert_eq!(Spends::<Test, _>::iter().count(), 4);
+	});
+}
+
+#[test]
+fn spend_works() {
+	new_test_ext().execute_with(|| {
+		System::set_block_number(1);
+		assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 2, Box::new(6), None));
+
+		assert_eq!(SpendCount::<Test, _>::get(), 1);
+		assert_eq!(
+			Spends::<Test, _>::get(0).unwrap(),
+			SpendStatus {
+				asset_kind: 1,
+				amount: 2,
+				beneficiary: 6,
+				valid_from: 1,
+				expire_at: 6,
+				status: PaymentState::Pending,
+			}
+		);
+		System::assert_last_event(
+			Event::<Test, _>::AssetSpendApproved {
+				index: 0,
+				asset_kind: 1,
+				amount: 2,
+				beneficiary: 6,
+				valid_from: 1,
+				expire_at: 6,
+			}
+			.into(),
+		);
+	});
+}
+
+#[test]
+fn spend_expires() {
+	new_test_ext().execute_with(|| {
+		assert_eq!(<Test as Config>::PayoutPeriod::get(), 5);
+
+		// spend `0` expires in 5 blocks after the creating.
+		System::set_block_number(1);
+		assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 2, Box::new(6), None));
+		System::set_block_number(6);
+		assert_noop!(Treasury::payout(RuntimeOrigin::signed(1), 0), Error::<Test, _>::SpendExpired);
+
+		// spend cannot be approved since its already expired.
+		assert_noop!(
+			Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 2, Box::new(6), Some(0)),
+			Error::<Test, _>::SpendExpired
+		);
+	});
+}
+
+#[docify::export]
+#[test]
+fn spend_payout_works() {
+	new_test_ext().execute_with(|| {
+		System::set_block_number(1);
+		// approve a `2` coins spend of asset `1` to beneficiary `6`, the spend valid from now.
+		assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 2, Box::new(6), None));
+		// payout the spend.
+		assert_ok!(Treasury::payout(RuntimeOrigin::signed(1), 0));
+		// beneficiary received `2` coins of asset `1`.
+		assert_eq!(paid(6, 1), 2);
+		assert_eq!(SpendCount::<Test, _>::get(), 1);
+		let payment_id = get_payment_id(0).expect("no payment attempt");
+		System::assert_last_event(Event::<Test, _>::Paid { index: 0, payment_id }.into());
+		set_status(payment_id, PaymentStatus::Success);
+		// the payment succeed.
+		assert_ok!(Treasury::check_status(RuntimeOrigin::signed(1), 0));
+		System::assert_last_event(Event::<Test, _>::SpendProcessed { index: 0 }.into());
+		// cannot payout the same spend twice.
+		assert_noop!(Treasury::payout(RuntimeOrigin::signed(1), 0), Error::<Test, _>::InvalidIndex);
+	});
+}
+
+#[test]
+fn payout_retry_works() {
+	new_test_ext().execute_with(|| {
+		System::set_block_number(1);
+		assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 2, Box::new(6), None));
+		assert_ok!(Treasury::payout(RuntimeOrigin::signed(1), 0));
+		assert_eq!(paid(6, 1), 2);
+		let payment_id = get_payment_id(0).expect("no payment attempt");
+		// spend payment is failed
+		set_status(payment_id, PaymentStatus::Failure);
+		unpay(6, 1, 2);
+		// cannot payout a spend in the attempted state
+		assert_noop!(
+			Treasury::payout(RuntimeOrigin::signed(1), 0),
+			Error::<Test, _>::AlreadyAttempted
+		);
+		// check status and update it to retry the payout again
+		assert_ok!(Treasury::check_status(RuntimeOrigin::signed(1), 0));
+		System::assert_last_event(Event::<Test, _>::PaymentFailed { index: 0, payment_id }.into());
+		// the payout can be retried now
+		assert_ok!(Treasury::payout(RuntimeOrigin::signed(1), 0));
+		assert_eq!(paid(6, 1), 2);
+	});
+}
+
+#[test]
+fn spend_valid_from_works() {
+	new_test_ext().execute_with(|| {
+		assert_eq!(<Test as Config>::PayoutPeriod::get(), 5);
+		System::set_block_number(1);
+
+		// spend valid from block `2`.
+		assert_ok!(Treasury::spend(
+			RuntimeOrigin::signed(10),
+			Box::new(1),
+			2,
+			Box::new(6),
+			Some(2)
+		));
+		assert_noop!(Treasury::payout(RuntimeOrigin::signed(1), 0), Error::<Test, _>::EarlyPayout);
+		System::set_block_number(2);
+		assert_ok!(Treasury::payout(RuntimeOrigin::signed(1), 0));
+
+		System::set_block_number(5);
+		// spend approved even if `valid_from` in the past since the payout period has not passed.
+		assert_ok!(Treasury::spend(
+			RuntimeOrigin::signed(10),
+			Box::new(1),
+			2,
+			Box::new(6),
+			Some(4)
+		));
+		// spend paid.
+		assert_ok!(Treasury::payout(RuntimeOrigin::signed(1), 1));
+	});
+}
+
+#[test]
+fn void_spend_works() {
+	new_test_ext().execute_with(|| {
+		System::set_block_number(1);
+		// spend cannot be voided if already attempted.
+		assert_ok!(Treasury::spend(
+			RuntimeOrigin::signed(10),
+			Box::new(1),
+			2,
+			Box::new(6),
+			Some(1)
+		));
+		assert_ok!(Treasury::payout(RuntimeOrigin::signed(1), 0));
+		assert_noop!(
+			Treasury::void_spend(RuntimeOrigin::root(), 0),
+			Error::<Test, _>::AlreadyAttempted
+		);
+
+		// void spend.
+		assert_ok!(Treasury::spend(
+			RuntimeOrigin::signed(10),
+			Box::new(1),
+			2,
+			Box::new(6),
+			Some(10)
+		));
+		assert_ok!(Treasury::void_spend(RuntimeOrigin::root(), 1));
+		assert_eq!(Spends::<Test, _>::get(1), None);
+	});
+}
+
+#[test]
+fn check_status_works() {
+	new_test_ext().execute_with(|| {
+		assert_eq!(<Test as Config>::PayoutPeriod::get(), 5);
+		System::set_block_number(1);
+
+		// spend `0` expired and can be removed.
+		assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 2, Box::new(6), None));
+		System::set_block_number(7);
+		let info = Treasury::check_status(RuntimeOrigin::signed(1), 0).unwrap();
+		assert_eq!(info.pays_fee, Pays::No);
+		System::assert_last_event(Event::<Test, _>::SpendProcessed { index: 0 }.into());
+
+		// spend `1` payment failed and expired hence can be removed.
+		assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 2, Box::new(6), None));
+		assert_noop!(
+			Treasury::check_status(RuntimeOrigin::signed(1), 1),
+			Error::<Test, _>::NotAttempted
+		);
+		assert_ok!(Treasury::payout(RuntimeOrigin::signed(1), 1));
+		let payment_id = get_payment_id(1).expect("no payment attempt");
+		set_status(payment_id, PaymentStatus::Failure);
+		// spend expired.
+		System::set_block_number(13);
+		let info = Treasury::check_status(RuntimeOrigin::signed(1), 1).unwrap();
+		assert_eq!(info.pays_fee, Pays::Yes);
+		System::assert_last_event(Event::<Test, _>::PaymentFailed { index: 1, payment_id }.into());
+		let info = Treasury::check_status(RuntimeOrigin::signed(1), 1).unwrap();
+		assert_eq!(info.pays_fee, Pays::No);
+		System::assert_last_event(Event::<Test, _>::SpendProcessed { index: 1 }.into());
+
+		// spend `2` payment succeed.
+		assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 2, Box::new(6), None));
+		assert_ok!(Treasury::payout(RuntimeOrigin::signed(1), 2));
+		let payment_id = get_payment_id(2).expect("no payment attempt");
+		set_status(payment_id, PaymentStatus::Success);
+		let info = Treasury::check_status(RuntimeOrigin::signed(1), 2).unwrap();
+		assert_eq!(info.pays_fee, Pays::No);
+		System::assert_last_event(Event::<Test, _>::SpendProcessed { index: 2 }.into());
+
+		// spend `3` payment in process.
+		assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 2, Box::new(6), None));
+		assert_ok!(Treasury::payout(RuntimeOrigin::signed(1), 3));
+		let payment_id = get_payment_id(3).expect("no payment attempt");
+		set_status(payment_id, PaymentStatus::InProgress);
+		assert_noop!(
+			Treasury::check_status(RuntimeOrigin::signed(1), 3),
+			Error::<Test, _>::Inconclusive
+		);
+
+		// spend `4` removed since the payment status is unknown.
+		assert_ok!(Treasury::spend(RuntimeOrigin::signed(10), Box::new(1), 2, Box::new(6), None));
+		assert_ok!(Treasury::payout(RuntimeOrigin::signed(1), 4));
+		let payment_id = get_payment_id(4).expect("no payment attempt");
+		set_status(payment_id, PaymentStatus::Unknown);
+		let info = Treasury::check_status(RuntimeOrigin::signed(1), 4).unwrap();
+		assert_eq!(info.pays_fee, Pays::No);
+		System::assert_last_event(Event::<Test, _>::SpendProcessed { index: 4 }.into());
+	});
+}
diff --git a/substrate/frame/treasury/src/weights.rs b/substrate/frame/treasury/src/weights.rs
index 8f1418f76d969..030e18980eb54 100644
--- a/substrate/frame/treasury/src/weights.rs
+++ b/substrate/frame/treasury/src/weights.rs
@@ -18,28 +18,23 @@
 //! Autogenerated weights for pallet_treasury
 //!
 //! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
-//! DATE: 2023-06-16, STEPS: `50`, REPEAT: `20`, LOW RANGE: `[]`, HIGH RANGE: `[]`
+//! DATE: 2023-07-07, STEPS: `20`, REPEAT: `2`, LOW RANGE: `[]`, HIGH RANGE: `[]`
 //! WORST CASE MAP SIZE: `1000000`
-//! HOSTNAME: `runner-e8ezs4ez-project-145-concurrent-0`, CPU: `Intel(R) Xeon(R) CPU @ 2.60GHz`
-//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
+//! HOSTNAME: `cob`, CPU: `<UNKNOWN>`
+//! EXECUTION: None, WASM-EXECUTION: Compiled, CHAIN: Some("dev"), DB CACHE: 1024
 
 // Executed Command:
-// ./target/production/substrate
+// ./target/debug/substrate
 // benchmark
 // pallet
 // --chain=dev
-// --steps=50
-// --repeat=20
-// --pallet=pallet_treasury
-// --no-storage-info
-// --no-median-slopes
-// --no-min-squares
+// --steps=20
+// --repeat=2
+// --pallet=pallet-treasury
 // --extrinsic=*
-// --execution=wasm
 // --wasm-execution=compiled
 // --heap-pages=4096
-// --output=./frame/treasury/src/weights.rs
-// --header=./HEADER-APACHE2
+// --output=./frame/treasury/src/._weights.rs
 // --template=./.maintain/frame-weight-template.hbs
 
 #![cfg_attr(rustfmt, rustfmt_skip)]
@@ -52,12 +47,16 @@ use core::marker::PhantomData;
 
 /// Weight functions needed for pallet_treasury.
 pub trait WeightInfo {
-	fn spend() -> Weight;
+	fn spend_local() -> Weight;
 	fn propose_spend() -> Weight;
 	fn reject_proposal() -> Weight;
 	fn approve_proposal(p: u32, ) -> Weight;
 	fn remove_approval() -> Weight;
 	fn on_initialize_proposals(p: u32, ) -> Weight;
+	fn spend() -> Weight;
+	fn payout() -> Weight;
+	fn check_status() -> Weight;
+	fn void_spend() -> Weight;
 }
 
 /// Weights for pallet_treasury using the Substrate node and recommended hardware.
@@ -69,12 +68,12 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: Treasury Approvals (max_values: Some(1), max_size: Some(402), added: 897, mode: MaxEncodedLen)
 	/// Storage: Treasury Proposals (r:0 w:1)
 	/// Proof: Treasury Proposals (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen)
-	fn spend() -> Weight {
+	fn spend_local() -> Weight {
 		// Proof Size summary in bytes:
 		//  Measured:  `76`
 		//  Estimated: `1887`
-		// Minimum execution time: 15_057_000 picoseconds.
-		Weight::from_parts(15_803_000, 1887)
+		// Minimum execution time: 179_000_000 picoseconds.
+		Weight::from_parts(190_000_000, 1887)
 			.saturating_add(T::DbWeight::get().reads(2_u64))
 			.saturating_add(T::DbWeight::get().writes(3_u64))
 	}
@@ -86,8 +85,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `177`
 		//  Estimated: `1489`
-		// Minimum execution time: 28_923_000 picoseconds.
-		Weight::from_parts(29_495_000, 1489)
+		// Minimum execution time: 349_000_000 picoseconds.
+		Weight::from_parts(398_000_000, 1489)
 			.saturating_add(T::DbWeight::get().reads(1_u64))
 			.saturating_add(T::DbWeight::get().writes(2_u64))
 	}
@@ -99,8 +98,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `335`
 		//  Estimated: `3593`
-		// Minimum execution time: 30_539_000 picoseconds.
-		Weight::from_parts(30_986_000, 3593)
+		// Minimum execution time: 367_000_000 picoseconds.
+		Weight::from_parts(388_000_000, 3593)
 			.saturating_add(T::DbWeight::get().reads(2_u64))
 			.saturating_add(T::DbWeight::get().writes(2_u64))
 	}
@@ -111,12 +110,12 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// The range of component `p` is `[0, 99]`.
 	fn approve_proposal(p: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `504 + p * (8 ±0)`
+		//  Measured:  `483 + p * (9 ±0)`
 		//  Estimated: `3573`
-		// Minimum execution time: 9_320_000 picoseconds.
-		Weight::from_parts(12_606_599, 3573)
-			// Standard Error: 1_302
-			.saturating_add(Weight::from_parts(71_054, 0).saturating_mul(p.into()))
+		// Minimum execution time: 111_000_000 picoseconds.
+		Weight::from_parts(108_813_243, 3573)
+			// Standard Error: 147_887
+			.saturating_add(Weight::from_parts(683_216, 0).saturating_mul(p.into()))
 			.saturating_add(T::DbWeight::get().reads(2_u64))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
@@ -126,8 +125,8 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 		// Proof Size summary in bytes:
 		//  Measured:  `161`
 		//  Estimated: `1887`
-		// Minimum execution time: 7_231_000 picoseconds.
-		Weight::from_parts(7_459_000, 1887)
+		// Minimum execution time: 71_000_000 picoseconds.
+		Weight::from_parts(78_000_000, 1887)
 			.saturating_add(T::DbWeight::get().reads(1_u64))
 			.saturating_add(T::DbWeight::get().writes(1_u64))
 	}
@@ -135,27 +134,81 @@ impl<T: frame_system::Config> WeightInfo for SubstrateWeight<T> {
 	/// Proof: Treasury Deactivated (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen)
 	/// Storage: Treasury Approvals (r:1 w:1)
 	/// Proof: Treasury Approvals (max_values: Some(1), max_size: Some(402), added: 897, mode: MaxEncodedLen)
-	/// Storage: Treasury Proposals (r:100 w:100)
+	/// Storage: Treasury Proposals (r:99 w:99)
 	/// Proof: Treasury Proposals (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen)
-	/// Storage: System Account (r:200 w:200)
+	/// Storage: System Account (r:198 w:198)
 	/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
 	/// Storage: Bounties BountyApprovals (r:1 w:1)
 	/// Proof: Bounties BountyApprovals (max_values: Some(1), max_size: Some(402), added: 897, mode: MaxEncodedLen)
-	/// The range of component `p` is `[0, 100]`.
+	/// The range of component `p` is `[0, 99]`.
 	fn on_initialize_proposals(p: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `421 + p * (251 ±0)`
+		//  Measured:  `427 + p * (251 ±0)`
 		//  Estimated: `1887 + p * (5206 ±0)`
-		// Minimum execution time: 44_769_000 picoseconds.
-		Weight::from_parts(57_915_572, 1887)
-			// Standard Error: 59_484
-			.saturating_add(Weight::from_parts(42_343_732, 0).saturating_mul(p.into()))
+		// Minimum execution time: 614_000_000 picoseconds.
+		Weight::from_parts(498_501_558, 1887)
+			// Standard Error: 1_070_260
+			.saturating_add(Weight::from_parts(599_011_690, 0).saturating_mul(p.into()))
 			.saturating_add(T::DbWeight::get().reads(3_u64))
 			.saturating_add(T::DbWeight::get().reads((3_u64).saturating_mul(p.into())))
 			.saturating_add(T::DbWeight::get().writes(3_u64))
 			.saturating_add(T::DbWeight::get().writes((3_u64).saturating_mul(p.into())))
 			.saturating_add(Weight::from_parts(0, 5206).saturating_mul(p.into()))
 	}
+	/// Storage: AssetRate ConversionRateToNative (r:1 w:0)
+	/// Proof: AssetRate ConversionRateToNative (max_values: None, max_size: Some(36), added: 2511, mode: MaxEncodedLen)
+	/// Storage: Treasury SpendCount (r:1 w:1)
+	/// Proof: Treasury SpendCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
+	/// Storage: Treasury Spends (r:0 w:1)
+	/// Proof: Treasury Spends (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen)
+	fn spend() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `140`
+		//  Estimated: `3501`
+		// Minimum execution time: 214_000_000 picoseconds.
+		Weight::from_parts(216_000_000, 3501)
+			.saturating_add(T::DbWeight::get().reads(2_u64))
+			.saturating_add(T::DbWeight::get().writes(2_u64))
+	}
+	/// Storage: Treasury Spends (r:1 w:1)
+	/// Proof: Treasury Spends (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen)
+	/// Storage: Assets Asset (r:1 w:1)
+	/// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen)
+	/// Storage: Assets Account (r:2 w:2)
+	/// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen)
+	/// Storage: System Account (r:1 w:1)
+	/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
+	fn payout() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `705`
+		//  Estimated: `6208`
+		// Minimum execution time: 760_000_000 picoseconds.
+		Weight::from_parts(822_000_000, 6208)
+			.saturating_add(T::DbWeight::get().reads(5_u64))
+			.saturating_add(T::DbWeight::get().writes(5_u64))
+	}
+	/// Storage: Treasury Spends (r:1 w:1)
+	/// Proof: Treasury Spends (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen)
+	fn check_status() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `194`
+		//  Estimated: `3534`
+		// Minimum execution time: 153_000_000 picoseconds.
+		Weight::from_parts(160_000_000, 3534)
+			.saturating_add(T::DbWeight::get().reads(1_u64))
+			.saturating_add(T::DbWeight::get().writes(1_u64))
+	}
+	/// Storage: Treasury Spends (r:1 w:1)
+	/// Proof: Treasury Spends (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen)
+	fn void_spend() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `194`
+		//  Estimated: `3534`
+		// Minimum execution time: 147_000_000 picoseconds.
+		Weight::from_parts(181_000_000, 3534)
+			.saturating_add(T::DbWeight::get().reads(1_u64))
+			.saturating_add(T::DbWeight::get().writes(1_u64))
+	}
 }
 
 // For backwards compatibility and tests
@@ -166,12 +219,12 @@ impl WeightInfo for () {
 	/// Proof: Treasury Approvals (max_values: Some(1), max_size: Some(402), added: 897, mode: MaxEncodedLen)
 	/// Storage: Treasury Proposals (r:0 w:1)
 	/// Proof: Treasury Proposals (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen)
-	fn spend() -> Weight {
+	fn spend_local() -> Weight {
 		// Proof Size summary in bytes:
 		//  Measured:  `76`
 		//  Estimated: `1887`
-		// Minimum execution time: 15_057_000 picoseconds.
-		Weight::from_parts(15_803_000, 1887)
+		// Minimum execution time: 179_000_000 picoseconds.
+		Weight::from_parts(190_000_000, 1887)
 			.saturating_add(RocksDbWeight::get().reads(2_u64))
 			.saturating_add(RocksDbWeight::get().writes(3_u64))
 	}
@@ -183,8 +236,8 @@ impl WeightInfo for () {
 		// Proof Size summary in bytes:
 		//  Measured:  `177`
 		//  Estimated: `1489`
-		// Minimum execution time: 28_923_000 picoseconds.
-		Weight::from_parts(29_495_000, 1489)
+		// Minimum execution time: 349_000_000 picoseconds.
+		Weight::from_parts(398_000_000, 1489)
 			.saturating_add(RocksDbWeight::get().reads(1_u64))
 			.saturating_add(RocksDbWeight::get().writes(2_u64))
 	}
@@ -196,8 +249,8 @@ impl WeightInfo for () {
 		// Proof Size summary in bytes:
 		//  Measured:  `335`
 		//  Estimated: `3593`
-		// Minimum execution time: 30_539_000 picoseconds.
-		Weight::from_parts(30_986_000, 3593)
+		// Minimum execution time: 367_000_000 picoseconds.
+		Weight::from_parts(388_000_000, 3593)
 			.saturating_add(RocksDbWeight::get().reads(2_u64))
 			.saturating_add(RocksDbWeight::get().writes(2_u64))
 	}
@@ -208,12 +261,12 @@ impl WeightInfo for () {
 	/// The range of component `p` is `[0, 99]`.
 	fn approve_proposal(p: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `504 + p * (8 ±0)`
+		//  Measured:  `483 + p * (9 ±0)`
 		//  Estimated: `3573`
-		// Minimum execution time: 9_320_000 picoseconds.
-		Weight::from_parts(12_606_599, 3573)
-			// Standard Error: 1_302
-			.saturating_add(Weight::from_parts(71_054, 0).saturating_mul(p.into()))
+		// Minimum execution time: 111_000_000 picoseconds.
+		Weight::from_parts(108_813_243, 3573)
+			// Standard Error: 147_887
+			.saturating_add(Weight::from_parts(683_216, 0).saturating_mul(p.into()))
 			.saturating_add(RocksDbWeight::get().reads(2_u64))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
@@ -223,8 +276,8 @@ impl WeightInfo for () {
 		// Proof Size summary in bytes:
 		//  Measured:  `161`
 		//  Estimated: `1887`
-		// Minimum execution time: 7_231_000 picoseconds.
-		Weight::from_parts(7_459_000, 1887)
+		// Minimum execution time: 71_000_000 picoseconds.
+		Weight::from_parts(78_000_000, 1887)
 			.saturating_add(RocksDbWeight::get().reads(1_u64))
 			.saturating_add(RocksDbWeight::get().writes(1_u64))
 	}
@@ -232,25 +285,79 @@ impl WeightInfo for () {
 	/// Proof: Treasury Deactivated (max_values: Some(1), max_size: Some(16), added: 511, mode: MaxEncodedLen)
 	/// Storage: Treasury Approvals (r:1 w:1)
 	/// Proof: Treasury Approvals (max_values: Some(1), max_size: Some(402), added: 897, mode: MaxEncodedLen)
-	/// Storage: Treasury Proposals (r:100 w:100)
+	/// Storage: Treasury Proposals (r:99 w:99)
 	/// Proof: Treasury Proposals (max_values: None, max_size: Some(108), added: 2583, mode: MaxEncodedLen)
-	/// Storage: System Account (r:200 w:200)
+	/// Storage: System Account (r:198 w:198)
 	/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
 	/// Storage: Bounties BountyApprovals (r:1 w:1)
 	/// Proof: Bounties BountyApprovals (max_values: Some(1), max_size: Some(402), added: 897, mode: MaxEncodedLen)
-	/// The range of component `p` is `[0, 100]`.
+	/// The range of component `p` is `[0, 99]`.
 	fn on_initialize_proposals(p: u32, ) -> Weight {
 		// Proof Size summary in bytes:
-		//  Measured:  `421 + p * (251 ±0)`
+		//  Measured:  `427 + p * (251 ±0)`
 		//  Estimated: `1887 + p * (5206 ±0)`
-		// Minimum execution time: 44_769_000 picoseconds.
-		Weight::from_parts(57_915_572, 1887)
-			// Standard Error: 59_484
-			.saturating_add(Weight::from_parts(42_343_732, 0).saturating_mul(p.into()))
+		// Minimum execution time: 614_000_000 picoseconds.
+		Weight::from_parts(498_501_558, 1887)
+			// Standard Error: 1_070_260
+			.saturating_add(Weight::from_parts(599_011_690, 0).saturating_mul(p.into()))
 			.saturating_add(RocksDbWeight::get().reads(3_u64))
 			.saturating_add(RocksDbWeight::get().reads((3_u64).saturating_mul(p.into())))
 			.saturating_add(RocksDbWeight::get().writes(3_u64))
 			.saturating_add(RocksDbWeight::get().writes((3_u64).saturating_mul(p.into())))
 			.saturating_add(Weight::from_parts(0, 5206).saturating_mul(p.into()))
 	}
+	/// Storage: AssetRate ConversionRateToNative (r:1 w:0)
+	/// Proof: AssetRate ConversionRateToNative (max_values: None, max_size: Some(36), added: 2511, mode: MaxEncodedLen)
+	/// Storage: Treasury SpendCount (r:1 w:1)
+	/// Proof: Treasury SpendCount (max_values: Some(1), max_size: Some(4), added: 499, mode: MaxEncodedLen)
+	/// Storage: Treasury Spends (r:0 w:1)
+	/// Proof: Treasury Spends (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen)
+	fn spend() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `140`
+		//  Estimated: `3501`
+		// Minimum execution time: 214_000_000 picoseconds.
+		Weight::from_parts(216_000_000, 3501)
+			.saturating_add(RocksDbWeight::get().reads(2_u64))
+			.saturating_add(RocksDbWeight::get().writes(2_u64))
+	}
+	/// Storage: Treasury Spends (r:1 w:1)
+	/// Proof: Treasury Spends (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen)
+	/// Storage: Assets Asset (r:1 w:1)
+	/// Proof: Assets Asset (max_values: None, max_size: Some(210), added: 2685, mode: MaxEncodedLen)
+	/// Storage: Assets Account (r:2 w:2)
+	/// Proof: Assets Account (max_values: None, max_size: Some(134), added: 2609, mode: MaxEncodedLen)
+	/// Storage: System Account (r:1 w:1)
+	/// Proof: System Account (max_values: None, max_size: Some(128), added: 2603, mode: MaxEncodedLen)
+	fn payout() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `705`
+		//  Estimated: `6208`
+		// Minimum execution time: 760_000_000 picoseconds.
+		Weight::from_parts(822_000_000, 6208)
+			.saturating_add(RocksDbWeight::get().reads(5_u64))
+			.saturating_add(RocksDbWeight::get().writes(5_u64))
+	}
+	/// Storage: Treasury Spends (r:1 w:1)
+	/// Proof: Treasury Spends (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen)
+	fn check_status() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `194`
+		//  Estimated: `3534`
+		// Minimum execution time: 153_000_000 picoseconds.
+		Weight::from_parts(160_000_000, 3534)
+			.saturating_add(RocksDbWeight::get().reads(1_u64))
+			.saturating_add(RocksDbWeight::get().writes(1_u64))
+	}
+	/// Storage: Treasury Spends (r:1 w:1)
+	/// Proof: Treasury Spends (max_values: None, max_size: Some(69), added: 2544, mode: MaxEncodedLen)
+	fn void_spend() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `194`
+		//  Estimated: `3534`
+		// Minimum execution time: 147_000_000 picoseconds.
+		Weight::from_parts(181_000_000, 3534)
+			.saturating_add(RocksDbWeight::get().reads(1_u64))
+			.saturating_add(RocksDbWeight::get().writes(1_u64))
+	}
 }
diff --git a/substrate/primitives/core/src/crypto.rs b/substrate/primitives/core/src/crypto.rs
index 8c7d98f00cd89..be9f56eb2ba4d 100644
--- a/substrate/primitives/core/src/crypto.rs
+++ b/substrate/primitives/core/src/crypto.rs
@@ -630,6 +630,13 @@ impl sp_std::str::FromStr for AccountId32 {
 	}
 }
 
+/// Creates an [`AccountId32`] from the input, which should contain at least 32 bytes.
+impl FromEntropy for AccountId32 {
+	fn from_entropy(input: &mut impl codec::Input) -> Result<Self, codec::Error> {
+		Ok(AccountId32::new(FromEntropy::from_entropy(input)?))
+	}
+}
+
 #[cfg(feature = "std")]
 pub use self::dummy::*;
 
@@ -1171,6 +1178,13 @@ impl FromEntropy for bool {
 	}
 }
 
+/// Create the unit type for any given input.
+impl FromEntropy for () {
+	fn from_entropy(_: &mut impl codec::Input) -> Result<Self, codec::Error> {
+		Ok(())
+	}
+}
+
 macro_rules! impl_from_entropy {
 	($type:ty , $( $others:tt )*) => {
 		impl_from_entropy!($type);