diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs
index 767f8bda68d79ccbfa2f578f52be8248cb27b056..a361a2a6ec5cc24d960e02ebac65dcda41bd7e44 100644
--- a/polkadot/runtime/test-runtime/src/lib.rs
+++ b/polkadot/runtime/test-runtime/src/lib.rs
@@ -546,6 +546,7 @@ pub mod pallet_test_notifier {
 	use pallet_xcm::ensure_response;
 	use sp_runtime::DispatchResult;
 	use xcm::latest::prelude::*;
+	use xcm_executor::traits::QueryHandler as XcmQueryHandler;
 
 	#[pallet::pallet]
 	pub struct Pallet<T>(_);
@@ -581,7 +582,7 @@ pub mod pallet_test_notifier {
 			let id = who
 				.using_encoded(|mut d| <[u8; 32]>::decode(&mut d))
 				.map_err(|_| Error::<T>::BadAccountFormat)?;
-			let qid = pallet_xcm::Pallet::<T>::new_query(
+			let qid = <pallet_xcm::Pallet<T> as XcmQueryHandler>::new_query(
 				Junction::AccountId32 { network: None, id },
 				100u32.into(),
 				Here,
diff --git a/polkadot/xcm/pallet-xcm/src/lib.rs b/polkadot/xcm/pallet-xcm/src/lib.rs
index 29c10de003aebdf1f29803f7fffa4a12b1885008..61243c7d682ba356d35e292836fb032a750cc7eb 100644
--- a/polkadot/xcm/pallet-xcm/src/lib.rs
+++ b/polkadot/xcm/pallet-xcm/src/lib.rs
@@ -52,8 +52,8 @@ use frame_system::pallet_prelude::*;
 pub use pallet::*;
 use xcm_executor::{
 	traits::{
-		CheckSuspension, ClaimAssets, DropAssets, MatchesFungible, OnResponse,
-		VersionChangeNotifier, WeightBounds,
+		CheckSuspension, ClaimAssets, DropAssets, MatchesFungible, OnResponse, QueryHandler,
+		QueryResponseStatus, VersionChangeNotifier, WeightBounds,
 	},
 	Assets,
 };
@@ -1126,6 +1126,66 @@ pub mod pallet {
 /// The maximum number of distinct assets allowed to be transferred in a single helper extrinsic.
 const MAX_ASSETS_FOR_TRANSFER: usize = 2;
 
+impl<T: Config> QueryHandler for Pallet<T> {
+	type QueryId = u64;
+	type BlockNumber = T::BlockNumber;
+	type Error = XcmError;
+	type UniversalLocation = T::UniversalLocation;
+
+	/// Attempt to create a new query ID and register it as a query that is yet to respond.
+	fn new_query(
+		responder: impl Into<MultiLocation>,
+		timeout: T::BlockNumber,
+		match_querier: impl Into<MultiLocation>,
+	) -> Self::QueryId {
+		Self::do_new_query(responder, None, timeout, match_querier).into()
+	}
+
+	/// To check the status of the query, use `fn query()` passing the resultant `QueryId`
+	/// value.
+	fn report_outcome(
+		message: &mut Xcm<()>,
+		responder: impl Into<MultiLocation>,
+		timeout: Self::BlockNumber,
+	) -> Result<Self::QueryId, Self::Error> {
+		let responder = responder.into();
+		let destination = Self::UniversalLocation::get()
+			.invert_target(&responder)
+			.map_err(|()| XcmError::LocationNotInvertible)?;
+		let query_id = Self::new_query(responder, timeout, Here);
+		let response_info = QueryResponseInfo { destination, query_id, max_weight: Weight::zero() };
+		let report_error = Xcm(vec![ReportError(response_info)]);
+		message.0.insert(0, SetAppendix(report_error));
+		Ok(query_id)
+	}
+
+	/// Removes response when ready and emits [Event::ResponseTaken] event.
+	fn take_response(query_id: Self::QueryId) -> QueryResponseStatus<Self::BlockNumber> {
+		match Queries::<T>::get(query_id) {
+			Some(QueryStatus::Ready { response, at }) => match response.try_into() {
+				Ok(response) => {
+					Queries::<T>::remove(query_id);
+					Self::deposit_event(Event::ResponseTaken { query_id });
+					QueryResponseStatus::Ready { response, at }
+				},
+				Err(_) => QueryResponseStatus::UnexpectedVersion,
+			},
+			Some(QueryStatus::Pending { timeout, .. }) => QueryResponseStatus::Pending { timeout },
+			Some(_) => QueryResponseStatus::UnexpectedVersion,
+			None => QueryResponseStatus::NotFound,
+		}
+	}
+
+	#[cfg(feature = "runtime-benchmarks")]
+	fn expect_response(id: Self::QueryId, response: Response) {
+		let response = response.into();
+		Queries::<T>::insert(
+			id,
+			QueryStatus::Ready { response, at: frame_system::Pallet::<T>::block_number() },
+		);
+	}
+}
+
 impl<T: Config> Pallet<T> {
 	fn do_reserve_transfer_assets(
 		origin: OriginFor<T>,
@@ -1497,36 +1557,6 @@ impl<T: Config> Pallet<T> {
 		})
 	}
 
-	/// Consume `message` and return another which is equivalent to it except that it reports
-	/// back the outcome.
-	///
-	/// - `message`: The message whose outcome should be reported.
-	/// - `responder`: The origin from which a response should be expected.
-	/// - `timeout`: The block number after which it is permissible for `notify` not to be
-	///   called even if a response is received.
-	///
-	/// `report_outcome` may return an error if the `responder` is not invertible.
-	///
-	/// It is assumed that the querier of the response will be `Here`.
-	///
-	/// To check the status of the query, use `fn query()` passing the resultant `QueryId`
-	/// value.
-	pub fn report_outcome(
-		message: &mut Xcm<()>,
-		responder: impl Into<MultiLocation>,
-		timeout: T::BlockNumber,
-	) -> Result<QueryId, XcmError> {
-		let responder = responder.into();
-		let destination = T::UniversalLocation::get()
-			.invert_target(&responder)
-			.map_err(|()| XcmError::LocationNotInvertible)?;
-		let query_id = Self::new_query(responder, timeout, Here);
-		let response_info = QueryResponseInfo { destination, query_id, max_weight: Weight::zero() };
-		let report_error = Xcm(vec![ReportError(response_info)]);
-		message.0.insert(0, SetAppendix(report_error));
-		Ok(query_id)
-	}
-
 	/// Consume `message` and return another which is equivalent to it except that it reports
 	/// back the outcome and dispatches `notify` on this chain.
 	///
@@ -1568,15 +1598,6 @@ impl<T: Config> Pallet<T> {
 		Ok(())
 	}
 
-	/// Attempt to create a new query ID and register it as a query that is yet to respond.
-	pub fn new_query(
-		responder: impl Into<MultiLocation>,
-		timeout: T::BlockNumber,
-		match_querier: impl Into<MultiLocation>,
-	) -> u64 {
-		Self::do_new_query(responder, None, timeout, match_querier)
-	}
-
 	/// Attempt to create a new query ID and register it as a query that is yet to respond, and
 	/// which will call a dispatchable when a response happens.
 	pub fn new_notify_query(
@@ -1591,20 +1612,6 @@ impl<T: Config> Pallet<T> {
 		Self::do_new_query(responder, Some(notify), timeout, match_querier)
 	}
 
-	/// Attempt to remove and return the response of query with ID `query_id`.
-	///
-	/// Returns `None` if the response is not (yet) available.
-	pub fn take_response(query_id: QueryId) -> Option<(Response, T::BlockNumber)> {
-		if let Some(QueryStatus::Ready { response, at }) = Queries::<T>::get(query_id) {
-			let response = response.try_into().ok()?;
-			Queries::<T>::remove(query_id);
-			Self::deposit_event(Event::ResponseTaken { query_id });
-			Some((response, at))
-		} else {
-			None
-		}
-	}
-
 	/// Note that a particular destination to whom we would like to send a message is unknown
 	/// and queue it for version discovery.
 	fn note_unknown_version(dest: &MultiLocation) {
diff --git a/polkadot/xcm/pallet-xcm/src/mock.rs b/polkadot/xcm/pallet-xcm/src/mock.rs
index de3f8d4957a34830e44908e96b8d450fe86e97ea..6cfc1447e2b4889c385c66c89070d83186b54110 100644
--- a/polkadot/xcm/pallet-xcm/src/mock.rs
+++ b/polkadot/xcm/pallet-xcm/src/mock.rs
@@ -50,6 +50,7 @@ pub mod pallet_test_notifier {
 	use frame_system::pallet_prelude::*;
 	use sp_runtime::DispatchResult;
 	use xcm::latest::prelude::*;
+	use xcm_executor::traits::QueryHandler;
 
 	#[pallet::pallet]
 	pub struct Pallet<T>(_);
@@ -85,7 +86,7 @@ pub mod pallet_test_notifier {
 			let id = who
 				.using_encoded(|mut d| <[u8; 32]>::decode(&mut d))
 				.map_err(|_| Error::<T>::BadAccountFormat)?;
-			let qid = crate::Pallet::<T>::new_query(
+			let qid = <crate::Pallet<T> as QueryHandler>::new_query(
 				Junction::AccountId32 { network: None, id },
 				100u32.into(),
 				querier,
diff --git a/polkadot/xcm/pallet-xcm/src/tests.rs b/polkadot/xcm/pallet-xcm/src/tests.rs
index 6415fe03d8954d73de7035b9c24cad3920370053..2ad13dced936314416a356ef3f601b6e2d91d8db 100644
--- a/polkadot/xcm/pallet-xcm/src/tests.rs
+++ b/polkadot/xcm/pallet-xcm/src/tests.rs
@@ -28,7 +28,7 @@ use sp_runtime::traits::{AccountIdConversion, BlakeTwo256, Hash};
 use xcm::{latest::QueryResponseInfo, prelude::*};
 use xcm_builder::AllowKnownQueryResponses;
 use xcm_executor::{
-	traits::{Properties, ShouldExecute},
+	traits::{Properties, QueryHandler, QueryResponseStatus, ShouldExecute},
 	XcmExecutor,
 };
 
@@ -170,7 +170,8 @@ fn report_outcome_works() {
 			})
 		);
 
-		let response = Some((Response::ExecutionResult(None), 1));
+		let response =
+			QueryResponseStatus::Ready { response: Response::ExecutionResult(None), at: 1 };
 		assert_eq!(XcmPallet::take_response(0), response);
 	});
 }
@@ -270,7 +271,8 @@ fn custom_querier_works() {
 			})
 		);
 
-		let response = Some((Response::ExecutionResult(None), 1));
+		let response =
+			QueryResponseStatus::Ready { response: Response::ExecutionResult(None), at: 1 };
 		assert_eq!(XcmPallet::take_response(0), response);
 	});
 }
diff --git a/polkadot/xcm/xcm-builder/Cargo.toml b/polkadot/xcm/xcm-builder/Cargo.toml
index 6f4a4f9dde141225e3a07f5f80e6e0bd20f28e6f..465d338fd0a7cf7e6f9524a317f334e287529bf0 100644
--- a/polkadot/xcm/xcm-builder/Cargo.toml
+++ b/polkadot/xcm/xcm-builder/Cargo.toml
@@ -36,7 +36,8 @@ polkadot-test-runtime = { path = "../../runtime/test-runtime" }
 default = ["std"]
 runtime-benchmarks = [
 	"frame-support/runtime-benchmarks",
-	"frame-system/runtime-benchmarks"
+	"frame-system/runtime-benchmarks",
+	"xcm-executor/runtime-benchmarks",
 ]
 std = [
 	"log/std",
diff --git a/polkadot/xcm/xcm-builder/src/lib.rs b/polkadot/xcm/xcm-builder/src/lib.rs
index 9ff37209c015760ab74094e51942e9b95235115c..124e83d3c338dc294e55616d380ae0828f41173a 100644
--- a/polkadot/xcm/xcm-builder/src/lib.rs
+++ b/polkadot/xcm/xcm-builder/src/lib.rs
@@ -28,8 +28,9 @@ pub mod test_utils;
 
 mod location_conversion;
 pub use location_conversion::{
-	Account32Hash, AccountId32Aliases, AccountKey20Aliases, ChildParachainConvertsVia,
-	GlobalConsensusParachainConvertsFor, ParentIsPreset, SiblingParachainConvertsVia,
+	Account32Hash, AccountId32Aliases, AccountKey20Aliases, AliasesIntoAccountId32,
+	ChildParachainConvertsVia, GlobalConsensusParachainConvertsFor, ParentIsPreset,
+	SiblingParachainConvertsVia,
 };
 
 mod origin_conversion;
@@ -95,3 +96,6 @@ pub use universal_exports::{
 	ExporterFor, HaulBlob, HaulBlobError, HaulBlobExporter, NetworkExportTable,
 	SovereignPaidRemoteExporter, UnpaidLocalExporter, UnpaidRemoteExporter,
 };
+
+mod pay;
+pub use pay::{FixedLocation, LocatableAssetId, PayAccountId32OnChainOverXcm, PayOverXcm};
diff --git a/polkadot/xcm/xcm-builder/src/location_conversion.rs b/polkadot/xcm/xcm-builder/src/location_conversion.rs
index 66c9fc6dd31d861c814bff825d5ddd44221c2a73..bafec5da4a30c133e0c0b02f1cc2f7a72a5ce7f3 100644
--- a/polkadot/xcm/xcm-builder/src/location_conversion.rs
+++ b/polkadot/xcm/xcm-builder/src/location_conversion.rs
@@ -232,6 +232,26 @@ impl<Network: Get<Option<NetworkId>>, AccountId: From<[u8; 32]> + Into<[u8; 32]>
 	}
 }
 
