diff --git a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs
index a837b8d25dcf7a1f5b03e598447f8e34c0e0e49c..54cfc64930b80fb7b5a26e156049b278bd57883e 100644
--- a/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs
+++ b/cumulus/parachains/runtimes/coretime/coretime-rococo/src/coretime.rs
@@ -111,6 +111,7 @@ enum CoretimeProviderCalls {
 
 parameter_types! {
 	pub const BrokerPalletId: PalletId = PalletId(*b"py/broke");
+	pub const MinimumCreditPurchase: Balance =  UNITS / 10;
 	pub RevenueAccumulationAccount: AccountId = BrokerPalletId::get().into_sub_account_truncating(b"burnstash");
 }
 
@@ -319,4 +320,5 @@ impl pallet_broker::Config for Runtime {
 	type SovereignAccountOf = SovereignAccountOf;
 	type MaxAutoRenewals = ConstU32<100>;
 	type PriceAdapter = pallet_broker::CenterTargetPrice<Balance>;
+	type MinimumCreditPurchase = MinimumCreditPurchase;
 }
diff --git a/cumulus/parachains/runtimes/coretime/coretime-westend/src/coretime.rs b/cumulus/parachains/runtimes/coretime/coretime-westend/src/coretime.rs
index 805861b1f8bdb90acd967e93bdaf198c892ee64c..9aa9e699b65da0689c2f0e14236371e254dcfcb8 100644
--- a/cumulus/parachains/runtimes/coretime/coretime-westend/src/coretime.rs
+++ b/cumulus/parachains/runtimes/coretime/coretime-westend/src/coretime.rs
@@ -111,6 +111,7 @@ enum CoretimeProviderCalls {
 
 parameter_types! {
 	pub const BrokerPalletId: PalletId = PalletId(*b"py/broke");
+	pub const MinimumCreditPurchase: Balance = UNITS / 10;
 	pub RevenueAccumulationAccount: AccountId = BrokerPalletId::get().into_sub_account_truncating(b"burnstash");
 }
 
@@ -332,4 +333,5 @@ impl pallet_broker::Config for Runtime {
 	type SovereignAccountOf = SovereignAccountOf;
 	type MaxAutoRenewals = ConstU32<20>;
 	type PriceAdapter = pallet_broker::CenterTargetPrice<Balance>;
+	type MinimumCreditPurchase = MinimumCreditPurchase;
 }
diff --git a/polkadot/runtime/parachains/src/assigner_coretime/tests.rs b/polkadot/runtime/parachains/src/assigner_coretime/tests.rs
index ab011bfc4ae126050f39e9b0d3fffaab04e3c8e4..ed56170cbcdec14657780de06a7369be6450543f 100644
--- a/polkadot/runtime/parachains/src/assigner_coretime/tests.rs
+++ b/polkadot/runtime/parachains/src/assigner_coretime/tests.rs
@@ -20,13 +20,13 @@ use crate::{
 	assigner_coretime::{mock_helpers::GenesisConfigBuilder, pallet::Error, Schedule},
 	initializer::SessionChangeNotification,
 	mock::{
-		new_test_ext, Balances, CoretimeAssigner, OnDemand, Paras, ParasShared, RuntimeOrigin,
-		Scheduler, System, Test,
+		new_test_ext, CoretimeAssigner, OnDemand, Paras, ParasShared, RuntimeOrigin, Scheduler,
+		System, Test,
 	},
 	paras::{ParaGenesisArgs, ParaKind},
 	scheduler::common::Assignment,
 };
-use frame_support::{assert_noop, assert_ok, pallet_prelude::*, traits::Currency};
+use frame_support::{assert_noop, assert_ok, pallet_prelude::*};
 use pallet_broker::TaskId;
 use polkadot_primitives::{BlockNumber, Id as ParaId, SessionIndex, ValidationCode};
 
@@ -494,9 +494,9 @@ fn pop_assignment_for_core_works() {
 		// Initialize the parathread, wait for it to be ready, then add an
 		// on demand order to later pop with our Coretime assigner.
 		schedule_blank_para(para_id, ParaKind::Parathread);
-		Balances::make_free_balance_be(&alice, amt);
+		on_demand::Credits::<Test>::insert(&alice, amt);
 		run_to_block(1, |n| if n == 1 { Some(Default::default()) } else { None });
-		assert_ok!(OnDemand::place_order_allow_death(RuntimeOrigin::signed(alice), amt, para_id));
+		assert_ok!(OnDemand::place_order_with_credits(RuntimeOrigin::signed(alice), amt, para_id));
 
 		// Case 1: Assignment idle
 		assert_ok!(CoretimeAssigner::assign_core(
diff --git a/polkadot/runtime/parachains/src/coretime/benchmarking.rs b/polkadot/runtime/parachains/src/coretime/benchmarking.rs
index 49e3d8a88c0158f09aa3a4d8e98a969ca907be44..aaa4a4f9ee9a921acd33d95be1596ab333039cb2 100644
--- a/polkadot/runtime/parachains/src/coretime/benchmarking.rs
+++ b/polkadot/runtime/parachains/src/coretime/benchmarking.rs
@@ -96,4 +96,14 @@ mod benchmarks {
 			Some(BlockNumberFor::<T>::from(20u32)),
 		)
 	}
+
+	#[benchmark]
+	fn credit_account() {
+		// Setup
+		let root_origin = <T as frame_system::Config>::RuntimeOrigin::root();
+		let who: T::AccountId = whitelisted_caller();
+
+		#[extrinsic_call]
+		_(root_origin as <T as frame_system::Config>::RuntimeOrigin, who, 1_000_000u32.into())
+	}
 }
diff --git a/polkadot/runtime/parachains/src/coretime/mod.rs b/polkadot/runtime/parachains/src/coretime/mod.rs
index 5656e92b90be064d45f52481460e5fce36cc591d..e961d5fa76a8b61e653e4840870d5cf99a4541c5 100644
--- a/polkadot/runtime/parachains/src/coretime/mod.rs
+++ b/polkadot/runtime/parachains/src/coretime/mod.rs
@@ -48,7 +48,7 @@ const LOG_TARGET: &str = "runtime::parachains::coretime";
 pub trait WeightInfo {
 	fn request_core_count() -> Weight;
 	fn request_revenue_at() -> Weight;
-	//fn credit_account() -> Weight;
+	fn credit_account() -> Weight;
 	fn assign_core(s: u32) -> Weight;
 }
 
@@ -62,19 +62,18 @@ impl WeightInfo for TestWeightInfo {
 	fn request_revenue_at() -> Weight {
 		Weight::MAX
 	}
-	// TODO: Add real benchmarking functionality for each of these to
-	// benchmarking.rs, then uncomment here and in trait definition.
-	//fn credit_account() -> Weight {
-	//	Weight::MAX
-	//}
+	fn credit_account() -> Weight {
+		Weight::MAX
+	}
 	fn assign_core(_s: u32) -> Weight {
 		Weight::MAX
 	}
 }
 
 /// Shorthand for the Balance type the runtime is using.
-pub type BalanceOf<T> =
-	<<T as Config>::Currency as Currency<<T as frame_system::Config>::AccountId>>::Balance;
+pub type BalanceOf<T> = <<T as on_demand::Config>::Currency as Currency<
+	<T as frame_system::Config>::AccountId,
+>>::Balance;
 
 /// Broker pallet index on the coretime chain. Used to
 ///
@@ -120,8 +119,6 @@ pub mod pallet {
 		type RuntimeOrigin: From<<Self as frame_system::Config>::RuntimeOrigin>
 			+ Into<result::Result<Origin, <Self as Config>::RuntimeOrigin>>;
 		type RuntimeEvent: From<Event<Self>> + IsType<<Self as frame_system::Config>::RuntimeEvent>;
-		/// The runtime's definition of a Currency.
-		type Currency: Currency<Self::AccountId>;
 		/// The ParaId of the coretime chain.
 		#[pallet::constant]
 		type BrokerId: Get<u32>;
@@ -195,18 +192,19 @@ pub mod pallet {
 			Self::notify_revenue(when)
 		}
 
-		//// TODO Impl me!
-		////#[pallet::weight(<T as Config>::WeightInfo::credit_account())]
-		//#[pallet::call_index(3)]
-		//pub fn credit_account(
-		//	origin: OriginFor<T>,
-		//	_who: T::AccountId,
-		//	_amount: BalanceOf<T>,
-		//) -> DispatchResult {
-		//	// Ignore requests not coming from the coretime chain or root.
-		//	Self::ensure_root_or_para(origin, <T as Config>::BrokerId::get().into())?;
-		//	Ok(())
-		//}
+		#[pallet::weight(<T as Config>::WeightInfo::credit_account())]
+		#[pallet::call_index(3)]
+		pub fn credit_account(
+			origin: OriginFor<T>,
+			who: T::AccountId,
+			amount: BalanceOf<T>,
+		) -> DispatchResult {
+			// Ignore requests not coming from the coretime chain or root.
+			Self::ensure_root_or_para(origin, <T as Config>::BrokerId::get().into())?;
+
+			on_demand::Pallet::<T>::credit_account(who, amount.saturated_into());
+			Ok(())
+		}
 
 		/// Receive instructions from the `ExternalBrokerOrigin`, detailing how a specific core is
 		/// to be used.
diff --git a/polkadot/runtime/parachains/src/mock.rs b/polkadot/runtime/parachains/src/mock.rs
index ee1990a7b618ab9ad8a3a6261a55b2e1163d3080..fdf4898eaaaa298089ea3250495149f8840b6b38 100644
--- a/polkadot/runtime/parachains/src/mock.rs
+++ b/polkadot/runtime/parachains/src/mock.rs
@@ -434,7 +434,6 @@ impl Get<InteriorLocation> for BrokerPot {
 impl coretime::Config for Test {
 	type RuntimeOrigin = RuntimeOrigin;
 	type RuntimeEvent = RuntimeEvent;
-	type Currency = pallet_balances::Pallet<Test>;
 	type BrokerId = BrokerId;
 	type WeightInfo = crate::coretime::TestWeightInfo;
 	type SendXcm = DummyXcmSender;
diff --git a/polkadot/runtime/parachains/src/on_demand/benchmarking.rs b/polkadot/runtime/parachains/src/on_demand/benchmarking.rs
index d494a77a5c4dbeee27e510e4df35f1f78741b885..4a996848bb029f11ff593aa88487c952637e022a 100644
--- a/polkadot/runtime/parachains/src/on_demand/benchmarking.rs
+++ b/polkadot/runtime/parachains/src/on_demand/benchmarking.rs
@@ -91,6 +91,20 @@ mod benchmarks {
 		_(RawOrigin::Signed(caller.into()), BalanceOf::<T>::max_value(), para_id)
 	}
 
+	#[benchmark]
+	fn place_order_with_credits(s: Linear<1, MAX_FILL_BENCH>) {
+		// Setup
+		let caller: T::AccountId = whitelisted_caller();
+		let para_id = ParaId::from(111u32);
+		init_parathread::<T>(para_id);
+		Credits::<T>::insert(&caller, BalanceOf::<T>::max_value());
+
+		Pallet::<T>::populate_queue(para_id, s);
+
+		#[extrinsic_call]
+		_(RawOrigin::Signed(caller.into()), BalanceOf::<T>::max_value(), para_id)
+	}
+
 	impl_benchmark_test_suite!(
 		Pallet,
 		crate::mock::new_test_ext(
diff --git a/polkadot/runtime/parachains/src/on_demand/mod.rs b/polkadot/runtime/parachains/src/on_demand/mod.rs
index 66400eb00fd9d7a64ed2ffd660ac0e0f7c3e9027..c8ff4b1ae4a5d96b3b6b041ddbced04ae1b779cc 100644
--- a/polkadot/runtime/parachains/src/on_demand/mod.rs
+++ b/polkadot/runtime/parachains/src/on_demand/mod.rs
@@ -73,6 +73,7 @@ pub use pallet::*;
 pub trait WeightInfo {
 	fn place_order_allow_death(s: u32) -> Weight;
 	fn place_order_keep_alive(s: u32) -> Weight;
+	fn place_order_with_credits(s: u32) -> Weight;
 }
 
 /// A weight info that is only suitable for testing.
@@ -86,6 +87,19 @@ impl WeightInfo for TestWeightInfo {
 	fn place_order_keep_alive(_: u32) -> Weight {
 		Weight::MAX
 	}
+
+	fn place_order_with_credits(_: u32) -> Weight {
+		Weight::MAX
+	}
+}
+
+/// Defines how the account wants to pay for on-demand.
+#[derive(Encode, Decode, TypeInfo, Debug, PartialEq, Clone, Eq)]
+enum PaymentType {
+	/// Use credits to purchase on-demand coretime.
+	Credits,
+	/// Use account's free balance to purchase on-demand coretime.
+	Balance,
 }
 
 #[frame_support::pallet]
@@ -169,6 +183,11 @@ pub mod pallet {
 	pub type Revenue<T: Config> =
 		StorageValue<_, BoundedVec<BalanceOf<T>, T::MaxHistoricalRevenue>, ValueQuery>;
 
+	/// Keeps track of credits owned by each account.
+	#[pallet::storage]
+	pub type Credits<T: Config> =
+		StorageMap<_, Blake2_128Concat, T::AccountId, BalanceOf<T>, ValueQuery>;
+
 	#[pallet::event]
 	#[pallet::generate_deposit(pub(super) fn deposit_event)]
 	pub enum Event<T: Config> {
@@ -176,6 +195,8 @@ pub mod pallet {
 		OnDemandOrderPlaced { para_id: ParaId, spot_price: BalanceOf<T>, ordered_by: T::AccountId },
 		/// The value of the spot price has likely changed
 		SpotPriceSet { spot_price: BalanceOf<T> },
+		/// An account was given credits.
+		AccountCredited { who: T::AccountId, amount: BalanceOf<T> },
 	}
 
 	#[pallet::error]
@@ -185,6 +206,8 @@ pub mod pallet {
 		/// The current spot price is higher than the max amount specified in the `place_order`
 		/// call, making it invalid.
 		SpotPriceHigherThanMaxAmount,
+		/// The account doesn't have enough credits to purchase on-demand coretime.
+		InsufficientCredits,
 	}
 
 	#[pallet::hooks]
@@ -235,13 +258,21 @@ pub mod pallet {
 		/// - `OnDemandOrderPlaced`
 		#[pallet::call_index(0)]
 		#[pallet::weight(<T as Config>::WeightInfo::place_order_allow_death(QueueStatus::<T>::get().size()))]
+		#[allow(deprecated)]
+		#[deprecated(note = "This will be removed in favor of using `place_order_with_credits`")]
 		pub fn place_order_allow_death(
 			origin: OriginFor<T>,
 			max_amount: BalanceOf<T>,
 			para_id: ParaId,
 		) -> DispatchResult {
 			let sender = ensure_signed(origin)?;
-			Pallet::<T>::do_place_order(sender, max_amount, para_id, AllowDeath)
+			Pallet::<T>::do_place_order(
+				sender,
+				max_amount,
+				para_id,
+				AllowDeath,
+				PaymentType::Balance,
+			)
 		}
 
 		/// Same as the [`place_order_allow_death`](Self::place_order_allow_death) call , but with a
@@ -261,13 +292,55 @@ pub mod pallet {
 		/// - `OnDemandOrderPlaced`
 		#[pallet::call_index(1)]
 		#[pallet::weight(<T as Config>::WeightInfo::place_order_keep_alive(QueueStatus::<T>::get().size()))]
+		#[allow(deprecated)]
+		#[deprecated(note = "This will be removed in favor of using `place_order_with_credits`")]
 		pub fn place_order_keep_alive(
 			origin: OriginFor<T>,
 			max_amount: BalanceOf<T>,
 			para_id: ParaId,
 		) -> DispatchResult {
 			let sender = ensure_signed(origin)?;
-			Pallet::<T>::do_place_order(sender, max_amount, para_id, KeepAlive)
+			Pallet::<T>::do_place_order(
+				sender,
+				max_amount,
+				para_id,
+				KeepAlive,
+				PaymentType::Balance,
+			)
+		}
+
+		/// Create a single on demand core order with credits.
+		/// Will charge the owner's on-demand credit account the spot price for the current block.
+		///
+		/// Parameters:
+		/// - `origin`: The sender of the call, on-demand credits will be withdrawn from this
+		///   account.
+		/// - `max_amount`: The maximum number of credits to spend from the origin to place an
+		///   order.
+		/// - `para_id`: A `ParaId` the origin wants to provide blockspace for.
+		///
+		/// Errors:
+		/// - `InsufficientCredits`
+		/// - `QueueFull`
+		/// - `SpotPriceHigherThanMaxAmount`
+		///
+		/// Events:
+		/// - `OnDemandOrderPlaced`
+		#[pallet::call_index(2)]
+		#[pallet::weight(<T as Config>::WeightInfo::place_order_with_credits(QueueStatus::<T>::get().size()))]
+		pub fn place_order_with_credits(
+			origin: OriginFor<T>,
+			max_amount: BalanceOf<T>,
+			para_id: ParaId,
+		) -> DispatchResult {
+			let sender = ensure_signed(origin)?;
+			Pallet::<T>::do_place_order(
+				sender,
+				max_amount,
+				para_id,
+				KeepAlive,
+				PaymentType::Credits,
+			)
 		}
 	}
 }
@@ -349,6 +422,18 @@ where
 		});
 	}
 
+	/// Adds credits to the specified account.
+	///
+	/// Parameters:
+	/// - `who`: Credit receiver.
+	/// - `amount`: The amount of new credits the account will receive.
+	pub fn credit_account(who: T::AccountId, amount: BalanceOf<T>) {
+		Credits::<T>::mutate(who.clone(), |credits| {
+			*credits = credits.saturating_add(amount);
+		});
+		Pallet::<T>::deposit_event(Event::<T>::AccountCredited { who, amount });
+	}
+
 	/// Helper function for `place_order_*` calls. Used to differentiate between placing orders
 	/// with a keep alive check or to allow the account to be reaped. The amount charged is
 	/// stored to the pallet account to be later paid out as revenue.
@@ -358,6 +443,7 @@ where
 	/// - `max_amount`: The maximum balance to withdraw from the origin to place an order.
 	/// - `para_id`: A `ParaId` the origin wants to provide blockspace for.
 	/// - `existence_requirement`: Whether or not to ensure that the account will not be reaped.
+	/// - `payment_type`: Defines how the user wants to pay for on-demand.
 	///
 	/// Errors:
 	/// - `InsufficientBalance`: from the Currency implementation
@@ -371,6 +457,7 @@ where
 		max_amount: BalanceOf<T>,
 		para_id: ParaId,
 		existence_requirement: ExistenceRequirement,
+		payment_type: PaymentType,
 	) -> DispatchResult {
 		let config = configuration::ActiveConfig::<T>::get();
 
@@ -391,22 +478,39 @@ where
 				Error::<T>::QueueFull
 			);
 
-			// Charge the sending account the spot price. The amount will be teleported to the
-			// broker chain once it requests revenue information.
-			let amt = T::Currency::withdraw(
-				&sender,
-				spot_price,
-				WithdrawReasons::FEE,
-				existence_requirement,
-			)?;
-
-			// Consume the negative imbalance and deposit it into the pallet account. Make sure the
-			// account preserves even without the existential deposit.
-			let pot = Self::account_id();
-			if !System::<T>::account_exists(&pot) {
-				System::<T>::inc_providers(&pot);
+			match payment_type {
+				PaymentType::Balance => {
+					// Charge the sending account the spot price. The amount will be teleported to
+					// the broker chain once it requests revenue information.
+					let amt = T::Currency::withdraw(
+						&sender,
+						spot_price,
+						WithdrawReasons::FEE,
+						existence_requirement,
+					)?;
+
+					// Consume the negative imbalance and deposit it into the pallet account. Make
+					// sure the account preserves even without the existential deposit.
+					let pot = Self::account_id();
+					if !System::<T>::account_exists(&pot) {
+						System::<T>::inc_providers(&pot);
+					}
+					T::Currency::resolve_creating(&pot, amt);
+				},
+				PaymentType::Credits => {
+					let credits = Credits::<T>::get(&sender);
+
+					// Charge the sending account the spot price in credits.
+					let new_credits_value =
+						credits.checked_sub(&spot_price).ok_or(Error::<T>::InsufficientCredits)?;
+
+					if new_credits_value.is_zero() {
+						Credits::<T>::remove(&sender);
+					} else {
+						Credits::<T>::insert(&sender, new_credits_value);
+					}
+				},
 			}
-			T::Currency::resolve_creating(&pot, amt);
 
 			// Add the amount to the current block's (index 0) revenue information.
 			Revenue::<T>::mutate(|bounded_revenue| {
@@ -619,7 +723,7 @@ where
 
 	/// Increases the affinity of a `ParaId` to a specified `CoreIndex`.
 	/// Adds to the count of the `CoreAffinityCount` if an entry is found and the core_index
-	/// matches. A non-existent entry will be initialized with a count of 1 and uses the  supplied
+	/// matches. A non-existent entry will be initialized with a count of 1 and uses the supplied
 	/// `CoreIndex`.
 	fn increase_affinity(para_id: ParaId, core_index: CoreIndex) {
 		ParaIdAffinity::<T>::mutate(para_id, |maybe_affinity| match maybe_affinity {
diff --git a/polkadot/runtime/parachains/src/on_demand/tests.rs b/polkadot/runtime/parachains/src/on_demand/tests.rs
index 7da16942c7ad6989a2188fe749c947141f073ee0..a435598d8f5559c736973d973a7876ed41f25cbf 100644
--- a/polkadot/runtime/parachains/src/on_demand/tests.rs
+++ b/polkadot/runtime/parachains/src/on_demand/tests.rs
@@ -98,6 +98,7 @@ fn place_order_run_to_blocknumber(para_id: ParaId, blocknumber: Option<BlockNumb
 	if let Some(bn) = blocknumber {
 		run_to_block(bn, |n| if n == bn { Some(Default::default()) } else { None });
 	}
+	#[allow(deprecated)]
 	OnDemand::place_order_allow_death(RuntimeOrigin::signed(alice), amt, para_id).unwrap()
 }
 
@@ -266,6 +267,7 @@ fn spot_traffic_decreases_between_idle_blocks() {
 }
 
 #[test]
+#[allow(deprecated)]
 fn place_order_works() {
 	let alice = 1u64;
 	let amt = 10_000_000u128;
@@ -308,6 +310,7 @@ fn place_order_works() {
 }
 
 #[test]
+#[allow(deprecated)]
 fn place_order_keep_alive_keeps_alive() {
 	let alice = 1u64;
 	let amt = 1u128; // The same as crate::mock's EXISTENTIAL_DEPOSIT
@@ -315,6 +318,8 @@ fn place_order_keep_alive_keeps_alive() {
 	let para_id = ParaId::from(111);
 
 	new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
+		let config = configuration::ActiveConfig::<Test>::get();
+
 		// Initialize the parathread and wait for it to be ready.
 		schedule_blank_para(para_id, ParaKind::Parathread);
 		Balances::make_free_balance_be(&alice, amt);
@@ -327,6 +332,71 @@ fn place_order_keep_alive_keeps_alive() {
 			OnDemand::place_order_keep_alive(RuntimeOrigin::signed(alice), max_amt, para_id),
 			BalancesError::<Test, _>::InsufficientBalance
 		);
+
+		Balances::make_free_balance_be(&alice, max_amt);
+		assert_ok!(OnDemand::place_order_keep_alive(
+			RuntimeOrigin::signed(alice),
+			max_amt,
+			para_id
+		),);
+
+		let queue_status = QueueStatus::<Test>::get();
+		let spot_price = queue_status.traffic.saturating_mul_int(
+			config.scheduler_params.on_demand_base_fee.saturated_into::<BalanceOf<Test>>(),
+		);
+		assert_eq!(Balances::free_balance(&alice), max_amt.saturating_sub(spot_price));
+		assert_eq!(
+			FreeEntries::<Test>::get().pop(),
+			Some(EnqueuedOrder::new(QueueIndex(0), para_id))
+		);
+	});
+}
+
+#[test]
+fn place_order_with_credits() {
+	let alice = 1u64;
+	let initial_credit = 10_000_000u128;
+	let para_id = ParaId::from(111);
+
+	new_test_ext(GenesisConfigBuilder::default().build()).execute_with(|| {
+		let config = configuration::ActiveConfig::<Test>::get();
+
+		// Initialize the parathread and wait for it to be ready.
+		schedule_blank_para(para_id, ParaKind::Parathread);
+		OnDemand::credit_account(alice, initial_credit);
+		assert_eq!(Credits::<Test>::get(alice), initial_credit);
+
+		assert!(!Paras::is_parathread(para_id));
+		run_to_block(100, |n| if n == 100 { Some(Default::default()) } else { None });
+		assert!(Paras::is_parathread(para_id));
+
+		let queue_status = QueueStatus::<Test>::get();
+		let spot_price = queue_status.traffic.saturating_mul_int(
+			config.scheduler_params.on_demand_base_fee.saturated_into::<BalanceOf<Test>>(),
+		);
+
+		// Create an order and pay for it with credits.
+		assert_ok!(OnDemand::place_order_with_credits(
+			RuntimeOrigin::signed(alice),
+			initial_credit,
+			para_id
+		));
+		assert_eq!(Credits::<Test>::get(alice), initial_credit.saturating_sub(spot_price));
+		assert_eq!(
+			FreeEntries::<Test>::get().pop(),
+			Some(EnqueuedOrder::new(QueueIndex(0), para_id))
+		);
+
+		// Insufficient credits:
+		Credits::<Test>::insert(alice, 1u128);
+		assert_noop!(
+			OnDemand::place_order_with_credits(
+				RuntimeOrigin::signed(alice),
+				1_000_000u128,
+				para_id
+			),
+			Error::<Test>::InsufficientCredits
+		);
 	});
 }
 
diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs
index f6729dd976257b506976264633387a0de944eb76..6195a9e356d47c304dce7441c393d174f6c2514c 100644
--- a/polkadot/runtime/rococo/src/lib.rs
+++ b/polkadot/runtime/rococo/src/lib.rs
@@ -1130,7 +1130,6 @@ impl Get<InteriorLocation> for BrokerPot {
 impl coretime::Config for Runtime {
 	type RuntimeOrigin = RuntimeOrigin;
 	type RuntimeEvent = RuntimeEvent;
-	type Currency = Balances;
 	type BrokerId = BrokerId;
 	type BrokerPotLocation = BrokerPot;
 	type WeightInfo = weights::polkadot_runtime_parachains_coretime::WeightInfo<Runtime>;
diff --git a/polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_coretime.rs b/polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_coretime.rs
index b2329c098cead5d2780b6ac44a1d6b5963efcaf8..94dc7a4e0750862c27e0a7dd64a47280364825ac 100644
--- a/polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_coretime.rs
+++ b/polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_coretime.rs
@@ -86,6 +86,22 @@ impl<T: frame_system::Config> polkadot_runtime_parachains::coretime::WeightInfo
 			.saturating_add(T::DbWeight::get().reads(3))
 			.saturating_add(T::DbWeight::get().writes(1))
 	}
+	/// Storage: `Configuration::PendingConfigs` (r:1 w:1)
+	/// Proof: `Configuration::PendingConfigs` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
+	/// Storage: `Configuration::BypassConsistencyCheck` (r:1 w:0)
+	/// Proof: `Configuration::BypassConsistencyCheck` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
+	/// Storage: `ParasShared::CurrentSessionIndex` (r:1 w:0)
+	/// Proof: `ParasShared::CurrentSessionIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
+	fn credit_account() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `151`
+		//  Estimated: `1636`
+		// Minimum execution time: 7_519_000 picoseconds.
+		Weight::from_parts(7_803_000, 0)
+			.saturating_add(Weight::from_parts(0, 1636))
+			.saturating_add(T::DbWeight::get().reads(3))
+			.saturating_add(T::DbWeight::get().writes(1))
+	}
 	/// Storage: `CoretimeAssignmentProvider::CoreDescriptors` (r:1 w:1)
 	/// Proof: `CoretimeAssignmentProvider::CoreDescriptors` (`max_values`: None, `max_size`: None, mode: `Measured`)
 	/// Storage: `CoretimeAssignmentProvider::CoreSchedules` (r:0 w:1)
diff --git a/polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_on_demand.rs b/polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_on_demand.rs
index 1dd62d129f9a0c5e06a67e5b949ab21decc0d260..f251ad5f6b86b9d304c84b687b7190e0881510ff 100644
--- a/polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_on_demand.rs
+++ b/polkadot/runtime/rococo/src/weights/polkadot_runtime_parachains_on_demand.rs
@@ -102,4 +102,28 @@ impl<T: frame_system::Config> polkadot_runtime_parachains::on_demand::WeightInfo
 			.saturating_add(T::DbWeight::get().writes(3))
 			.saturating_add(Weight::from_parts(0, 8).saturating_mul(s.into()))
 	}
+	/// Storage: `OnDemandAssignmentProvider::QueueStatus` (r:1 w:1)
+	/// Proof: `OnDemandAssignmentProvider::QueueStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
+	/// Storage: `System::Account` (r:1 w:0)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	/// Storage: `OnDemandAssignmentProvider::Revenue` (r:1 w:1)
+	/// Proof: `OnDemandAssignmentProvider::Revenue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
+	/// Storage: `OnDemandAssignmentProvider::ParaIdAffinity` (r:1 w:0)
+	/// Proof: `OnDemandAssignmentProvider::ParaIdAffinity` (`max_values`: None, `max_size`: None, mode: `Measured`)
+	/// Storage: `OnDemandAssignmentProvider::FreeEntries` (r:1 w:1)
+	/// Proof: `OnDemandAssignmentProvider::FreeEntries` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
+	/// The range of component `s` is `[1, 9999]`.
+	fn place_order_with_credits(s: u32, ) -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `270 + s * (8 ±0)`
+		//  Estimated: `3733 + s * (8 ±0)`
+		// Minimum execution time: 28_422_000 picoseconds.
+		Weight::from_parts(28_146_882, 0)
+			.saturating_add(Weight::from_parts(0, 3733))
+			// Standard Error: 140
+			.saturating_add(Weight::from_parts(21_283, 0).saturating_mul(s.into()))
+			.saturating_add(T::DbWeight::get().reads(5))
+			.saturating_add(T::DbWeight::get().writes(3))
+			.saturating_add(Weight::from_parts(0, 8).saturating_mul(s.into()))
+	}
 }
diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs
index 1a19b637b798afbd03ac2cae5694407fdf05edb2..f592dc2b61df05db85ec98c22de6976ea0fa96d8 100644
--- a/polkadot/runtime/test-runtime/src/lib.rs
+++ b/polkadot/runtime/test-runtime/src/lib.rs
@@ -659,7 +659,6 @@ impl SendXcm for DummyXcmSender {
 impl coretime::Config for Runtime {
 	type RuntimeOrigin = RuntimeOrigin;
 	type RuntimeEvent = RuntimeEvent;
-	type Currency = pallet_balances::Pallet<Runtime>;
 	type BrokerId = BrokerId;
 	type WeightInfo = crate::coretime::TestWeightInfo;
 	type SendXcm = DummyXcmSender;
diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs
index 7fd2ac53530ac982040bacc6e53bd4ded6fb1748..11788c0193e49571c3ecee90aa5b0401fb241924 100644
--- a/polkadot/runtime/westend/src/lib.rs
+++ b/polkadot/runtime/westend/src/lib.rs
@@ -1356,7 +1356,6 @@ impl Get<InteriorLocation> for BrokerPot {
 impl coretime::Config for Runtime {
 	type RuntimeOrigin = RuntimeOrigin;
 	type RuntimeEvent = RuntimeEvent;
-	type Currency = Balances;
 	type BrokerId = BrokerId;
 	type BrokerPotLocation = BrokerPot;
 	type WeightInfo = weights::polkadot_runtime_parachains_coretime::WeightInfo<Runtime>;
diff --git a/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_coretime.rs b/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_coretime.rs
index 9df382875f5f12ddee62751eeca5feb16dfece41..a36fefb704deb225cc7dc02b00533f8541444263 100644
--- a/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_coretime.rs
+++ b/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_coretime.rs
@@ -86,6 +86,22 @@ impl<T: frame_system::Config> polkadot_runtime_parachains::coretime::WeightInfo
 			.saturating_add(T::DbWeight::get().reads(3))
 			.saturating_add(T::DbWeight::get().writes(1))
 	}
+	/// Storage: `Configuration::PendingConfigs` (r:1 w:1)
+	/// Proof: `Configuration::PendingConfigs` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
+	/// Storage: `Configuration::BypassConsistencyCheck` (r:1 w:0)
+	/// Proof: `Configuration::BypassConsistencyCheck` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
+	/// Storage: `ParasShared::CurrentSessionIndex` (r:1 w:0)
+	/// Proof: `ParasShared::CurrentSessionIndex` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
+	fn credit_account() -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `151`
+		//  Estimated: `1636`
+		// Minimum execution time: 7_519_000 picoseconds.
+		Weight::from_parts(7_803_000, 0)
+			.saturating_add(Weight::from_parts(0, 1636))
+			.saturating_add(T::DbWeight::get().reads(3))
+			.saturating_add(T::DbWeight::get().writes(1))
+	}
 	/// Storage: `CoretimeAssignmentProvider::CoreDescriptors` (r:1 w:1)
 	/// Proof: `CoretimeAssignmentProvider::CoreDescriptors` (`max_values`: None, `max_size`: None, mode: `Measured`)
 	/// Storage: `CoretimeAssignmentProvider::CoreSchedules` (r:0 w:1)
diff --git a/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_on_demand.rs b/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_on_demand.rs
index fc7efa6edfcf361f51892ef809721d3c34e915dd..2e84319d0b628facd5b3383908214017a07a2603 100644
--- a/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_on_demand.rs
+++ b/polkadot/runtime/westend/src/weights/polkadot_runtime_parachains_on_demand.rs
@@ -96,4 +96,28 @@ impl<T: frame_system::Config> polkadot_runtime_parachains::on_demand::WeightInfo
 			.saturating_add(T::DbWeight::get().writes(3))
 			.saturating_add(Weight::from_parts(0, 8).saturating_mul(s.into()))
 	}
+	/// Storage: `OnDemandAssignmentProvider::QueueStatus` (r:1 w:1)
+	/// Proof: `OnDemandAssignmentProvider::QueueStatus` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
+	/// Storage: `System::Account` (r:1 w:0)
+	/// Proof: `System::Account` (`max_values`: None, `max_size`: Some(128), added: 2603, mode: `MaxEncodedLen`)
+	/// Storage: `OnDemandAssignmentProvider::Revenue` (r:1 w:1)
+	/// Proof: `OnDemandAssignmentProvider::Revenue` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
+	/// Storage: `OnDemandAssignmentProvider::ParaIdAffinity` (r:1 w:0)
+	/// Proof: `OnDemandAssignmentProvider::ParaIdAffinity` (`max_values`: None, `max_size`: None, mode: `Measured`)
+	/// Storage: `OnDemandAssignmentProvider::FreeEntries` (r:1 w:1)
+	/// Proof: `OnDemandAssignmentProvider::FreeEntries` (`max_values`: Some(1), `max_size`: None, mode: `Measured`)
+	/// The range of component `s` is `[1, 9999]`.
+	fn place_order_with_credits(s: u32, ) -> Weight {
+		// Proof Size summary in bytes:
+		//  Measured:  `270 + s * (8 ±0)`
+		//  Estimated: `3733 + s * (8 ±0)`
+		// Minimum execution time: 28_422_000 picoseconds.
+		Weight::from_parts(28_146_882, 0)
+			.saturating_add(Weight::from_parts(0, 3733))
+			// Standard Error: 140
+			.saturating_add(Weight::from_parts(21_283, 0).saturating_mul(s.into()))
+			.saturating_add(T::DbWeight::get().reads(5))
+			.saturating_add(T::DbWeight::get().writes(3))
+			.saturating_add(Weight::from_parts(0, 8).saturating_mul(s.into()))
+	}
 }
diff --git a/polkadot/zombienet-sdk-tests/tests/smoke/coretime_revenue.rs b/polkadot/zombienet-sdk-tests/tests/smoke/coretime_revenue.rs
index 59a71a83e01ecf91bbad9b5358caa0bc08f0cd0e..daa65c81d8003e01574672d86dd825ff645bffb7 100644
--- a/polkadot/zombienet-sdk-tests/tests/smoke/coretime_revenue.rs
+++ b/polkadot/zombienet-sdk-tests/tests/smoke/coretime_revenue.rs
@@ -14,6 +14,9 @@ use anyhow::anyhow;
 #[subxt::subxt(runtime_metadata_path = "metadata-files/coretime-rococo-local.scale")]
 mod coretime_rococo {}
 
+#[subxt::subxt(runtime_metadata_path = "metadata-files/rococo-local.scale")]
+mod rococo {}
+
 use crate::helpers::rococo::{
 	self as rococo_api,
 	runtime_types::{
@@ -43,6 +46,7 @@ use coretime_rococo::{
 		sp_arithmetic::per_things::Perbill,
 	},
 };
+use rococo::on_demand_assignment_provider::events as on_demand_events;
 
 type CoretimeRuntimeCall = coretime_api::runtime_types::coretime_rococo_runtime::RuntimeCall;
 type CoretimeUtilityCall = coretime_api::runtime_types::pallet_utility::pallet::Call;
@@ -87,7 +91,7 @@ async fn assert_total_issuance(
 	assert_eq!(ti, actual_ti);
 }
 
-type ParaEvents<C> = Arc<RwLock<Vec<(u64, subxt::events::EventDetails<C>)>>>;
+type EventOf<C> = Arc<RwLock<Vec<(u64, subxt::events::EventDetails<C>)>>>;
 
 macro_rules! trace_event {
 	($event:ident : $mod:ident => $($ev:ident),*) => {
@@ -101,7 +105,7 @@ macro_rules! trace_event {
 	};
 }
 
-async fn para_watcher<C: subxt::Config + Clone>(api: OnlineClient<C>, events: ParaEvents<C>)
+async fn para_watcher<C: subxt::Config + Clone>(api: OnlineClient<C>, events: EventOf<C>)
 where
 	<C::Header as subxt::config::Header>::Number: Display,
 {
@@ -129,8 +133,35 @@ where
 	}
 }
 
-async fn wait_for_para_event<C: subxt::Config + Clone, E: StaticEvent, P: Fn(&E) -> bool + Copy>(
-	events: ParaEvents<C>,
+async fn relay_watcher<C: subxt::Config + Clone>(api: OnlineClient<C>, events: EventOf<C>)
+where
+	<C::Header as subxt::config::Header>::Number: Display,
+{
+	let mut blocks_sub = api.blocks().subscribe_finalized().await.unwrap();
+
+	log::debug!("Starting parachain watcher");
+	while let Some(block) = blocks_sub.next().await {
+		let block = block.unwrap();
+		log::debug!("Finalized parachain block {}", block.number());
+
+		for event in block.events().await.unwrap().iter() {
+			let event = event.unwrap();
+			log::debug!("Got event: {} :: {}", event.pallet_name(), event.variant_name());
+			{
+				events.write().await.push((block.number().into(), event.clone()));
+			}
+
+			if event.pallet_name() == "OnDemandAssignmentProvider" {
+				trace_event!(event: on_demand_events =>
+					AccountCredited, SpotPriceSet, OnDemandOrderPlaced
+				);
+			}
+		}
+	}
+}
+
+async fn wait_for_event<C: subxt::Config + Clone, E: StaticEvent, P: Fn(&E) -> bool + Copy>(
+	events: EventOf<C>,
 	pallet: &'static str,
 	variant: &'static str,
 	predicate: P,
@@ -230,14 +261,22 @@ async fn coretime_revenue_test() -> Result<(), anyhow::Error> {
 
 	let bob = dev::bob();
 
-	let para_events: ParaEvents<PolkadotConfig> = Arc::new(RwLock::new(Vec::new()));
+	let para_events: EventOf<PolkadotConfig> = Arc::new(RwLock::new(Vec::new()));
 	let p_api = para_node.wait_client().await?;
 	let p_events = para_events.clone();
 
-	let _subscriber = tokio::spawn(async move {
+	let _subscriber1 = tokio::spawn(async move {
 		para_watcher(p_api, p_events).await;
 	});
 
+	let relay_events: EventOf<PolkadotConfig> = Arc::new(RwLock::new(Vec::new()));
+	let r_api = relay_node.wait_client().await?;
+	let r_events = relay_events.clone();
+
+	let _subscriber2 = tokio::spawn(async move {
+		relay_watcher(r_api, r_events).await;
+	});
+
 	let api: OnlineClient<PolkadotConfig> = para_node.wait_client().await?;
 	let _s1 = tokio::spawn(async move {
 		ti_watcher(api, "PARA").await;
@@ -276,7 +315,7 @@ async fn coretime_revenue_test() -> Result<(), anyhow::Error> {
 		)
 		.await?;
 
-	wait_for_para_event(
+	wait_for_event(
 		para_events.clone(),
 		"Balances",
 		"Minted",
@@ -328,16 +367,16 @@ async fn coretime_revenue_test() -> Result<(), anyhow::Error> {
 
 	log::info!("Waiting for a full-length sale to begin");
 
-	// Skip the first sale completeley as it may be a short one. Also, `request_code_count` requires
+	// Skip the first sale completeley as it may be a short one. Also, `request_core_count` requires
 	// two session boundaries to propagate. Given that the `fast-runtime` session is 10 blocks and
 	// the timeslice is 20 blocks, we should be just in time.
 
 	let _: coretime_api::broker::events::SaleInitialized =
-		wait_for_para_event(para_events.clone(), "Broker", "SaleInitialized", |_| true).await;
+		wait_for_event(para_events.clone(), "Broker", "SaleInitialized", |_| true).await;
 	log::info!("Skipped short sale");
 
 	let sale: coretime_api::broker::events::SaleInitialized =
-		wait_for_para_event(para_events.clone(), "Broker", "SaleInitialized", |_| true).await;
+		wait_for_event(para_events.clone(), "Broker", "SaleInitialized", |_| true).await;
 	log::info!("{:?}", sale);
 
 	// Alice buys a region
@@ -349,7 +388,7 @@ async fn coretime_revenue_test() -> Result<(), anyhow::Error> {
 		.sign_and_submit_default(&coretime_api::tx().broker().purchase(1_000_000_000), &alice)
 		.await?;
 
-	let purchase = wait_for_para_event(
+	let purchase = wait_for_event(
 		para_events.clone(),
 		"Broker",
 		"Purchased",
@@ -381,19 +420,17 @@ async fn coretime_revenue_test() -> Result<(), anyhow::Error> {
 		)
 		.await?;
 
-	let pooled = wait_for_para_event(
-		para_events.clone(),
-		"Broker",
-		"Pooled",
-		|e: &broker_events::Pooled| e.region_id.begin == region_begin,
-	)
-	.await;
+	let pooled =
+		wait_for_event(para_events.clone(), "Broker", "Pooled", |e: &broker_events::Pooled| {
+			e.region_id.begin == region_begin
+		})
+		.await;
 
 	// Wait until the beginning of the timeslice where the region belongs to
 
 	log::info!("Waiting for the region to begin");
 
-	let hist = wait_for_para_event(
+	let hist = wait_for_event(
 		para_events.clone(),
 		"Broker",
 		"HistoryInitialized",
@@ -443,7 +480,7 @@ async fn coretime_revenue_test() -> Result<(), anyhow::Error> {
 
 	log::info!("Waiting for Alice's revenue to be ready to claim");
 
-	let claims_ready = wait_for_para_event(
+	let claims_ready = wait_for_event(
 		para_events.clone(),
 		"Broker",
 		"ClaimsReady",
@@ -460,6 +497,54 @@ async fn coretime_revenue_test() -> Result<(), anyhow::Error> {
 
 	assert_total_issuance(relay_client.clone(), para_client.clone(), total_issuance).await;
 
+	// Try purchasing on-demand with credits:
+
+	log::info!("Bob is going to buy on-demand credits for alice");
+
+	let r = para_client
+		.tx()
+		.sign_and_submit_then_watch_default(
+			&coretime_api::tx().broker().purchase_credit(100_000_000, alice_acc.clone()),
+			&bob,
+		)
+		.await?
+		.wait_for_finalized_success()
+		.await?;
+
+	assert!(r.find_first::<coretime_api::broker::events::CreditPurchased>()?.is_some());
+
+	let _account_credited = wait_for_event(
+		relay_events.clone(),
+		"OnDemandAssignmentProvider",
+		"AccountCredited",
+		|e: &on_demand_events::AccountCredited| e.who == alice_acc && e.amount == 100_000_000,
+	)
+	.await;
+
+	// Once the account is credit we can place an on-demand order using credits
+	log::info!("Alice is going to place an on-demand order using credits");
+
+	let r = relay_client
+		.tx()
+		.sign_and_submit_then_watch_default(
+			&rococo_api::tx()
+				.on_demand_assignment_provider()
+				.place_order_with_credits(100_000_000, primitives::Id(100)),
+			&alice,
+		)
+		.await?
+		.wait_for_finalized_success()
+		.await?;
+
+	let order = r
+		.find_first::<rococo_api::on_demand_assignment_provider::events::OnDemandOrderPlaced>()?
+		.unwrap();
+
+	assert_eq!(order.spot_price, ON_DEMAND_BASE_FEE);
+
+	// NOTE: Purchasing on-demand with credits doesn't affect the total issuance, as the credits are
+	// purchased on the PC. Therefore we don't check for total issuance changes.
+
 	// Alice claims her revenue
 
 	log::info!("Alice is going to claim her revenue");
@@ -472,7 +557,7 @@ async fn coretime_revenue_test() -> Result<(), anyhow::Error> {
 		)
 		.await?;
 
-	let claim_paid = wait_for_para_event(
+	let claim_paid = wait_for_event(
 		para_events.clone(),
 		"Broker",
 		"RevenueClaimPaid",
@@ -490,16 +575,18 @@ async fn coretime_revenue_test() -> Result<(), anyhow::Error> {
 	// between.
 
 	let _: coretime_api::broker::events::SaleInitialized =
-		wait_for_para_event(para_events.clone(), "Broker", "SaleInitialized", |_| true).await;
+		wait_for_event(para_events.clone(), "Broker", "SaleInitialized", |_| true).await;
 
 	total_issuance.0 -= ON_DEMAND_BASE_FEE / 2;
 	total_issuance.1 -= ON_DEMAND_BASE_FEE / 2;
 
 	let _: coretime_api::broker::events::SaleInitialized =
-		wait_for_para_event(para_events.clone(), "Broker", "SaleInitialized", |_| true).await;
+		wait_for_event(para_events.clone(), "Broker", "SaleInitialized", |_| true).await;
 
 	assert_total_issuance(relay_client.clone(), para_client.clone(), total_issuance).await;
 
+	assert_eq!(order.spot_price, ON_DEMAND_BASE_FEE);
+
 	log::info!("Test finished successfully");
 
 	Ok(())
diff --git a/prdoc/pr_5990.prdoc b/prdoc/pr_5990.prdoc
new file mode 100644
index 0000000000000000000000000000000000000000..ee13ad634dcf514b67d4fe80399e18cd033f8c10
--- /dev/null
+++ b/prdoc/pr_5990.prdoc
@@ -0,0 +1,30 @@
+# Schema: Polkadot SDK PRDoc Schema (prdoc) v1.0.0
+# See doc at https://raw.githubusercontent.com/paritytech/polkadot-sdk/master/prdoc/schema_user.json
+
+title: On-demand credits
+
+doc:
+  - audience: [ Runtime User, Runtime Dev ]
+    description: |
+      The PR implements functionality on the relay chain for purchasing on-demand 
+      Coretime using credits. This means on-demand Coretime should no longer be 
+      purchased with the relay chain balance but rather with credits acquired 
+      on the Coretime chain. The extrinsic to use for purchasing Coretime is 
+      `place_order_with_credits`. It is worth noting that the PR also introduces
+      a minimum credit purchase requirement to prevent potential attacks.
+
+crates:
+  - name: pallet-broker
+    bump: major 
+  - name: polkadot-runtime-parachains
+    bump: major 
+  - name: rococo-runtime
+    bump: patch
+  - name: westend-runtime
+    bump: patch
+  - name: polkadot-test-runtime
+    bump: patch
+  - name: coretime-rococo-runtime
+    bump: major 
+  - name: coretime-westend-runtime
+    bump: major 
diff --git a/substrate/bin/node/runtime/src/lib.rs b/substrate/bin/node/runtime/src/lib.rs
index 795e80e6746cb789c1c0dc524eba132b225c0cbc..6e847a7dc655557e445a17644e08018ac10aa267 100644
--- a/substrate/bin/node/runtime/src/lib.rs
+++ b/substrate/bin/node/runtime/src/lib.rs
@@ -2316,6 +2316,7 @@ impl pallet_migrations::Config for Runtime {
 
 parameter_types! {
 	pub const BrokerPalletId: PalletId = PalletId(*b"py/broke");
+	pub const MinimumCreditPurchase: Balance =  100 * MILLICENTS;
 }
 
 pub struct IntoAuthor;
@@ -2368,6 +2369,7 @@ impl pallet_broker::Config for Runtime {
 	type SovereignAccountOf = SovereignAccountOf;
 	type MaxAutoRenewals = ConstU32<10>;
 	type PriceAdapter = pallet_broker::CenterTargetPrice<Balance>;
+	type MinimumCreditPurchase = MinimumCreditPurchase;
 }
 
 parameter_types! {
diff --git a/substrate/frame/broker/src/benchmarking.rs b/substrate/frame/broker/src/benchmarking.rs
index 516518740f7d03db946e8aec1f33595f8b894923..49003afcdd8bc357f0e4f1fe469167b91561ad69 100644
--- a/substrate/frame/broker/src/benchmarking.rs
+++ b/substrate/frame/broker/src/benchmarking.rs
@@ -543,26 +543,22 @@ mod benches {
 		let caller: T::AccountId = whitelisted_caller();
 		T::Currency::set_balance(
 			&caller.clone(),
-			T::Currency::minimum_balance().saturating_add(30_000_000u32.into()),
+			T::Currency::minimum_balance().saturating_add(T::MinimumCreditPurchase::get()),
 		);
 		T::Currency::set_balance(&Broker::<T>::account_id(), T::Currency::minimum_balance());
 
-		let region = Broker::<T>::do_purchase(caller.clone(), 10_000_000u32.into())
-			.expect("Offer not high enough for configuration.");
-
-		let recipient: T::AccountId = account("recipient", 0, SEED);
-
-		Broker::<T>::do_pool(region, None, recipient, Final)
-			.map_err(|_| BenchmarkError::Weightless)?;
-
 		let beneficiary: RelayAccountIdOf<T> = account("beneficiary", 0, SEED);
 
 		#[extrinsic_call]
-		_(RawOrigin::Signed(caller.clone()), 20_000_000u32.into(), beneficiary.clone());
+		_(RawOrigin::Signed(caller.clone()), T::MinimumCreditPurchase::get(), beneficiary.clone());
 
 		assert_last_event::<T>(
-			Event::CreditPurchased { who: caller, beneficiary, amount: 20_000_000u32.into() }
-				.into(),
+			Event::CreditPurchased {
+				who: caller,
+				beneficiary,
+				amount: T::MinimumCreditPurchase::get(),
+			}
+			.into(),
 		);
 
 		Ok(())
diff --git a/substrate/frame/broker/src/dispatchable_impls.rs b/substrate/frame/broker/src/dispatchable_impls.rs
index 489be12bdd1545d5deb731ca2b29ee9549f8fd1d..77bbf0878b4fa0f3a454d08d419c9fcc9f33fab5 100644
--- a/substrate/frame/broker/src/dispatchable_impls.rs
+++ b/substrate/frame/broker/src/dispatchable_impls.rs
@@ -426,6 +426,7 @@ impl<T: Config> Pallet<T> {
 		amount: BalanceOf<T>,
 		beneficiary: RelayAccountIdOf<T>,
 	) -> DispatchResult {
+		ensure!(amount >= T::MinimumCreditPurchase::get(), Error::<T>::CreditPurchaseTooSmall);
 		T::Currency::transfer(&who, &Self::account_id(), amount, Expendable)?;
 		let rc_amount = T::ConvertBalance::convert(amount);
 		T::Coretime::credit_account(beneficiary.clone(), rc_amount);
diff --git a/substrate/frame/broker/src/lib.rs b/substrate/frame/broker/src/lib.rs
index 01368fd6404da182f1054a9b29c80410afd96e5d..f605815a421cac6668b7ce9052a3f8353db100e7 100644
--- a/substrate/frame/broker/src/lib.rs
+++ b/substrate/frame/broker/src/lib.rs
@@ -121,8 +121,15 @@ pub mod pallet {
 		#[pallet::constant]
 		type MaxReservedCores: Get<u32>;
 
+		/// Given that we are performing all auto-renewals in a single block, it has to be limited.
 		#[pallet::constant]
 		type MaxAutoRenewals: Get<u32>;
+
+		/// The smallest amount of credits a user can purchase.
+		///
+		/// Needed to prevent spam attacks.
+		#[pallet::constant]
+		type MinimumCreditPurchase: Get<BalanceOf<Self>>;
 	}
 
 	/// The current configuration of this pallet.
@@ -544,6 +551,9 @@ pub mod pallet {
 		SovereignAccountNotFound,
 		/// Attempted to disable auto-renewal for a core that didn't have it enabled.
 		AutoRenewalNotEnabled,
+		/// Needed to prevent spam attacks.The amount of credits the user attempted to purchase is
+		/// below `T::MinimumCreditPurchase`.
+		CreditPurchaseTooSmall,
 	}
 
 	#[derive(frame_support::DefaultNoBound)]
diff --git a/substrate/frame/broker/src/mock.rs b/substrate/frame/broker/src/mock.rs
index 42377eefdb22ef60c2225ffd8a9b889d40b7c8d4..40233a22edfc9777ea12824fae20eb17974e3546 100644
--- a/substrate/frame/broker/src/mock.rs
+++ b/substrate/frame/broker/src/mock.rs
@@ -177,6 +177,7 @@ impl OnUnbalanced<Credit<u64, <Test as Config>::Currency>> for IntoZero {
 
 ord_parameter_types! {
 	pub const One: u64 = 1;
+	pub const MinimumCreditPurchase: u64 = 50;
 }
 type EnsureOneOrRoot = EitherOfDiverse<EnsureRoot<u64>, EnsureSignedBy<One, u64>>;
 
@@ -203,6 +204,7 @@ impl crate::Config for Test {
 	type SovereignAccountOf = SovereignAccountOf;
 	type MaxAutoRenewals = ConstU32<3>;
 	type PriceAdapter = CenterTargetPrice<BalanceOf<Self>>;
+	type MinimumCreditPurchase = MinimumCreditPurchase;
 }
 
 pub fn advance_to(b: u64) {
diff --git a/substrate/frame/broker/src/tests.rs b/substrate/frame/broker/src/tests.rs
index a130a2050d9a1aba164e586890a50e98c714cc02..984650aac08edeadf3c9eaead83b56d991733865 100644
--- a/substrate/frame/broker/src/tests.rs
+++ b/substrate/frame/broker/src/tests.rs
@@ -113,7 +113,7 @@ fn drop_history_works() {
 	TestExt::new()
 		.contribution_timeout(4)
 		.endow(1, 1000)
-		.endow(2, 30)
+		.endow(2, 50)
 		.execute_with(|| {
 			assert_ok!(Broker::do_start_sales(100, 1));
 			advance_to(2);
@@ -121,7 +121,7 @@ fn drop_history_works() {
 			// Place region in pool. Active in pool timeslices 4, 5, 6 = rcblocks 8, 10, 12; we
 			// expect to make/receive revenue reports on blocks 10, 12, 14.
 			assert_ok!(Broker::do_pool(region, Some(1), 1, Final));
-			assert_ok!(Broker::do_purchase_credit(2, 30, 2));
+			assert_ok!(Broker::do_purchase_credit(2, 50, 2));
 			advance_to(6);
 			// In the stable state with no pending payouts, we expect to see 3 items in
 			// InstaPoolHistory here since there is a latency of 1 timeslice (for generating the
@@ -694,6 +694,24 @@ fn purchase_works() {
 	});
 }
 
+#[test]
+fn purchase_credit_works() {
+	TestExt::new().endow(1, 50).execute_with(|| {
+		assert_ok!(Broker::do_start_sales(100, 1));
+		advance_to(2);
+
+		let credits = CoretimeCredit::get();
+		assert_eq!(credits.get(&1), None);
+
+		assert_noop!(Broker::do_purchase_credit(1, 10, 1), Error::<Test>::CreditPurchaseTooSmall);
+		assert_noop!(Broker::do_purchase_credit(1, 100, 1), TokenError::FundsUnavailable);
+
+		assert_ok!(Broker::do_purchase_credit(1, 50, 1));
+		let credits = CoretimeCredit::get();
+		assert_eq!(credits.get(&1), Some(&50));
+	});
+}
+
 #[test]
 fn partition_works() {
 	TestExt::new().endow(1, 1000).execute_with(|| {