+/// Conversion implementation which converts from a `[u8; 32]`-based `AccountId` into a
+/// `MultiLocation` consisting solely of a `AccountId32` junction with a fixed value for its
+/// network (provided by `Network`) and the `AccountId`'s `[u8; 32]` datum for the `id`.
+pub struct AliasesIntoAccountId32<Network, AccountId>(PhantomData<(Network, AccountId)>);
+impl<'a, Network: Get<Option<NetworkId>>, AccountId: Clone + Into<[u8; 32]> + Clone>
+	Convert<&'a AccountId, MultiLocation> for AliasesIntoAccountId32<Network, AccountId>
+{
+	fn convert(who: &AccountId) -> Result<MultiLocation, &'a AccountId> {
+		Ok(AccountId32 { network: Network::get(), id: who.clone().into() }.into())
+	}
+}
+
+impl<Network: Get<Option<NetworkId>>, AccountId: Into<[u8; 32]> + Clone>
+	Convert<AccountId, MultiLocation> for AliasesIntoAccountId32<Network, AccountId>
+{
+	fn convert(who: AccountId) -> Result<MultiLocation, AccountId> {
+		Ok(AccountId32 { network: Network::get(), id: who.into() }.into())
+	}
+}
+
 pub struct AccountKey20Aliases<Network, AccountId>(PhantomData<(Network, AccountId)>);
 impl<Network: Get<Option<NetworkId>>, AccountId: From<[u8; 20]> + Into<[u8; 20]> + Clone>
 	Convert<MultiLocation, AccountId> for AccountKey20Aliases<Network, AccountId>
diff --git a/polkadot/xcm/xcm-builder/src/pay.rs b/polkadot/xcm/xcm-builder/src/pay.rs
new file mode 100644
index 0000000000000000000000000000000000000000..e8cd2b2bb287b0de6b5026128950c806d17460f7
--- /dev/null
+++ b/polkadot/xcm/xcm-builder/src/pay.rs
@@ -0,0 +1,205 @@
+// Copyright 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/>.
+
+//! `PayOverXcm` struct for paying through XCM and getting the status back.
+
+use frame_support::traits::{
+	tokens::{Pay, PaymentStatus},
+	Get,
+};
+use sp_runtime::traits::Convert;
+use sp_std::{marker::PhantomData, vec};
+use xcm::{opaque::lts::Weight, prelude::*};
+use xcm_executor::traits::{QueryHandler, QueryResponseStatus};
+
+/// Implementation of the `frame_support::traits::tokens::Pay` trait, to allow
+/// for XCM-based payments of a given `Balance` of some asset ID existing on some chain under
+/// ownership of some `Interior` location of the local chain to a particular `Beneficiary`. The
+/// `AssetKind` value can be converted into both the XCM `AssetId` (via and `Into` bound) and the
+/// the destination chain's location, via the `AssetKindToLocatableAsset` type parameter.
+///
+/// This relies on the XCM `TransferAsset` instruction. A trait `BeneficiaryRefToLocation` must be
+/// provided in order to convert the `Beneficiary` reference into a location usable by
+/// `TransferAsset`.
+///
+/// `PayOverXcm::pay` is asynchronous, and returns a `QueryId` which can then be used in
+/// `check_payment` to check the status of the XCM transaction.
+///
+/// See also `PayAccountId32OverXcm` which is similar to this except that `BeneficiaryRefToLocation`
+/// need not be supplied and `Beneficiary` must implement `Into<[u8; 32]>`.
+pub struct PayOverXcm<
+	Interior,
+	Router,
+	Querier,
+	Timeout,
+	Beneficiary,
+	AssetKind,
+	AssetKindToLocatableAsset,
+	BeneficiaryRefToLocation,
+>(
+	PhantomData<(
+		Interior,
+		Router,
+		Querier,
+		Timeout,
+		Beneficiary,
+		AssetKind,
+		AssetKindToLocatableAsset,
+		BeneficiaryRefToLocation,
+	)>,
+);
+impl<
+		Interior: Get<InteriorMultiLocation>,
+		Router: SendXcm,
+		Querier: QueryHandler,
+		Timeout: Get<Querier::BlockNumber>,
+		Beneficiary: Clone,
+		AssetKind,
+		AssetKindToLocatableAsset: Convert<AssetKind, LocatableAssetId>,
+		BeneficiaryRefToLocation: for<'a> Convert<&'a Beneficiary, MultiLocation>,
+	> Pay
+	for PayOverXcm<
+		Interior,
+		Router,
+		Querier,
+		Timeout,
+		Beneficiary,
+		AssetKind,
+		AssetKindToLocatableAsset,
+		BeneficiaryRefToLocation,
+	>
+{
+	type Beneficiary = Beneficiary;
+	type AssetKind = AssetKind;
+	type Balance = u128;
+	type Id = Querier::QueryId;
+	type Error = xcm::latest::Error;
+
+	fn pay(
+		who: &Self::Beneficiary,
+		asset_kind: Self::AssetKind,
+		amount: Self::Balance,
+	) -> Result<Self::Id, Self::Error> {
+		let locatable = AssetKindToLocatableAsset::convert(asset_kind);
+		let LocatableAssetId { asset_id, location: asset_location } = locatable;
+		let destination = Querier::UniversalLocation::get()
+			.invert_target(&asset_location)
+			.map_err(|()| Self::Error::LocationNotInvertible)?;
+		let beneficiary = BeneficiaryRefToLocation::convert(&who);
+
+		let query_id = Querier::new_query(asset_location, Timeout::get(), Interior::get());
+
+		let message = Xcm(vec![
+			UnpaidExecution { weight_limit: Unlimited, check_origin: None },
+			SetAppendix(Xcm(vec![ReportError(QueryResponseInfo {
+				destination,
+				query_id,
+				max_weight: Weight::zero(),
+			})])),
+			DescendOrigin(Interior::get()),
+			TransferAsset {
+				beneficiary,
+				assets: vec![MultiAsset { id: asset_id, fun: Fungibility::Fungible(amount) }]
+					.into(),
+			},
+		]);
+
+		let (ticket, _) = Router::validate(&mut Some(asset_location), &mut Some(message))?;
+		Router::deliver(ticket)?;
+		Ok(query_id.into())
+	}
+
+	fn check_payment(id: Self::Id) -> PaymentStatus {
+		use QueryResponseStatus::*;
+		match Querier::take_response(id) {
+			Ready { response, .. } => match response {
+				Response::ExecutionResult(None) => PaymentStatus::Success,
+				Response::ExecutionResult(Some(_)) => PaymentStatus::Failure,
+				_ => PaymentStatus::Unknown,
+			},
+			Pending { .. } => PaymentStatus::InProgress,
+			NotFound | UnexpectedVersion => PaymentStatus::Unknown,
+		}
+	}
+
+	#[cfg(feature = "runtime-benchmarks")]
+	fn ensure_successful(_: &Self::Beneficiary, _: Self::AssetKind, _: Self::Balance) {
+		// We cannot generally guarantee this will go through successfully since we don't have any
+		// control over the XCM transport layers. We just assume that the benchmark environment
+		// will be sending it somewhere sensible.
+	}
+
+	#[cfg(feature = "runtime-benchmarks")]
+	fn ensure_concluded(id: Self::Id) {
+		Querier::expect_response(id, Response::ExecutionResult(None));
+	}
+}
+
+/// Specialization of the [PayOverXcm] trait to allow `[u8; 32]`-based `AccountId` values to be
+/// paid on a remote chain.
+///
+/// Implementation of the [frame_support::traits::tokens::Pay] trait, to allow
+/// for XCM payments of a given `Balance` of `AssetKind` existing on a `DestinationChain` under
+/// ownership of some `Interior` location of the local chain to a particular `Beneficiary`.
+///
+/// This relies on the XCM `TransferAsset` instruction. `Beneficiary` must implement
+/// `Into<[u8; 32]>` (as 32-byte `AccountId`s generally do), and the actual XCM beneficiary will be
+/// the location consisting of a single `AccountId32` junction with an appropriate account and no
+/// specific network.
+///
+/// `PayOverXcm::pay` is asynchronous, and returns a `QueryId` which can then be used in
+/// `check_payment` to check the status of the XCM transaction.
+pub type PayAccountId32OnChainOverXcm<
+	DestinationChain,
+	Interior,
+	Router,
+	Querier,
+	Timeout,
+	Beneficiary,
+	AssetKind,
+> = PayOverXcm<
+	Interior,
+	Router,
+	Querier,
+	Timeout,
+	Beneficiary,
+	AssetKind,
+	crate::AliasesIntoAccountId32<(), Beneficiary>,
+	FixedLocation<DestinationChain>,
+>;
+
+/// Simple struct which contains both an XCM `location` and `asset_id` to identift an asset which
+/// exists on some chain.
+pub struct LocatableAssetId {
+	/// The asset's ID.
+	pub asset_id: AssetId,
+	/// The (relative) location in which the asset ID is meaningful.
+	pub location: MultiLocation,
+}
+
+/// Adapter `struct` which implements a conversion from any `AssetKind` into a [LocatableAsset]
+/// value using a fixed `Location` for the `location` field.
+pub struct FixedLocation<Location>(sp_std::marker::PhantomData<Location>);
+impl<Location: Get<MultiLocation>, AssetKind: Into<AssetId>> Convert<AssetKind, LocatableAssetId>
+	for FixedLocation<Location>
+{
+	fn convert(value: AssetKind) -> LocatableAssetId {
+		LocatableAssetId { asset_id: value.into(), location: Location::get() }
+	}
+}
+
+#[test]
+fn it_builds() {}
diff --git a/polkadot/xcm/xcm-executor/src/traits/mod.rs b/polkadot/xcm/xcm-executor/src/traits/mod.rs
index 3b904630d73efe4b769204f4e6041d48c7d4a7f8..cac9c73ee277eedca4aed15a9d20cc32a42526e9 100644
--- a/polkadot/xcm/xcm-executor/src/traits/mod.rs
+++ b/polkadot/xcm/xcm-executor/src/traits/mod.rs
@@ -38,7 +38,7 @@ pub use token_matching::{
 	Error, MatchesFungible, MatchesFungibles, MatchesNonFungible, MatchesNonFungibles,
 };
 mod on_response;
-pub use on_response::{OnResponse, VersionChangeNotifier};
+pub use on_response::{OnResponse, QueryHandler, QueryResponseStatus, VersionChangeNotifier};
 mod should_execute;
 pub use should_execute::{CheckSuspension, Properties, ShouldExecute};
 mod transact_asset;
diff --git a/polkadot/xcm/xcm-executor/src/traits/on_response.rs b/polkadot/xcm/xcm-executor/src/traits/on_response.rs
index dcb7d924d799e02117469eddbb00e50429c322e3..34bb7eb9597d13cc84d5f7057cc0ac13d64b7497 100644
--- a/polkadot/xcm/xcm-executor/src/traits/on_response.rs
+++ b/polkadot/xcm/xcm-executor/src/traits/on_response.rs
@@ -14,8 +14,17 @@
 // You should have received a copy of the GNU General Public License
 // along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
 
+use crate::Xcm;
+use core::result;
+use frame_support::{
+	dispatch::fmt::Debug,
+	pallet_prelude::{Get, TypeInfo},
+};
+use parity_scale_codec::{FullCodec, MaxEncodedLen};
+use sp_arithmetic::traits::Zero;
 use xcm::latest::{
-	Error as XcmError, MultiLocation, QueryId, Response, Result as XcmResult, Weight, XcmContext,
+	Error as XcmError, InteriorMultiLocation, MultiLocation, QueryId, Response,
+	Result as XcmResult, Weight, XcmContext,
 };
 
 /// Define what needs to be done upon receiving a query response.
@@ -94,3 +103,63 @@ impl VersionChangeNotifier for () {
 		false
 	}
 }
+
+/// The possible state of an XCM query response.
+#[derive(Debug, PartialEq, Eq)]
+pub enum QueryResponseStatus<BlockNumber> {
+	/// The response has arrived, and includes the inner Response and the block number it arrived at.
+	Ready { response: Response, at: BlockNumber },
+	/// The response has not yet arrived, the XCM might still be executing or the response might be in transit.
+	Pending { timeout: BlockNumber },
+	/// No response with the given `QueryId` was found, or the response was already queried and removed from local storage.
+	NotFound,
+	/// Got an unexpected XCM version.
+	UnexpectedVersion,
+}
+
+/// Provides methods to expect responses from XCMs and query their status.
+pub trait QueryHandler {
+	type QueryId: From<u64>
+		+ FullCodec
+		+ MaxEncodedLen
+		+ TypeInfo
+		+ Clone
+		+ Eq
+		+ PartialEq
+		+ Debug
+		+ Copy;
+	type BlockNumber: Zero;
+	type Error;
+	type UniversalLocation: Get<InteriorMultiLocation>;
+
+	/// Attempt to create a new query ID and register it as a query that is yet to respond.
+	fn new_query(
+		responder: impl Into<MultiLocation>,
+		timeout: Self::BlockNumber,
+		match_querier: impl Into<MultiLocation>,
+	) -> QueryId;
+
+	/// Consume `message` and return another which is equivalent to it except that it reports
+	/// back the outcome.
+	///
+	/// - `message`: The message whose outcome should be reported.
+	/// - `responder`: The origin from which a response should be expected.
+	/// - `timeout`: The block number after which it is permissible to return `NotFound` from `take_response`.
+	///
+	/// `report_outcome` may return an error if the `responder` is not invertible.
+	///
+	/// It is assumed that the querier of the response will be `Here`.
+	/// The response can be queried with `take_response`.
+	fn report_outcome(
+		message: &mut Xcm<()>,
+		responder: impl Into<MultiLocation>,
+		timeout: Self::BlockNumber,
+	) -> result::Result<Self::QueryId, Self::Error>;
+
+	/// Attempt to remove and return the response of query with ID `query_id`.
+	fn take_response(id: Self::QueryId) -> QueryResponseStatus<Self::BlockNumber>;
+
+	/// Makes sure to expect a response with the given id.
+	#[cfg(feature = "runtime-benchmarks")]
+	fn expect_response(id: Self::QueryId, response: Response);
+}