diff --git a/Cargo.lock b/Cargo.lock
index ad59187b72b..2d882e0c93b 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -230,9 +230,9 @@ dependencies = [
 
 [[package]]
 name = "async-trait"
-version = "0.1.81"
+version = "0.1.83"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107"
+checksum = "721cae7de5c34fbb2acd27e21e6d2cf7b886dce0c27388d46c4e6c47ea4318dd"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -1499,6 +1499,7 @@ name = "drive"
 version = "1.4.0-dev.1"
 dependencies = [
  "arc-swap",
+ "assert_matches",
  "base64 0.22.1",
  "bincode",
  "bs58 0.5.1",
@@ -5102,18 +5103,18 @@ dependencies = [
 
 [[package]]
 name = "thiserror"
-version = "1.0.63"
+version = "1.0.64"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724"
+checksum = "d50af8abc119fb8bb6dbabcfa89656f46f84aa0ac7688088608076ad2b459a84"
 dependencies = [
  "thiserror-impl",
 ]
 
 [[package]]
 name = "thiserror-impl"
-version = "1.0.63"
+version = "1.0.64"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
+checksum = "08904e7672f5eb876eaaf87e0ce17857500934f4981c4a0ab2b4aa98baac7fc3"
 dependencies = [
  "proc-macro2",
  "quote",
@@ -5188,9 +5189,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
 
 [[package]]
 name = "tokio"
-version = "1.39.3"
+version = "1.40.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5"
+checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998"
 dependencies = [
  "backtrace",
  "bytes",
@@ -5284,9 +5285,9 @@ dependencies = [
 
 [[package]]
 name = "tokio-util"
-version = "0.7.11"
+version = "0.7.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1"
+checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a"
 dependencies = [
  "bytes",
  "futures-core",
diff --git a/packages/dashpay-contract/Cargo.toml b/packages/dashpay-contract/Cargo.toml
index cdb3ea808ee..c425b0738a0 100644
--- a/packages/dashpay-contract/Cargo.toml
+++ b/packages/dashpay-contract/Cargo.toml
@@ -8,6 +8,6 @@ license = "MIT"
 
 [dependencies]
 platform-version = { path = "../rs-platform-version" }
-thiserror = "1.0.58"
+thiserror = "1.0.64"
 serde_json = { version = "1.0" }
 platform-value = { path = "../rs-platform-value" }
diff --git a/packages/data-contracts/Cargo.toml b/packages/data-contracts/Cargo.toml
index 84cf2284a02..fb0fe7d640f 100644
--- a/packages/data-contracts/Cargo.toml
+++ b/packages/data-contracts/Cargo.toml
@@ -7,7 +7,7 @@ rust-version.workspace = true
 license = "MIT"
 
 [dependencies]
-thiserror = "1.0.58"
+thiserror = "1.0.64"
 platform-version = { path = "../rs-platform-version" }
 serde_json = { version = "1.0" }
 withdrawals-contract = { path = "../withdrawals-contract" }
diff --git a/packages/dpns-contract/Cargo.toml b/packages/dpns-contract/Cargo.toml
index 8b66395d80c..328a6fa2fa2 100644
--- a/packages/dpns-contract/Cargo.toml
+++ b/packages/dpns-contract/Cargo.toml
@@ -7,7 +7,7 @@ rust-version.workspace = true
 license = "MIT"
 
 [dependencies]
-thiserror = "1.0.58"
+thiserror = "1.0.64"
 platform-version = { path = "../rs-platform-version" }
 serde_json = { version = "1.0" }
 platform-value = { path = "../rs-platform-value" }
diff --git a/packages/feature-flags-contract/Cargo.toml b/packages/feature-flags-contract/Cargo.toml
index 2bfb4c00875..04abaeacfaf 100644
--- a/packages/feature-flags-contract/Cargo.toml
+++ b/packages/feature-flags-contract/Cargo.toml
@@ -7,7 +7,7 @@ rust-version.workspace = true
 license = "MIT"
 
 [dependencies]
-thiserror = "1.0.58"
+thiserror = "1.0.64"
 platform-version = { path = "../rs-platform-version" }
 serde_json = { version = "1.0" }
 platform-value = { path = "../rs-platform-value" }
diff --git a/packages/masternode-reward-shares-contract/Cargo.toml b/packages/masternode-reward-shares-contract/Cargo.toml
index 49c81c24dbb..8eb18072879 100644
--- a/packages/masternode-reward-shares-contract/Cargo.toml
+++ b/packages/masternode-reward-shares-contract/Cargo.toml
@@ -7,7 +7,7 @@ rust-version.workspace = true
 license = "MIT"
 
 [dependencies]
-thiserror = "1.0.58"
+thiserror = "1.0.64"
 platform-version = { path = "../rs-platform-version" }
 serde_json = { version = "1.0" }
 platform-value = { path = "../rs-platform-value" }
diff --git a/packages/rs-dapi-client/Cargo.toml b/packages/rs-dapi-client/Cargo.toml
index c95a2a458df..79beb87fd8e 100644
--- a/packages/rs-dapi-client/Cargo.toml
+++ b/packages/rs-dapi-client/Cargo.toml
@@ -25,7 +25,7 @@ dapi-grpc = { path = "../dapi-grpc" }
 futures = "0.3.28"
 http-serde = { version = "1.1.3", optional = true }
 rand = { version = "0.8.5", features = ["small_rng"] }
-thiserror = "1.0.58"
+thiserror = "1.0.64"
 tracing = "0.1.40"
 tokio = { version = "1.32.0", default-features = false }
 sha2 = { version = "0.10", optional = true }
diff --git a/packages/rs-dpp/src/errors/consensus/basic/identity/invalid_identity_credit_withdrawal_transition_amount_error.rs b/packages/rs-dpp/src/errors/consensus/basic/identity/invalid_identity_credit_withdrawal_transition_amount_error.rs
index 666c8ff2991..11b91aba7e7 100644
--- a/packages/rs-dpp/src/errors/consensus/basic/identity/invalid_identity_credit_withdrawal_transition_amount_error.rs
+++ b/packages/rs-dpp/src/errors/consensus/basic/identity/invalid_identity_credit_withdrawal_transition_amount_error.rs
@@ -10,7 +10,7 @@ use bincode::{Decode, Encode};
 #[derive(
     Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize,
 )]
-#[error("Credit withdrawal amount {amount} must be greater or equal to {min_amount}")]
+#[error("Credit withdrawal amount {amount} must be greater or equal to {min_amount} and less than {max_amount}")]
 #[platform_serialize(unversioned)]
 pub struct InvalidIdentityCreditWithdrawalTransitionAmountError {
     /*
@@ -20,11 +20,16 @@ pub struct InvalidIdentityCreditWithdrawalTransitionAmountError {
     */
     pub amount: u64,
     pub min_amount: u64,
+    pub max_amount: u64,
 }
 
 impl InvalidIdentityCreditWithdrawalTransitionAmountError {
-    pub fn new(amount: u64, min_amount: u64) -> Self {
-        Self { amount, min_amount }
+    pub fn new(amount: u64, min_amount: u64, max_amount: u64) -> Self {
+        Self {
+            amount,
+            min_amount,
+            max_amount,
+        }
     }
 
     pub fn amount(&self) -> u64 {
@@ -34,6 +39,10 @@ impl InvalidIdentityCreditWithdrawalTransitionAmountError {
     pub fn min_amount(&self) -> u64 {
         self.min_amount
     }
+
+    pub fn max_amount(&self) -> u64 {
+        self.max_amount
+    }
 }
 
 impl From<InvalidIdentityCreditWithdrawalTransitionAmountError> for ConsensusError {
diff --git a/packages/rs-dpp/src/identity/core_script.rs b/packages/rs-dpp/src/identity/core_script.rs
index 8aa7fd38a48..ff43e445717 100644
--- a/packages/rs-dpp/src/identity/core_script.rs
+++ b/packages/rs-dpp/src/identity/core_script.rs
@@ -51,19 +51,23 @@ impl CoreScript {
         Self::from_bytes(bytes)
     }
 
-    pub fn random_p2pkh(rng: &mut StdRng) -> Self {
-        Self::new_p2pkh(rng.gen::<[u8; 20]>())
-    }
-
-    pub fn random_p2sh(rng: &mut StdRng) -> Self {
+    pub fn new_p2sh(script_hash: [u8; 20]) -> Self {
         let mut bytes = vec![
             opcodes::all::OP_HASH160.to_u8(),
             opcodes::all::OP_PUSHBYTES_20.to_u8(),
         ];
-        bytes.append(&mut rng.gen::<[u8; 20]>().to_vec());
+        bytes.extend_from_slice(&script_hash);
         bytes.push(opcodes::all::OP_EQUAL.to_u8());
         Self::from_bytes(bytes)
     }
+
+    pub fn random_p2sh(rng: &mut StdRng) -> Self {
+        Self::new_p2sh(rng.gen())
+    }
+
+    pub fn random_p2pkh(rng: &mut StdRng) -> Self {
+        Self::new_p2pkh(rng.gen())
+    }
 }
 
 impl From<Vec<u8>> for CoreScript {
diff --git a/packages/rs-dpp/src/util/units.rs b/packages/rs-dpp/src/util/units.rs
index ca00561cf05..83af5e89769 100644
--- a/packages/rs-dpp/src/util/units.rs
+++ b/packages/rs-dpp/src/util/units.rs
@@ -27,3 +27,33 @@ macro_rules! dash_to_credits {
         credits as u64
     }};
 }
+
+#[macro_export]
+macro_rules! dash_to_duffs {
+    // The macro takes a string literal representing the Dash amount.
+    ($dash:expr) => {{
+        let dash_str = stringify!($dash);
+
+        // Parsing the input string to separate the whole and fractional parts.
+        let parts: Vec<&str> = dash_str.split('.').collect();
+        let mut credits: u128 = 0;
+
+        // Process the whole number part if it exists.
+        if let Some(whole) = parts.get(0) {
+            if let Ok(whole_number) = whole.parse::<u128>() {
+                credits += whole_number * 100_000_000; // Whole Dash amount to credits
+            }
+        }
+
+        // Process the fractional part if it exists.
+        if let Some(fraction) = parts.get(1) {
+            let fraction_length = fraction.len();
+            let fraction_number = fraction.parse::<u128>().unwrap_or(0);
+            // Calculate the multiplier based on the number of digits in the fraction.
+            let multiplier = 10u128.pow(8 - fraction_length as u32);
+            credits += fraction_number * multiplier; // Fractional Dash to credits
+        }
+
+        credits as u64
+    }};
+}
diff --git a/packages/rs-dpp/src/withdrawal/daily_withdrawal_limit/mod.rs b/packages/rs-dpp/src/withdrawal/daily_withdrawal_limit/mod.rs
new file mode 100644
index 00000000000..b1cd0a477db
--- /dev/null
+++ b/packages/rs-dpp/src/withdrawal/daily_withdrawal_limit/mod.rs
@@ -0,0 +1,18 @@
+use crate::fee::Credits;
+use crate::withdrawal::daily_withdrawal_limit::v0::daily_withdrawal_limit_v0;
+use crate::ProtocolError;
+use platform_version::version::PlatformVersion;
+
+mod v0;
+
+pub fn daily_withdrawal_limit(
+    total_credits_in_platform: Credits,
+    platform_version: &PlatformVersion,
+) -> Result<Credits, ProtocolError> {
+    match platform_version.dpp.methods.daily_withdrawal_limit {
+        0 => Ok(daily_withdrawal_limit_v0(total_credits_in_platform)),
+        v => Err(ProtocolError::UnknownVersionError(format!(
+            "Unknown daily_withdrawal_limit version {v}"
+        ))),
+    }
+}
diff --git a/packages/rs-dpp/src/withdrawal/daily_withdrawal_limit/v0/mod.rs b/packages/rs-dpp/src/withdrawal/daily_withdrawal_limit/v0/mod.rs
new file mode 100644
index 00000000000..c50b89e152c
--- /dev/null
+++ b/packages/rs-dpp/src/withdrawal/daily_withdrawal_limit/v0/mod.rs
@@ -0,0 +1,54 @@
+use crate::fee::Credits;
+
+/// Calculates the daily withdrawal limit based on the total credits available in the platform.
+///
+/// The function enforces the following rules:
+///
+/// 1. If the total credits are 1000 Dash in Credits or more:
+///     - The withdrawal limit is set to 10% of the total credits.
+/// 2. If the total credits are between 100 and 999 Dash in Credits:
+///     - The withdrawal limit is capped at 100 credits.
+/// 3. If the total credits are less than 100 Dash in Credits:
+///     - The withdrawal limit is the total available credits, as no more than the available amount can be withdrawn.
+///
+/// # Parameters
+///
+/// * `total_credits_in_platform`: The total amount of credits available in the platform.
+///
+/// # Returns
+///
+/// * `Credits`: The calculated daily withdrawal limit based on the available credits.
+///
+pub fn daily_withdrawal_limit_v0(total_credits_in_platform: Credits) -> Credits {
+    if total_credits_in_platform >= 100_000_000_000_000 {
+        // 1000 Dash
+        total_credits_in_platform / 10
+    } else if total_credits_in_platform >= 10_000_000_000_000 {
+        // 100 Dash
+        10_000_000_000_000
+    } else {
+        total_credits_in_platform
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::dash_to_credits;
+
+    #[test]
+    fn test_daily_withdrawal_limit() {
+        assert_eq!(
+            daily_withdrawal_limit_v0(dash_to_credits!(2000)),
+            dash_to_credits!(200)
+        );
+        assert_eq!(
+            daily_withdrawal_limit_v0(dash_to_credits!(500)),
+            dash_to_credits!(100)
+        );
+        assert_eq!(
+            daily_withdrawal_limit_v0(dash_to_credits!(50)),
+            dash_to_credits!(50)
+        );
+    }
+}
diff --git a/packages/rs-dpp/src/withdrawal/mod.rs b/packages/rs-dpp/src/withdrawal/mod.rs
index 1aaac3052dd..6ce0f25811d 100644
--- a/packages/rs-dpp/src/withdrawal/mod.rs
+++ b/packages/rs-dpp/src/withdrawal/mod.rs
@@ -1,3 +1,4 @@
+pub mod daily_withdrawal_limit;
 #[cfg(feature = "system_contracts")]
 mod document_try_into_asset_unlock_base_transaction_info;
 
@@ -19,4 +20,5 @@ pub enum Pooling {
 pub type WithdrawalTransactionIndex = u64;
 
 /// Simple type alias for withdrawal transaction with it's index
+
 pub type WithdrawalTransactionIndexAndBytes = (WithdrawalTransactionIndex, Vec<u8>);
diff --git a/packages/rs-drive-abci/Cargo.toml b/packages/rs-drive-abci/Cargo.toml
index 6ca9d43b972..4e57a1c9f19 100644
--- a/packages/rs-drive-abci/Cargo.toml
+++ b/packages/rs-drive-abci/Cargo.toml
@@ -23,7 +23,7 @@ drive = { path = "../rs-drive", default-features = false, features = [
   "server",
   "grovedb_operations_logging",
 ] }
-thiserror = "1.0.58"
+thiserror = "1.0.64"
 rand = "0.8.5"
 tempfile = "3.3.0"
 hex = "0.4.3"
diff --git a/packages/rs-drive-abci/src/execution/engine/initialization/init_chain/v0/mod.rs b/packages/rs-drive-abci/src/execution/engine/initialization/init_chain/v0/mod.rs
index d190c5c0551..3d0bc673a1d 100644
--- a/packages/rs-drive-abci/src/execution/engine/initialization/init_chain/v0/mod.rs
+++ b/packages/rs-drive-abci/src/execution/engine/initialization/init_chain/v0/mod.rs
@@ -13,7 +13,7 @@ use crate::platform_types::platform_state::PlatformState;
 use crate::platform_types::validator_set::ValidatorSetExt;
 use dpp::version::PlatformVersion;
 use std::sync::Arc;
-use tenderdash_abci::proto::abci::{RequestInitChain, ResponseInitChain, ValidatorSetUpdate};
+use tenderdash_abci::proto::abci::{RequestInitChain, ResponseInitChain};
 use tenderdash_abci::proto::google::protobuf::Timestamp;
 use tenderdash_abci::proto::serializers::timestamp::FromMilis;
 
diff --git a/packages/rs-drive-abci/src/execution/engine/run_block_proposal/mod.rs b/packages/rs-drive-abci/src/execution/engine/run_block_proposal/mod.rs
index a8420b34b04..e2149678b3a 100644
--- a/packages/rs-drive-abci/src/execution/engine/run_block_proposal/mod.rs
+++ b/packages/rs-drive-abci/src/execution/engine/run_block_proposal/mod.rs
@@ -91,8 +91,19 @@ Your software version: {}, latest supported protocol version: {}."#,
                 );
             };
 
-            // Set current protocol version to the block platform state
-            block_platform_state.set_current_protocol_version_in_consensus(next_protocol_version);
+            let old_protocol_version = block_platform_state.current_protocol_version_in_consensus();
+
+            if old_protocol_version != next_protocol_version {
+                // Set current protocol version to the block platform state
+                block_platform_state
+                    .set_current_protocol_version_in_consensus(next_protocol_version);
+                // This is for events like adding stuff to the root tree, or making structural changes/fixes
+                self.perform_events_on_first_block_of_protocol_change(
+                    transaction,
+                    old_protocol_version,
+                    next_platform_version,
+                )?;
+            }
 
             next_platform_version
         } else {
diff --git a/packages/rs-drive-abci/src/execution/engine/run_block_proposal/v0/mod.rs b/packages/rs-drive-abci/src/execution/engine/run_block_proposal/v0/mod.rs
index 63e1437f7fa..0b20f41d880 100644
--- a/packages/rs-drive-abci/src/execution/engine/run_block_proposal/v0/mod.rs
+++ b/packages/rs-drive-abci/src/execution/engine/run_block_proposal/v0/mod.rs
@@ -326,6 +326,17 @@ where
             platform_version,
         )?;
 
+        // Cleans up the expired locks for withdrawal amounts
+        // This is for example when we make a withdrawal for 30 Dash
+        // But we can only withdraw 1000 Dash a day
+        // after the withdrawal we should only be able to withdraw 970 Dash
+        // But 24 hours later that locked 30 comes back
+        self.clean_up_expired_locks_of_withdrawal_amounts(
+            &block_info,
+            transaction,
+            platform_version,
+        )?;
+
         // Create a new block execution context
 
         let mut block_execution_context: BlockExecutionContext =
diff --git a/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/mod.rs
index b2e1e826d4e..588f34d1697 100644
--- a/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/mod.rs
+++ b/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/mod.rs
@@ -1,2 +1,3 @@
 mod check_for_desired_protocol_upgrade;
+mod perform_events_on_first_block_of_protocol_change;
 mod upgrade_protocol_version;
diff --git a/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/mod.rs
new file mode 100644
index 00000000000..659a7e94aa0
--- /dev/null
+++ b/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/mod.rs
@@ -0,0 +1,66 @@
+mod v0;
+
+use crate::error::execution::ExecutionError;
+use crate::error::Error;
+use crate::platform_types::platform::Platform;
+use dpp::version::PlatformVersion;
+use dpp::version::ProtocolVersion;
+use drive::grovedb::Transaction;
+
+impl<C> Platform<C> {
+    /// Executes protocol-specific events on the first block after a protocol version change.
+    ///
+    /// This function is triggered when there is a protocol version upgrade detected in the network.
+    /// It checks if the current protocol version has transitioned from an earlier version to version 4,
+    /// and if so, performs the necessary setup or migration tasks associated with version 4.
+    ///
+    /// Currently, the function handles the transition to version 4 by initializing new structures
+    /// or states required for the new protocol version.
+    ///
+    /// # Parameters
+    ///
+    /// * `transaction`: A reference to the transaction context in which the changes should be applied.
+    /// * `previous_protocol_version`: The protocol version prior to the upgrade.
+    /// * `platform_version`: The current platform version containing the updated protocol version and relevant configuration details.
+    ///
+    /// # Returns
+    ///
+    /// * `Ok(())`: If all events related to the protocol change were successfully executed.
+    /// * `Err(Error)`: If there was an issue executing the protocol-specific events.
+    ///
+    /// # Versioning
+    ///
+    /// This function uses the `platform_version` parameter to determine which version-specific implementation
+    /// of the protocol change events should be executed:
+    ///
+    /// - If the version is `0`, it calls the `perform_events_on_first_block_of_protocol_change_v0` function,
+    ///   which contains the logic for version `0`.
+    /// - If no version is specified (`None`), the function does nothing and returns `Ok(())`.
+    /// - If a different version is specified, it returns an error indicating an unknown version mismatch.
+    ///
+    pub fn perform_events_on_first_block_of_protocol_change(
+        &self,
+        transaction: &Transaction,
+        previous_protocol_version: ProtocolVersion,
+        platform_version: &PlatformVersion,
+    ) -> Result<(), Error> {
+        match platform_version
+            .drive_abci
+            .methods
+            .protocol_upgrade
+            .perform_events_on_first_block_of_protocol_change
+        {
+            Some(0) => self.perform_events_on_first_block_of_protocol_change_v0(
+                transaction,
+                previous_protocol_version,
+                platform_version,
+            ),
+            None => return Ok(()),
+            Some(version) => Err(Error::Execution(ExecutionError::UnknownVersionMismatch {
+                method: "perform_events_on_first_block_of_protocol_change".to_string(),
+                known_versions: vec![0],
+                received: version,
+            })),
+        }
+    }
+}
diff --git a/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/v0/mod.rs
new file mode 100644
index 00000000000..d732893b80a
--- /dev/null
+++ b/packages/rs-drive-abci/src/execution/platform_events/protocol_upgrade/perform_events_on_first_block_of_protocol_change/v0/mod.rs
@@ -0,0 +1,73 @@
+use crate::error::Error;
+use crate::platform_types::platform::Platform;
+use dpp::version::PlatformVersion;
+use dpp::version::ProtocolVersion;
+use drive::drive::identity::withdrawals::paths::{
+    get_withdrawal_root_path, WITHDRAWAL_TRANSACTIONS_SUM_AMOUNT_TREE_KEY,
+};
+use drive::grovedb::{Element, Transaction};
+
+impl<C> Platform<C> {
+    /// Executes protocol-specific events on the first block after a protocol version change.
+    ///
+    /// This function is triggered when there is a protocol version upgrade detected in the network.
+    /// It checks if the current protocol version has transitioned from an earlier version to version 4,
+    /// and if so, performs the necessary setup or migration tasks associated with version 4.
+    ///
+    /// Currently, the function handles the transition to version 4 by initializing new structures
+    /// or states required for the new protocol version.
+    ///
+    /// # Parameters
+    ///
+    /// * `transaction`: A reference to the transaction context in which the changes should be applied.
+    /// * `previous_protocol_version`: The protocol version prior to the upgrade.
+    /// * `platform_version`: The current platform version containing the updated protocol version and relevant configuration details.
+    ///
+    /// # Returns
+    ///
+    /// * `Ok(())`: If all events related to the protocol change were successfully executed.
+    /// * `Err(Error)`: If there was an issue executing the protocol-specific events.
+    pub(super) fn perform_events_on_first_block_of_protocol_change_v0(
+        &self,
+        transaction: &Transaction,
+        previous_protocol_version: ProtocolVersion,
+        platform_version: &PlatformVersion,
+    ) -> Result<(), Error> {
+        if previous_protocol_version < 4 && platform_version.protocol_version >= 4 {
+            self.transition_to_version_4(transaction, platform_version);
+        }
+
+        Ok(())
+    }
+
+    /// Initializes an empty sum tree for withdrawal transactions required for protocol version 4.
+    ///
+    /// This function is called during the transition to protocol version 4 to set up
+    /// an empty sum tree at the specified path if it does not already exist.
+    ///
+    /// # Parameters
+    ///
+    /// * `transaction`: A reference to the transaction context in which the changes should be applied.
+    /// * `platform_version`: The current platform version containing the updated protocol version and relevant configuration details.
+    ///
+    /// # Returns
+    ///
+    /// * `Ok(())`: If the transition to version 4 was successful.
+    /// * `Err(Error)`: If there was an issue creating or updating the necessary data structures.
+    fn transition_to_version_4(
+        &self,
+        transaction: &Transaction,
+        platform_version: &PlatformVersion,
+    ) -> Result<(), Error> {
+        let path = get_withdrawal_root_path();
+        self.drive.grove_insert_if_not_exists(
+            (&path).into(),
+            &WITHDRAWAL_TRANSACTIONS_SUM_AMOUNT_TREE_KEY,
+            Element::empty_sum_tree(),
+            Some(transaction),
+            None,
+            &platform_version.drive,
+        )?;
+        Ok(())
+    }
+}
diff --git a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/append_signatures_and_broadcast_withdrawal_transactions/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/append_signatures_and_broadcast_withdrawal_transactions/v0/mod.rs
index 6d1deb5c82d..d84db008489 100644
--- a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/append_signatures_and_broadcast_withdrawal_transactions/v0/mod.rs
+++ b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/append_signatures_and_broadcast_withdrawal_transactions/v0/mod.rs
@@ -10,7 +10,7 @@ use dashcore_rpc::Error as CoreRPCError;
 use dpp::dashcore::bls_sig_utils::BLSSignature;
 use dpp::dashcore::transaction::special_transaction::TransactionPayload::AssetUnlockPayloadType;
 use dpp::dashcore::{consensus, Transaction, Txid};
-use std::collections::{BTreeMap, HashMap};
+use std::collections::BTreeMap;
 
 use std::fs::{self, File};
 use std::io::Write;
diff --git a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/build_untied_withdrawal_transactions_from_documents/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/build_untied_withdrawal_transactions_from_documents/mod.rs
index 0f0ff66a740..fb6717b7b84 100644
--- a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/build_untied_withdrawal_transactions_from_documents/mod.rs
+++ b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/build_untied_withdrawal_transactions_from_documents/mod.rs
@@ -4,6 +4,7 @@ use crate::platform_types::platform::Platform;
 use crate::rpc::core::CoreRPCLike;
 use dpp::block::block_info::BlockInfo;
 use dpp::document::Document;
+use dpp::fee::Credits;
 use dpp::version::PlatformVersion;
 use dpp::withdrawal::{WithdrawalTransactionIndex, WithdrawalTransactionIndexAndBytes};
 
@@ -35,7 +36,7 @@ where
         start_index: WithdrawalTransactionIndex,
         block_info: &BlockInfo,
         platform_version: &PlatformVersion,
-    ) -> Result<Vec<WithdrawalTransactionIndexAndBytes>, Error> {
+    ) -> Result<(Vec<WithdrawalTransactionIndexAndBytes>, Credits), Error> {
         match platform_version
             .drive_abci
             .methods
diff --git a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/build_untied_withdrawal_transactions_from_documents/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/build_untied_withdrawal_transactions_from_documents/v0/mod.rs
index 2a1e5f8a743..13da6e72f1f 100644
--- a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/build_untied_withdrawal_transactions_from_documents/v0/mod.rs
+++ b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/build_untied_withdrawal_transactions_from_documents/v0/mod.rs
@@ -3,7 +3,9 @@ use dpp::block::block_info::BlockInfo;
 use dpp::data_contracts::withdrawals_contract;
 use dpp::data_contracts::withdrawals_contract::v1::document_types::withdrawal;
 use dpp::document::document_methods::DocumentMethodsV0;
-use dpp::document::{Document, DocumentV0Setters};
+use dpp::document::{Document, DocumentV0Getters, DocumentV0Setters};
+use dpp::fee::Credits;
+use dpp::platform_value::btreemap_extensions::BTreeValueMapHelper;
 use dpp::withdrawal::{WithdrawalTransactionIndex, WithdrawalTransactionIndexAndBytes};
 
 use crate::{
@@ -24,20 +26,35 @@ where
         start_index: WithdrawalTransactionIndex,
         block_info: &BlockInfo,
         platform_version: &PlatformVersion,
-    ) -> Result<Vec<WithdrawalTransactionIndexAndBytes>, Error> {
-        documents
-            .iter_mut()
-            .enumerate()
-            .map(|(i, document)| {
+    ) -> Result<(Vec<WithdrawalTransactionIndexAndBytes>, Credits), Error> {
+        documents.iter_mut().enumerate().try_fold(
+            (Vec::new(), 0u64), // Start with an empty vector for transactions and 0 for total amount.
+            |(mut transactions, mut total_amount), (i, document)| {
+                // Calculate the transaction index.
                 let transaction_index = start_index + i as WithdrawalTransactionIndex;
 
+                // Convert the document into the withdrawal transaction information.
                 let withdrawal_transaction = document.try_into_asset_unlock_base_transaction_info(
                     transaction_index,
                     platform_version,
                 )?;
 
+                // Create a buffer to hold the encoded transaction.
                 let mut transaction_buffer: Vec<u8> = vec![];
 
+                // Get the withdrawal amount from the document properties.
+                let amount: u64 = document
+                    .properties()
+                    .get_integer(withdrawal::properties::AMOUNT)?;
+
+                // Add the amount to the total, checking for overflow.
+                total_amount = total_amount.checked_add(amount).ok_or_else(|| {
+                    Error::Execution(ExecutionError::Overflow(
+                        "Overflow while calculating total amount",
+                    ))
+                })?;
+
+                // Consensus encode the withdrawal transaction into the buffer.
                 withdrawal_transaction
                     .consensus_encode(&mut transaction_buffer)
                     .map_err(|_| {
@@ -46,24 +63,28 @@ where
                         ))
                     })?;
 
+                // Update the document properties.
                 document.set_u64(withdrawal::properties::TRANSACTION_INDEX, transaction_index);
-
                 document.set_u8(
                     withdrawal::properties::STATUS,
                     withdrawals_contract::WithdrawalStatus::POOLED as u8,
                 );
-
                 document.set_updated_at(Some(block_info.time_ms));
 
+                // Increment the document revision, handle error if it fails.
                 document.increment_revision().map_err(|_| {
-                    Error::Execution(ExecutionError::CorruptedCodeExecution(
-                        "Could not increment document revision",
+                    Error::Execution(ExecutionError::Overflow(
+                        "Overflow when adding to document revision for withdrawals",
                     ))
                 })?;
 
-                Ok((transaction_index, transaction_buffer))
-            })
-            .collect()
+                // Add the transaction index and encoded transaction to the result.
+                transactions.push((transaction_index, transaction_buffer));
+
+                // Return the updated accumulator.
+                Ok((transactions, total_amount))
+            },
+        )
     }
 }
 
@@ -160,7 +181,7 @@ mod tests {
 
             let block_info = BlockInfo::default_with_time(50);
 
-            let transactions = platform
+            let (transactions, credits) = platform
                 .build_untied_withdrawal_transactions_from_documents_v0(
                     &mut documents,
                     50,
@@ -190,6 +211,8 @@ mod tests {
                     ),
                 ]
             );
+
+            assert_eq!(credits, 2000);
         }
     }
 }
diff --git a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/cleanup_expired_locks_of_withdrawal_amounts/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/cleanup_expired_locks_of_withdrawal_amounts/mod.rs
new file mode 100644
index 00000000000..837ffd1bf9f
--- /dev/null
+++ b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/cleanup_expired_locks_of_withdrawal_amounts/mod.rs
@@ -0,0 +1,58 @@
+use crate::error::execution::ExecutionError;
+use crate::error::Error;
+use crate::platform_types::platform::Platform;
+
+use crate::rpc::core::CoreRPCLike;
+use dpp::block::block_info::BlockInfo;
+
+use dpp::version::PlatformVersion;
+use drive::grovedb::Transaction;
+
+mod v0;
+
+impl<C> Platform<C>
+where
+    C: CoreRPCLike,
+{
+    /// Cleans up expired locks of withdrawal amounts based on the protocol version.
+    ///
+    /// This function determines the appropriate versioned function to call for cleaning up expired
+    /// withdrawal locks according to the provided platform version. It deletes expired withdrawal locks
+    /// that have surpassed their allowed time limit, ensuring that only valid withdrawal entries remain.
+    ///
+    /// # Parameters
+    /// * `block_info`: Information about the current block, including the timestamp used to identify expired locks.
+    /// * `transaction`: The transaction under which this operation should be performed.
+    /// * `platform_version`: The version of the platform to ensure compatibility with the appropriate cleanup method.
+    ///
+    /// # Returns
+    /// * `Ok(())`: If the cleanup was performed successfully.
+    /// * `Err(Error::Execution(ExecutionError::UnknownVersionMismatch))`: If the platform version does not match known versions.
+    ///
+    /// # Errors
+    /// Returns an error if the platform version is unknown or if the cleanup process encounters an issue.
+    pub fn clean_up_expired_locks_of_withdrawal_amounts(
+        &self,
+        block_info: &BlockInfo,
+        transaction: &Transaction,
+        platform_version: &PlatformVersion,
+    ) -> Result<(), Error> {
+        match platform_version
+            .drive_abci
+            .methods
+            .withdrawals
+            .cleanup_expired_locks_of_withdrawal_amounts
+        {
+            0 => self.cleanup_expired_locks_of_withdrawal_amounts_v0(
+                block_info,
+                transaction,
+                platform_version,
+            ),
+            version => Err(Error::Execution(ExecutionError::UnknownVersionMismatch {
+                method: "cleanup_expired_locks_of_withdrawal_amounts".to_string(),
+                known_versions: vec![0],
+                received: version,
+            })),
+        }
+    }
+}
diff --git a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/cleanup_expired_locks_of_withdrawal_amounts/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/cleanup_expired_locks_of_withdrawal_amounts/v0/mod.rs
new file mode 100644
index 00000000000..74315a1d6c7
--- /dev/null
+++ b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/cleanup_expired_locks_of_withdrawal_amounts/v0/mod.rs
@@ -0,0 +1,69 @@
+use crate::error::Error;
+use crate::platform_types::platform::Platform;
+
+use crate::rpc::core::CoreRPCLike;
+use dpp::block::block_info::BlockInfo;
+
+use dpp::version::PlatformVersion;
+use drive::drive::identity::withdrawals::paths::get_withdrawal_transactions_sum_tree_path_vec;
+use drive::grovedb::{PathQuery, QueryItem, Transaction};
+use drive::util::grove_operations::BatchDeleteApplyType;
+
+impl<C> Platform<C>
+where
+    C: CoreRPCLike,
+{
+    pub(super) fn cleanup_expired_locks_of_withdrawal_amounts_v0(
+        &self,
+        block_info: &BlockInfo,
+        transaction: &Transaction,
+        platform_version: &PlatformVersion,
+    ) -> Result<(), Error> {
+        if platform_version
+            .drive_abci
+            .withdrawal_constants
+            .cleanup_expired_locks_of_withdrawal_amounts_limit
+            == 0
+        {
+            // No clean up
+            return Ok(());
+        }
+        let mut batch_operations = vec![];
+
+        let sum_path = get_withdrawal_transactions_sum_tree_path_vec();
+
+        let mut path_query = PathQuery::new_single_query_item(
+            sum_path,
+            QueryItem::RangeTo(..block_info.time_ms.to_be_bytes().to_vec()),
+        );
+
+        path_query.query.limit = Some(
+            platform_version
+                .drive_abci
+                .withdrawal_constants
+                .cleanup_expired_locks_of_withdrawal_amounts_limit,
+        );
+
+        self.drive.batch_delete_items_in_path_query(
+            &path_query,
+            true,
+            // we know that we are not deleting a subtree
+            BatchDeleteApplyType::StatefulBatchDelete {
+                is_known_to_be_subtree_with_sum: Some((false, false)),
+            },
+            Some(transaction),
+            &mut batch_operations,
+            &platform_version.drive,
+        )?;
+
+        self.drive.apply_batch_low_level_drive_operations(
+            None,
+            Some(transaction),
+            batch_operations,
+            &mut vec![],
+            &platform_version.drive,
+        )?;
+
+        Ok(())
+    }
+}
diff --git a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/dequeue_and_build_unsigned_withdrawal_transactions/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/dequeue_and_build_unsigned_withdrawal_transactions/v0/mod.rs
index c2506d4f7d8..7a37525de68 100644
--- a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/dequeue_and_build_unsigned_withdrawal_transactions/v0/mod.rs
+++ b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/dequeue_and_build_unsigned_withdrawal_transactions/v0/mod.rs
@@ -42,7 +42,7 @@ where
     ) -> Result<UnsignedWithdrawalTxs, Error> {
         let mut drive_operations: Vec<DriveOperation> = vec![];
 
-        // Get withdrawal_transactions_per_block_limit (normally 16) latest withdrawal transactions from the queue
+        // Get withdrawal_transactions_per_block_limit (normally 4) latest withdrawal transactions from the queue
         let untied_withdrawal_transactions = self.drive.dequeue_untied_withdrawal_transactions(
             platform_version
                 .system_limits
@@ -150,8 +150,8 @@ where
                 document.set_updated_at(Some(block_info.time_ms));
 
                 document.increment_revision().map_err(|_| {
-                    Error::Execution(ExecutionError::CorruptedCodeExecution(
-                        "Could not increment document revision",
+                    Error::Execution(ExecutionError::Overflow(
+                        "Overflow when adding to document revision for withdrawals",
                     ))
                 })?;
 
diff --git a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/mod.rs
index 75412110566..0de6cbfcef2 100644
--- a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/mod.rs
+++ b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/mod.rs
@@ -1,5 +1,6 @@
 pub(in crate::execution) mod append_signatures_and_broadcast_withdrawal_transactions;
 pub(in crate::execution) mod build_untied_withdrawal_transactions_from_documents;
+pub(in crate::execution) mod cleanup_expired_locks_of_withdrawal_amounts;
 pub(in crate::execution) mod dequeue_and_build_unsigned_withdrawal_transactions;
 pub(in crate::execution) mod fetch_transactions_block_inclusion_status;
 pub(in crate::execution) mod pool_withdrawals_into_transactions_queue;
diff --git a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/pool_withdrawals_into_transactions_queue/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/pool_withdrawals_into_transactions_queue/v0/mod.rs
index 00f1c8a7615..2f353c8225a 100644
--- a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/pool_withdrawals_into_transactions_queue/v0/mod.rs
+++ b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/pool_withdrawals_into_transactions_queue/v0/mod.rs
@@ -1,13 +1,13 @@
 use dpp::block::block_info::BlockInfo;
 
 use dpp::data_contract::accessors::v0::DataContractV0Getters;
-
+use dpp::document::DocumentV0Getters;
+use dpp::platform_value::btreemap_extensions::BTreeValueMapHelper;
 use dpp::version::PlatformVersion;
 use drive::grovedb::TransactionArg;
 
 use dpp::system_data_contracts::withdrawals_contract;
 use dpp::system_data_contracts::withdrawals_contract::v1::document_types::withdrawal;
-use drive::config::DEFAULT_QUERY_LIMIT;
 
 use crate::platform_types::platform_state::v0::PlatformStateV0Methods;
 use crate::platform_types::platform_state::PlatformState;
@@ -44,9 +44,11 @@ where
             );
             return Ok(());
         }
-        let mut documents = self.drive.fetch_oldest_withdrawal_documents_by_status(
+        let documents = self.drive.fetch_oldest_withdrawal_documents_by_status(
             withdrawals_contract::WithdrawalStatus::QUEUED.into(),
-            DEFAULT_QUERY_LIMIT,
+            platform_version
+                .system_limits
+                .withdrawal_transactions_per_block_limit,
             transaction,
             platform_version,
         )?;
@@ -55,16 +57,56 @@ where
             return Ok(());
         }
 
+        // Only take documents up to the withdrawal amount
+        let current_withdrawal_limit = self
+            .drive
+            .calculate_current_withdrawal_limit(transaction, platform_version)?;
+
+        // Only process documents up to the current withdrawal limit.
+        let mut total_withdrawal_amount = 0u64;
+
+        // Iterate over the documents and accumulate their withdrawal amounts.
+        let mut documents_to_process = vec![];
+        for document in documents {
+            // Get the withdrawal amount from the document properties.
+            let amount: u64 = document
+                .properties()
+                .get_integer(withdrawal::properties::AMOUNT)?;
+
+            // Check if adding this amount would exceed the current withdrawal limit.
+            let potential_total_withdrawal_amount =
+                total_withdrawal_amount.checked_add(amount).ok_or_else(|| {
+                    Error::Execution(ExecutionError::Overflow(
+                        "overflow in total withdrawal amount",
+                    ))
+                })?;
+
+            if potential_total_withdrawal_amount > current_withdrawal_limit {
+                // If adding this withdrawal would exceed the limit, stop processing further.
+                break;
+            }
+
+            total_withdrawal_amount = potential_total_withdrawal_amount;
+
+            // Add this document to the list of documents to be processed.
+            documents_to_process.push(document);
+        }
+
+        if documents_to_process.is_empty() {
+            return Ok(());
+        }
+
         let start_transaction_index = self
             .drive
             .fetch_next_withdrawal_transaction_index(transaction, platform_version)?;
 
-        let withdrawal_transactions = self.build_untied_withdrawal_transactions_from_documents(
-            &mut documents,
-            start_transaction_index,
-            block_info,
-            platform_version,
-        )?;
+        let (withdrawal_transactions, total_amount) = self
+            .build_untied_withdrawal_transactions_from_documents(
+                &mut documents_to_process,
+                start_transaction_index,
+                block_info,
+                platform_version,
+            )?;
 
         let withdrawal_transactions_count = withdrawal_transactions.len();
 
@@ -73,6 +115,7 @@ where
         self.drive
             .add_enqueue_untied_withdrawal_transaction_operations(
                 withdrawal_transactions,
+                total_amount,
                 &mut drive_operations,
                 platform_version,
             )?;
@@ -88,7 +131,7 @@ where
 
         tracing::debug!(
             "Pooled {} withdrawal documents into {} transactions with indices from {} to {}",
-            documents.len(),
+            documents_to_process.len(),
             withdrawal_transactions_count,
             start_transaction_index,
             end_transaction_index,
@@ -97,7 +140,7 @@ where
         let withdrawals_contract = self.drive.cache.system_data_contracts.load_withdrawals();
 
         self.drive.add_update_multiple_documents_operations(
-            &documents,
+            &documents_to_process,
             &withdrawals_contract,
             withdrawals_contract
                 .document_type_for_name(withdrawal::NAME)
@@ -142,6 +185,7 @@ mod tests {
     use dpp::platform_value::platform_value;
     use dpp::system_data_contracts::load_system_data_contract;
     use dpp::version::PlatformVersion;
+    use drive::config::DEFAULT_QUERY_LIMIT;
 
     #[test]
     fn test_pooling() {
diff --git a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/pool_withdrawals_into_transactions_queue/v1/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/pool_withdrawals_into_transactions_queue/v1/mod.rs
index 34b3174933f..cf7c3cec299 100644
--- a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/pool_withdrawals_into_transactions_queue/v1/mod.rs
+++ b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/pool_withdrawals_into_transactions_queue/v1/mod.rs
@@ -1,13 +1,13 @@
 use dpp::block::block_info::BlockInfo;
 
 use dpp::data_contract::accessors::v0::DataContractV0Getters;
-
+use dpp::document::DocumentV0Getters;
+use dpp::platform_value::btreemap_extensions::BTreeValueMapHelper;
 use dpp::version::PlatformVersion;
 use drive::grovedb::TransactionArg;
 
 use dpp::system_data_contracts::withdrawals_contract;
 use dpp::system_data_contracts::withdrawals_contract::v1::document_types::withdrawal;
-use drive::config::DEFAULT_QUERY_LIMIT;
 
 use crate::{
     error::{execution::ExecutionError, Error},
@@ -20,15 +20,19 @@ where
     C: CoreRPCLike,
 {
     /// Pool withdrawal documents into transactions
+    /// Version 1 changes on Version 0, by not having the Core 2 Quorum limit.
+    /// We should switch to Version 1 once Core has fixed the issue
     pub(super) fn pool_withdrawals_into_transactions_queue_v1(
         &self,
         block_info: &BlockInfo,
         transaction: TransactionArg,
         platform_version: &PlatformVersion,
     ) -> Result<(), Error> {
-        let mut documents = self.drive.fetch_oldest_withdrawal_documents_by_status(
+        let documents = self.drive.fetch_oldest_withdrawal_documents_by_status(
             withdrawals_contract::WithdrawalStatus::QUEUED.into(),
-            DEFAULT_QUERY_LIMIT,
+            platform_version
+                .system_limits
+                .withdrawal_transactions_per_block_limit,
             transaction,
             platform_version,
         )?;
@@ -37,16 +41,56 @@ where
             return Ok(());
         }
 
+        // Only take documents up to the withdrawal amount
+        let current_withdrawal_limit = self
+            .drive
+            .calculate_current_withdrawal_limit(transaction, platform_version)?;
+
+        // Only process documents up to the current withdrawal limit.
+        let mut total_withdrawal_amount = 0u64;
+
+        // Iterate over the documents and accumulate their withdrawal amounts.
+        let mut documents_to_process = vec![];
+        for document in documents {
+            // Get the withdrawal amount from the document properties.
+            let amount: u64 = document
+                .properties()
+                .get_integer(withdrawal::properties::AMOUNT)?;
+
+            // Check if adding this amount would exceed the current withdrawal limit.
+            let potential_total_withdrawal_amount =
+                total_withdrawal_amount.checked_add(amount).ok_or_else(|| {
+                    Error::Execution(ExecutionError::Overflow(
+                        "overflow in total withdrawal amount",
+                    ))
+                })?;
+
+            if potential_total_withdrawal_amount > current_withdrawal_limit {
+                // If adding this withdrawal would exceed the limit, stop processing further.
+                break;
+            }
+
+            total_withdrawal_amount = potential_total_withdrawal_amount;
+
+            // Add this document to the list of documents to be processed.
+            documents_to_process.push(document);
+        }
+
+        if documents_to_process.is_empty() {
+            return Ok(());
+        }
+
         let start_transaction_index = self
             .drive
             .fetch_next_withdrawal_transaction_index(transaction, platform_version)?;
 
-        let withdrawal_transactions = self.build_untied_withdrawal_transactions_from_documents(
-            &mut documents,
-            start_transaction_index,
-            block_info,
-            platform_version,
-        )?;
+        let (withdrawal_transactions, total_amount) = self
+            .build_untied_withdrawal_transactions_from_documents(
+                &mut documents_to_process,
+                start_transaction_index,
+                block_info,
+                platform_version,
+            )?;
 
         let withdrawal_transactions_count = withdrawal_transactions.len();
 
@@ -55,6 +99,7 @@ where
         self.drive
             .add_enqueue_untied_withdrawal_transaction_operations(
                 withdrawal_transactions,
+                total_amount,
                 &mut drive_operations,
                 platform_version,
             )?;
@@ -70,7 +115,7 @@ where
 
         tracing::debug!(
             "Pooled {} withdrawal documents into {} transactions with indices from {} to {}",
-            documents.len(),
+            documents_to_process.len(),
             withdrawal_transactions_count,
             start_transaction_index,
             end_transaction_index,
@@ -79,7 +124,7 @@ where
         let withdrawals_contract = self.drive.cache.system_data_contracts.load_withdrawals();
 
         self.drive.add_update_multiple_documents_operations(
-            &documents,
+            &documents_to_process,
             &withdrawals_contract,
             withdrawals_contract
                 .document_type_for_name(withdrawal::NAME)
@@ -124,6 +169,7 @@ mod tests {
     use dpp::platform_value::platform_value;
     use dpp::system_data_contracts::load_system_data_contract;
     use dpp::version::PlatformVersion;
+    use drive::config::DEFAULT_QUERY_LIMIT;
 
     #[test]
     fn test_pooling() {
diff --git a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/update_broadcasted_withdrawal_statuses/v0/mod.rs b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/update_broadcasted_withdrawal_statuses/v0/mod.rs
index 2eba2e228c8..81210da62e7 100644
--- a/packages/rs-drive-abci/src/execution/platform_events/withdrawals/update_broadcasted_withdrawal_statuses/v0/mod.rs
+++ b/packages/rs-drive-abci/src/execution/platform_events/withdrawals/update_broadcasted_withdrawal_statuses/v0/mod.rs
@@ -22,8 +22,6 @@ use crate::{
     rpc::core::CoreRPCLike,
 };
 
-const NUMBER_OF_BLOCKS_BEFORE_EXPIRED: u32 = 48;
-
 impl<C> Platform<C>
 where
     C: CoreRPCLike,
@@ -109,7 +107,12 @@ where
                 );
 
                 WithdrawalStatus::COMPLETE
-            } else if block_height_difference > NUMBER_OF_BLOCKS_BEFORE_EXPIRED {
+            } else if block_height_difference
+                > platform_version
+                    .drive_abci
+                    .withdrawal_constants
+                    .core_expiration_blocks
+            {
                 tracing::debug!(
                     transaction_sign_height,
                     "Withdrawal with transaction index {} is marked as expired",
diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_withdrawal/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_withdrawal/mod.rs
index f74cb363924..a1ed43702c5 100644
--- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_withdrawal/mod.rs
+++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_withdrawal/mod.rs
@@ -71,7 +71,7 @@ impl StateTransitionBasicStructureValidationV0 for IdentityCreditWithdrawalTrans
                 // Returns not supported
                 self.validate_basic_structure_v0(platform_version)
             }
-            Some(1) => self.validate_basic_structure_v1(),
+            Some(1) => self.validate_basic_structure_v1(platform_version),
             Some(version) => Err(Error::Execution(ExecutionError::UnknownVersionMismatch {
                 method: "identity credit withdrawal transition: validate_basic_structure"
                     .to_string(),
diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_withdrawal/structure/v1/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_withdrawal/structure/v1/mod.rs
index daaf9f7e3d8..7f018630a84 100644
--- a/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_withdrawal/structure/v1/mod.rs
+++ b/packages/rs-drive-abci/src/execution/validation/state_transition/state_transitions/identity_credit_withdrawal/structure/v1/mod.rs
@@ -13,23 +13,31 @@ use dpp::state_transition::identity_credit_withdrawal_transition::{
 };
 use dpp::util::is_fibonacci_number::is_fibonacci_number;
 use dpp::validation::SimpleConsensusValidationResult;
+use dpp::version::PlatformVersion;
 use dpp::withdrawal::Pooling;
 
 pub(in crate::execution::validation::state_transition::state_transitions::identity_credit_withdrawal) trait IdentityCreditWithdrawalStateTransitionStructureValidationV1 {
-    fn validate_basic_structure_v1(&self) -> Result<SimpleConsensusValidationResult, Error>;
+    fn validate_basic_structure_v1(&self, platform_version: &PlatformVersion) -> Result<SimpleConsensusValidationResult, Error>;
 }
 
 impl IdentityCreditWithdrawalStateTransitionStructureValidationV1
     for IdentityCreditWithdrawalTransition
 {
-    fn validate_basic_structure_v1(&self) -> Result<SimpleConsensusValidationResult, Error> {
+    fn validate_basic_structure_v1(
+        &self,
+        platform_version: &PlatformVersion,
+    ) -> Result<SimpleConsensusValidationResult, Error> {
         let mut result = SimpleConsensusValidationResult::default();
 
-        if self.amount() < MIN_WITHDRAWAL_AMOUNT {
+        let amount = self.amount();
+        if amount < MIN_WITHDRAWAL_AMOUNT
+            || amount > platform_version.system_limits.max_withdrawal_amount
+        {
             result.add_error(ConsensusError::from(
                 InvalidIdentityCreditWithdrawalTransitionAmountError::new(
                     self.amount(),
                     MIN_WITHDRAWAL_AMOUNT,
+                    platform_version.system_limits.max_withdrawal_amount,
                 ),
             ));
         }
diff --git a/packages/rs-drive-abci/src/platform_types/platform_state/mod.rs b/packages/rs-drive-abci/src/platform_types/platform_state/mod.rs
index 462ce2c3272..e937924c94d 100644
--- a/packages/rs-drive-abci/src/platform_types/platform_state/mod.rs
+++ b/packages/rs-drive-abci/src/platform_types/platform_state/mod.rs
@@ -25,7 +25,6 @@ use indexmap::IndexMap;
 use crate::config::PlatformConfig;
 use crate::error::execution::ExecutionError;
 use crate::platform_types::signature_verification_quorum_set::SignatureVerificationQuorumSet;
-use crate::platform_types::validator_set::v0::ValidatorSetV0Getters;
 use dpp::block::block_info::BlockInfo;
 use dpp::fee::default_costs::CachedEpochIndexFeeVersions;
 use dpp::util::hash::hash_double;
diff --git a/packages/rs-drive-abci/tests/strategy_tests/execution.rs b/packages/rs-drive-abci/tests/strategy_tests/execution.rs
index 133b281446b..8b6b5767ec3 100644
--- a/packages/rs-drive-abci/tests/strategy_tests/execution.rs
+++ b/packages/rs-drive-abci/tests/strategy_tests/execution.rs
@@ -1209,5 +1209,6 @@ pub(crate) fn continue_chain_for_strategy(
         validator_set_updates,
         state_transition_results_per_block,
         instant_lock_quorums,
+        signer,
     }
 }
diff --git a/packages/rs-drive-abci/tests/strategy_tests/main.rs b/packages/rs-drive-abci/tests/strategy_tests/main.rs
index 21d269d6be7..98e2b518a2c 100644
--- a/packages/rs-drive-abci/tests/strategy_tests/main.rs
+++ b/packages/rs-drive-abci/tests/strategy_tests/main.rs
@@ -47,6 +47,7 @@ mod tests {
     use crate::execution::{continue_chain_for_strategy, run_chain_for_strategy};
     use crate::query::QueryStrategy;
     use crate::strategy::{FailureStrategy, MasternodeListChangesStrategy};
+    use assert_matches::assert_matches;
     use dashcore_rpc::dashcore::hashes::Hash;
     use dashcore_rpc::dashcore::BlockHash;
     use dashcore_rpc::json::QuorumType;
@@ -80,6 +81,7 @@ mod tests {
     use rand::SeedableRng;
     use tenderdash_abci::proto::abci::{RequestInfo, ResponseInfo};
 
+    use dpp::dash_to_duffs;
     use dpp::data_contract::document_type::v0::random_document_type::{
         FieldMinMaxBounds, FieldTypeWeights, RandomDocumentTypeParameters,
     };
@@ -1247,6 +1249,16 @@ mod tests {
 
         let outcome = run_chain_for_strategy(&mut platform, 1, strategy, config, 15, &mut None);
 
+        for tx_results_per_block in outcome.state_transition_results_per_block.values() {
+            for (state_transition, result) in tx_results_per_block {
+                assert_eq!(
+                    result.code, 0,
+                    "state transition got code {} : {:?}",
+                    result.code, state_transition
+                );
+            }
+        }
+
         outcome
             .abci_app
             .platform
@@ -2325,6 +2337,7 @@ mod tests {
                     },
                     start_keys: 5,
                     extra_keys: Default::default(),
+                    start_balance_range: dash_to_duffs!(1)..=dash_to_duffs!(1),
                 },
 
                 identity_contract_nonce_gaps: None,
@@ -2458,6 +2471,7 @@ mod tests {
                     },
                     start_keys: 5,
                     extra_keys: Default::default(),
+                    start_balance_range: dash_to_duffs!(1)..=dash_to_duffs!(1),
                 },
 
                 identity_contract_nonce_gaps: None,
@@ -2581,6 +2595,7 @@ mod tests {
 
         let start_identities = create_state_transitions_for_identities(
             vec![identity1, identity2],
+            &(dash_to_duffs!(1)..=dash_to_duffs!(1)),
             &simple_signer,
             &mut rng,
             platform_version,
@@ -2750,6 +2765,7 @@ mod tests {
                     },
                     start_keys: 5,
                     extra_keys: Default::default(),
+                    start_balance_range: dash_to_duffs!(1)..=dash_to_duffs!(1),
                 },
 
                 identity_contract_nonce_gaps: None,
@@ -2915,6 +2931,7 @@ mod tests {
                     },
                     start_keys: 5,
                     extra_keys: Default::default(),
+                    start_balance_range: dash_to_duffs!(1)..=dash_to_duffs!(1),
                 },
 
                 identity_contract_nonce_gaps: None,
@@ -2976,7 +2993,7 @@ mod tests {
             strategy: Strategy {
                 start_contracts: vec![],
                 operations: vec![Operation {
-                    op_type: OperationType::IdentityTopUp,
+                    op_type: OperationType::IdentityTopUp(dash_to_duffs!(1)..=dash_to_duffs!(1)),
                     frequency: Frequency {
                         times_per_block_range: 1..3,
                         chance_per_block: None,
@@ -3233,6 +3250,7 @@ mod tests {
                     },
                     start_keys: 5,
                     extra_keys: Default::default(),
+                    start_balance_range: dash_to_duffs!(1)..=dash_to_duffs!(1),
                 },
 
                 identity_contract_nonce_gaps: None,
@@ -3419,6 +3437,7 @@ mod tests {
                     },
                     start_keys: 5,
                     extra_keys: Default::default(),
+                    start_balance_range: dash_to_duffs!(1)..=dash_to_duffs!(1),
                 },
 
                 identity_contract_nonce_gaps: None,
@@ -3581,6 +3600,7 @@ mod tests {
                     },
                     start_keys: 5,
                     extra_keys: Default::default(),
+                    start_balance_range: dash_to_duffs!(1)..=dash_to_duffs!(1),
                 },
 
                 identity_contract_nonce_gaps: None,
@@ -3906,6 +3926,7 @@ mod tests {
                         [(SecurityLevel::CRITICAL, vec![KeyType::ECDSA_SECP256K1])].into(),
                     )]
                     .into(),
+                    start_balance_range: dash_to_duffs!(1)..=dash_to_duffs!(1),
                 },
 
                 identity_contract_nonce_gaps: None,
@@ -3975,6 +3996,7 @@ mod tests {
                     },
                     start_keys: 5,
                     extra_keys: Default::default(),
+                    start_balance_range: dash_to_duffs!(1)..=dash_to_duffs!(1),
                 },
 
                 ..Default::default()
diff --git a/packages/rs-drive-abci/tests/strategy_tests/strategy.rs b/packages/rs-drive-abci/tests/strategy_tests/strategy.rs
index 13f22b16f89..667b8468688 100644
--- a/packages/rs-drive-abci/tests/strategy_tests/strategy.rs
+++ b/packages/rs-drive-abci/tests/strategy_tests/strategy.rs
@@ -14,7 +14,8 @@ use dpp::state_transition::identity_topup_transition::IdentityTopUpTransition;
 use strategy_tests::frequency::Frequency;
 use strategy_tests::operations::FinalizeBlockOperation::IdentityAddKeys;
 use strategy_tests::operations::{
-    DocumentAction, DocumentOp, FinalizeBlockOperation, IdentityUpdateOp, OperationType,
+    AmountRange, DocumentAction, DocumentOp, FinalizeBlockOperation, IdentityUpdateOp,
+    OperationType,
 };
 
 use dpp::document::DocumentV0Getters;
@@ -40,9 +41,10 @@ use drive_abci::rpc::core::MockCoreRPCLike;
 use rand::prelude::{IteratorRandom, SliceRandom, StdRng};
 use rand::Rng;
 use strategy_tests::Strategy;
-use strategy_tests::transitions::{create_state_transitions_for_identities, create_state_transitions_for_identities_and_proofs, instant_asset_lock_proof_fixture};
+use strategy_tests::transitions::{create_state_transitions_for_identities, create_state_transitions_for_identities_and_proofs, instant_asset_lock_proof_fixture, instant_asset_lock_proof_fixture_with_dynamic_range};
 use std::borrow::Cow;
 use std::collections::{BTreeMap, HashMap, HashSet};
+use std::ops::RangeInclusive;
 use std::str::FromStr;
 use tenderdash_abci::proto::abci::{ExecTxResult, ValidatorSetUpdate};
 use dpp::dashcore::hashes::Hash;
@@ -1118,7 +1120,7 @@ impl NetworkStrategy {
                             operations.push(document_batch_transition);
                         }
                     }
-                    OperationType::IdentityTopUp if !current_identities.is_empty() => {
+                    OperationType::IdentityTopUp(amount) if !current_identities.is_empty() => {
                         let indices: Vec<usize> =
                             (0..current_identities.len()).choose_multiple(rng, count as usize);
                         let random_identities: Vec<&Identity> = indices
@@ -1130,6 +1132,7 @@ impl NetworkStrategy {
                             operations.push(self.create_identity_top_up_transition(
                                 rng,
                                 random_identity,
+                                amount,
                                 instant_lock_quorums,
                                 &platform.config,
                                 platform_version,
@@ -1177,7 +1180,7 @@ impl NetworkStrategy {
                             }
                         }
                     }
-                    OperationType::IdentityWithdrawal if !current_identities.is_empty() => {
+                    OperationType::IdentityWithdrawal(amount) if !current_identities.is_empty() => {
                         let indices: Vec<usize> =
                             (0..current_identities.len()).choose_multiple(rng, count as usize);
                         for index in indices {
@@ -1185,6 +1188,7 @@ impl NetworkStrategy {
                             let state_transition =
                                 strategy_tests::transitions::create_identity_withdrawal_transition(
                                     random_identity,
+                                    amount.clone(),
                                     identity_nonce_counter,
                                     signer,
                                     rng,
@@ -1491,6 +1495,7 @@ impl NetworkStrategy {
     ) -> Vec<(Identity, StateTransition)> {
         let key_count = self.strategy.identity_inserts.start_keys as KeyID;
         let extra_keys = &self.strategy.identity_inserts.extra_keys;
+        let balance_range = &self.strategy.identity_inserts.start_balance_range;
 
         let (mut identities, mut keys) = Identity::random_identities_with_private_keys_with_rng::<
             Vec<_>,
@@ -1524,6 +1529,7 @@ impl NetworkStrategy {
         if self.sign_instant_locks {
             let identities_with_proofs = create_signed_instant_asset_lock_proofs_for_identities(
                 identities,
+                balance_range,
                 rng,
                 instant_lock_quorums,
                 platform_config,
@@ -1536,7 +1542,13 @@ impl NetworkStrategy {
                 platform_version,
             )
         } else {
-            create_state_transitions_for_identities(identities, signer, rng, platform_version)
+            create_state_transitions_for_identities(
+                identities,
+                balance_range,
+                signer,
+                rng,
+                platform_version,
+            )
         }
     }
 
@@ -1545,6 +1557,7 @@ impl NetworkStrategy {
         &self,
         rng: &mut StdRng,
         identity: &Identity,
+        amount_range: &AmountRange,
         instant_lock_quorums: &Quorums<SigningQuorum>,
         platform_config: &PlatformConfig,
         platform_version: &PlatformVersion,
@@ -1554,8 +1567,11 @@ impl NetworkStrategy {
             .unwrap();
         let sk: [u8; 32] = pk.try_into().unwrap();
         let secret_key = SecretKey::from_str(hex::encode(sk).as_str()).unwrap();
-        let mut asset_lock_proof =
-            instant_asset_lock_proof_fixture(PrivateKey::new(secret_key, Network::Dash));
+        let mut asset_lock_proof = instant_asset_lock_proof_fixture_with_dynamic_range(
+            PrivateKey::new(secret_key, Network::Dash),
+            amount_range,
+            rng,
+        );
 
         // Sign transaction and update signature in instant lock proof
         if self.sign_instant_locks {
@@ -1636,6 +1652,7 @@ pub struct ChainExecutionOutcome<'a> {
     /// height to the validator set update at that height
     pub validator_set_updates: BTreeMap<u64, ValidatorSetUpdate>,
     pub state_transition_results_per_block: BTreeMap<u64, Vec<(StateTransition, ExecTxResult)>>,
+    pub signer: SimpleSigner,
 }
 
 impl<'a> ChainExecutionOutcome<'a> {
@@ -1667,6 +1684,7 @@ pub struct ChainExecutionParameters {
 
 fn create_signed_instant_asset_lock_proofs_for_identities(
     identities: Vec<Identity>,
+    balance_range: &RangeInclusive<Credits>,
     rng: &mut StdRng,
     instant_lock_quorums: &Quorums<SigningQuorum>,
     platform_config: &PlatformConfig,
@@ -1691,7 +1709,11 @@ fn create_signed_instant_asset_lock_proofs_for_identities(
             let secret_key = SecretKey::from_str(hex::encode(pk_fixed).as_str()).unwrap();
             let private_key = PrivateKey::new(secret_key, Network::Dash);
 
-            let mut asset_lock_proof = instant_asset_lock_proof_fixture(private_key);
+            let mut asset_lock_proof = instant_asset_lock_proof_fixture_with_dynamic_range(
+                private_key,
+                balance_range,
+                rng,
+            );
 
             // Sign transaction and update instant lock
             let AssetLockProof::Instant(InstantAssetLockProof { instant_lock, .. }) =
diff --git a/packages/rs-drive-abci/tests/strategy_tests/voting_tests.rs b/packages/rs-drive-abci/tests/strategy_tests/voting_tests.rs
index c27e5e16f51..e14f8d7b1b0 100644
--- a/packages/rs-drive-abci/tests/strategy_tests/voting_tests.rs
+++ b/packages/rs-drive-abci/tests/strategy_tests/voting_tests.rs
@@ -24,6 +24,7 @@ mod tests {
     use dapi_grpc::platform::v0::get_contested_resource_vote_state_response::get_contested_resource_vote_state_response_v0::FinishedVoteInfo;
     use dapi_grpc::platform::v0::get_contested_resource_vote_state_response::get_contested_resource_vote_state_response_v0::finished_vote_info::FinishedVoteOutcome;
     use dpp::block::extended_block_info::v0::ExtendedBlockInfoV0Getters;
+    use dpp::dash_to_duffs;
     use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters;
     use dpp::state_transition::StateTransition;
     use dpp::voting::vote_choices::resource_vote_choice::ResourceVoteChoice;
@@ -80,6 +81,7 @@ mod tests {
 
         let start_identities = create_state_transitions_for_identities(
             vec![identity1],
+            &(dash_to_duffs!(1)..=dash_to_duffs!(1)),
             &simple_signer,
             &mut rng,
             platform_version,
@@ -363,6 +365,7 @@ mod tests {
 
         let start_identities = create_state_transitions_for_identities(
             vec![identity1, identity2],
+            &(dash_to_duffs!(1)..=dash_to_duffs!(1)),
             &simple_signer,
             &mut rng,
             platform_version,
@@ -634,6 +637,7 @@ mod tests {
 
         let start_identities = create_state_transitions_for_identities(
             vec![identity1, identity2],
+            &(dash_to_duffs!(1)..=dash_to_duffs!(1)),
             &simple_signer,
             &mut rng,
             platform_version,
@@ -986,6 +990,7 @@ mod tests {
 
         let start_identities = create_state_transitions_for_identities(
             vec![identity1, identity2],
+            &(dash_to_duffs!(1)..=dash_to_duffs!(1)),
             &simple_signer,
             &mut rng,
             platform_version,
@@ -1350,6 +1355,7 @@ mod tests {
 
         let start_identities = create_state_transitions_for_identities(
             vec![identity1, identity2],
+            &(dash_to_duffs!(1)..=dash_to_duffs!(1)),
             &simple_signer,
             &mut rng,
             platform_version,
diff --git a/packages/rs-drive-abci/tests/strategy_tests/withdrawal_tests.rs b/packages/rs-drive-abci/tests/strategy_tests/withdrawal_tests.rs
index 6113c154740..e0c33984174 100644
--- a/packages/rs-drive-abci/tests/strategy_tests/withdrawal_tests.rs
+++ b/packages/rs-drive-abci/tests/strategy_tests/withdrawal_tests.rs
@@ -12,7 +12,14 @@ mod tests {
     use dpp::data_contracts::withdrawals_contract;
     use dpp::identity::{KeyType, Purpose, SecurityLevel};
     use dpp::withdrawal::WithdrawalTransactionIndex;
+    use dpp::{dash_to_credits, dash_to_duffs};
     use drive::config::DEFAULT_QUERY_LIMIT;
+    use drive::drive::balances::TOTAL_SYSTEM_CREDITS_STORAGE_KEY;
+    use drive::drive::identity::withdrawals::paths::{
+        get_withdrawal_root_path, WITHDRAWAL_TRANSACTIONS_SUM_AMOUNT_TREE_KEY,
+    };
+    use drive::drive::system::misc_path;
+    use drive::util::grove_operations::DirectQueryType;
     use drive_abci::config::{
         ChainLockConfig, ExecutionConfig, InstantLockConfig, PlatformConfig, PlatformTestConfig,
         ValidatorSetConfig,
@@ -32,29 +39,20 @@ mod tests {
     }
 
     #[test]
-    fn run_chain_top_up_and_withdraw_from_identities() {
+    fn run_chain_withdraw_from_identities() {
         // TEST_PLATFORM_V3 is like v4, but without the single quorum can sign withdrawals restriction
         let platform_version = PlatformVersion::get(TEST_PLATFORM_V3.protocol_version)
             .expect("expected to get platform version");
-        let strategy = NetworkStrategy {
+        let start_strategy = NetworkStrategy {
             strategy: Strategy {
                 start_contracts: vec![],
-                operations: vec![
-                    Operation {
-                        op_type: OperationType::IdentityTopUp,
-                        frequency: Frequency {
-                            times_per_block_range: 1..4,
-                            chance_per_block: None,
-                        },
-                    },
-                    Operation {
-                        op_type: OperationType::IdentityWithdrawal,
-                        frequency: Frequency {
-                            times_per_block_range: 1..4,
-                            chance_per_block: None,
-                        },
+                operations: vec![Operation {
+                    op_type: OperationType::IdentityTopUp(dash_to_duffs!(10)..=dash_to_duffs!(10)),
+                    frequency: Frequency {
+                        times_per_block_range: 1..4,
+                        chance_per_block: None,
                     },
-                ],
+                }],
                 start_identities: StartIdentities::default(),
                 identity_inserts: IdentityInsertInfo {
                     frequency: Frequency {
@@ -67,6 +65,7 @@ mod tests {
                         [(SecurityLevel::CRITICAL, vec![KeyType::ECDSA_SECP256K1])].into(),
                     )]
                     .into(),
+                    start_balance_range: dash_to_duffs!(1)..=dash_to_duffs!(1),
                 },
                 identity_contract_nonce_gaps: None,
                 signer: None,
@@ -81,11 +80,11 @@ mod tests {
             rotate_quorums: false,
             failure_testing: None,
             query_testing: None,
-            // because we can add an identity and withdraw from it in the same block
-            // the result would be different from what would be expected
-            verify_state_transition_results: false,
+            verify_state_transition_results: true,
             ..Default::default()
         };
+
+        let hour_in_ms = 1000 * 60 * 60;
         let config = PlatformConfig {
             validator_set: ValidatorSetConfig::default_100_67(),
             chain_lock: ChainLockConfig::default_100_67(),
@@ -95,7 +94,7 @@ mod tests {
 
                 ..Default::default()
             },
-            block_spacing_ms: 3000,
+            block_spacing_ms: hour_in_ms,
             initial_protocol_version: TEST_PLATFORM_V3.protocol_version,
             testing_configs: PlatformTestConfig::default_minimal_verifications(),
             ..Default::default()
@@ -155,7 +154,135 @@ mod tests {
 
         // Run first two blocks:
         // - Block 1: creates identity
-        // - Block 2: tops up identity and initiates withdrawals
+        // - Block 2: tops up identity
+        let ChainExecutionOutcome {
+            abci_app,
+            proposers,
+            validator_quorums: quorums,
+            current_validator_quorum_hash: current_quorum_hash,
+            current_proposer_versions,
+            end_time_ms,
+            identity_nonce_counter,
+            identity_contract_nonce_counter,
+            instant_lock_quorums,
+            identities,
+            signer,
+            ..
+        } = {
+            let outcome = run_chain_for_strategy(
+                &mut platform,
+                2,
+                start_strategy,
+                config.clone(),
+                1,
+                &mut None,
+            );
+
+            for tx_results_per_block in outcome.state_transition_results_per_block.values() {
+                for (state_transition, result) in tx_results_per_block {
+                    assert_eq!(
+                        result.code, 0,
+                        "state transition got code {} : {:?}",
+                        result.code, state_transition
+                    );
+                }
+            }
+
+            // Withdrawal transactions are not populated to block execution context yet
+            assert_eq!(outcome.withdrawals.len(), 0);
+
+            // Withdrawal documents with pooled status should exist.
+            let withdrawal_documents_pooled = outcome
+                .abci_app
+                .platform
+                .drive
+                .fetch_oldest_withdrawal_documents_by_status(
+                    withdrawals_contract::WithdrawalStatus::POOLED.into(),
+                    DEFAULT_QUERY_LIMIT,
+                    None,
+                    platform_version,
+                )
+                .unwrap();
+            assert!(withdrawal_documents_pooled.is_empty());
+
+            let locked_amount = outcome
+                .abci_app
+                .platform
+                .drive
+                .grove_get_sum_tree_total_value(
+                    (&get_withdrawal_root_path()).into(),
+                    &WITHDRAWAL_TRANSACTIONS_SUM_AMOUNT_TREE_KEY,
+                    DirectQueryType::StatefulDirectQuery,
+                    None,
+                    &mut vec![],
+                    &platform_version.drive,
+                )
+                .expect("expected to get locked amount");
+
+            assert_eq!(locked_amount, 0);
+
+            let pooled_withdrawals = withdrawal_documents_pooled.len();
+
+            assert_eq!(pooled_withdrawals, 0);
+
+            outcome
+        };
+
+        let continue_strategy_only_withdrawal = NetworkStrategy {
+            strategy: Strategy {
+                start_contracts: vec![],
+                operations: vec![Operation {
+                    op_type: OperationType::IdentityWithdrawal(
+                        dash_to_credits!(0.1)..=dash_to_credits!(0.1),
+                    ),
+                    frequency: Frequency {
+                        times_per_block_range: 1..2,
+                        chance_per_block: None,
+                    },
+                }],
+                start_identities: StartIdentities::default(),
+                identity_inserts: IdentityInsertInfo::default(),
+                identity_contract_nonce_gaps: None,
+                signer: Some(signer.clone()),
+            },
+            total_hpmns: 100,
+            extra_normal_mns: 0,
+            validator_quorum_count: 24,
+            chain_lock_quorum_count: 24,
+            upgrading_info: None,
+
+            proposer_strategy: Default::default(),
+            rotate_quorums: false,
+            failure_testing: None,
+            query_testing: None,
+            verify_state_transition_results: true,
+            ..Default::default()
+        };
+
+        let continue_strategy_no_operations = NetworkStrategy {
+            strategy: Strategy {
+                start_contracts: vec![],
+                operations: vec![],
+                start_identities: StartIdentities::default(),
+                identity_inserts: IdentityInsertInfo::default(),
+                identity_contract_nonce_gaps: None,
+                signer: Some(signer),
+            },
+            total_hpmns: 100,
+            extra_normal_mns: 0,
+            validator_quorum_count: 24,
+            chain_lock_quorum_count: 24,
+            upgrading_info: None,
+
+            proposer_strategy: Default::default(),
+            rotate_quorums: false,
+            failure_testing: None,
+            query_testing: None,
+            verify_state_transition_results: true,
+            ..Default::default()
+        };
+
+        // Run Block 3: initiates a withdrawal
         let (
             ChainExecutionOutcome {
                 abci_app,
@@ -167,20 +294,36 @@ mod tests {
                 identity_nonce_counter,
                 identity_contract_nonce_counter,
                 instant_lock_quorums,
+                identities,
                 ..
             },
             last_block_pooled_withdrawals_amount,
         ) = {
-            let outcome = run_chain_for_strategy(
-                &mut platform,
-                2,
-                strategy.clone(),
+            let outcome = continue_chain_for_strategy(
+                abci_app,
+                ChainExecutionParameters {
+                    block_start: 3,
+                    core_height_start: 1,
+                    block_count: 1,
+                    proposers,
+                    validator_quorums: quorums,
+                    current_validator_quorum_hash: current_quorum_hash,
+                    current_proposer_versions: Some(current_proposer_versions),
+                    current_identity_nonce_counter: identity_nonce_counter,
+                    current_identity_contract_nonce_counter: identity_contract_nonce_counter,
+                    current_votes: BTreeMap::default(),
+                    start_time_ms: GENESIS_TIME_MS,
+                    current_time_ms: end_time_ms,
+                    instant_lock_quorums,
+                    current_identities: identities,
+                },
+                continue_strategy_only_withdrawal.clone(),
                 config.clone(),
-                1,
-                &mut None,
+                StrategyRandomness::SeedEntropy(2),
             );
 
             for tx_results_per_block in outcome.state_transition_results_per_block.values() {
+                assert_eq!(tx_results_per_block.len(), 1);
                 for (state_transition, result) in tx_results_per_block {
                     assert_eq!(
                         result.code, 0,
@@ -206,12 +349,29 @@ mod tests {
                 )
                 .unwrap();
             assert!(!withdrawal_documents_pooled.is_empty());
+
+            let locked_amount = outcome
+                .abci_app
+                .platform
+                .drive
+                .grove_get_sum_tree_total_value(
+                    (&get_withdrawal_root_path()).into(),
+                    &WITHDRAWAL_TRANSACTIONS_SUM_AMOUNT_TREE_KEY,
+                    DirectQueryType::StatefulDirectQuery,
+                    None,
+                    &mut vec![],
+                    &platform_version.drive,
+                )
+                .expect("expected to get locked amount");
+
+            assert_eq!(locked_amount, dash_to_credits!(0.1) as i64);
+
             let pooled_withdrawals = withdrawal_documents_pooled.len();
 
             (outcome, pooled_withdrawals)
         };
 
-        // Run block 3
+        // Run block 4
         // Should broadcast previously pooled withdrawals to core
         let ChainExecutionOutcome {
             abci_app,
@@ -224,12 +384,13 @@ mod tests {
             identity_nonce_counter,
             identity_contract_nonce_counter,
             instant_lock_quorums,
+            identities,
             ..
         } = {
             let outcome = continue_chain_for_strategy(
                 abci_app,
                 ChainExecutionParameters {
-                    block_start: 3,
+                    block_start: 4,
                     core_height_start: 1,
                     block_count: 1,
                     proposers,
@@ -242,11 +403,11 @@ mod tests {
                     start_time_ms: GENESIS_TIME_MS,
                     current_time_ms: end_time_ms,
                     instant_lock_quorums,
-                    current_identities: Vec::new(),
+                    current_identities: identities,
                 },
-                strategy.clone(),
+                continue_strategy_no_operations.clone(),
                 config.clone(),
-                StrategyRandomness::SeedEntropy(2),
+                StrategyRandomness::SeedEntropy(3),
             );
 
             // Withdrawal documents with pooled status should exist.
@@ -272,6 +433,22 @@ mod tests {
                 last_block_pooled_withdrawals_amount
             );
 
+            let locked_amount = outcome
+                .abci_app
+                .platform
+                .drive
+                .grove_get_sum_tree_total_value(
+                    (&get_withdrawal_root_path()).into(),
+                    &WITHDRAWAL_TRANSACTIONS_SUM_AMOUNT_TREE_KEY,
+                    DirectQueryType::StatefulDirectQuery,
+                    None,
+                    &mut vec![],
+                    &platform_version.drive,
+                )
+                .expect("expected to get locked amount");
+
+            assert_eq!(locked_amount, dash_to_credits!(0.1) as i64);
+
             outcome
         };
 
@@ -292,29 +469,26 @@ mod tests {
             });
         }
 
-        // Run block 4
-        // Should change pooled status to broadcasted
-        let last_block_broadcasted_withdrawals_amount = last_block_withdrawals.len();
-        let (
-            ChainExecutionOutcome {
-                abci_app,
-                proposers,
-                validator_quorums: quorums,
-                current_validator_quorum_hash: current_quorum_hash,
-                current_proposer_versions,
-                end_time_ms,
-                withdrawals: last_block_withdrawals,
-                identity_nonce_counter,
-                identity_contract_nonce_counter,
-                instant_lock_quorums,
-                ..
-            },
-            last_block_broadcased_withdrawals_amount,
-        ) = {
+        // Run block 5
+        // Should change do nothing, because core doesn't report any change
+        let ChainExecutionOutcome {
+            abci_app,
+            proposers,
+            validator_quorums: quorums,
+            current_validator_quorum_hash: current_quorum_hash,
+            current_proposer_versions,
+            end_time_ms,
+            withdrawals: last_block_withdrawals,
+            identity_nonce_counter,
+            identity_contract_nonce_counter,
+            instant_lock_quorums,
+            identities,
+            ..
+        } = {
             let outcome = continue_chain_for_strategy(
                 abci_app,
                 ChainExecutionParameters {
-                    block_start: 4,
+                    block_start: 5,
                     core_height_start: 1,
                     block_count: 1,
                     proposers,
@@ -327,49 +501,45 @@ mod tests {
                     start_time_ms: GENESIS_TIME_MS,
                     current_time_ms: end_time_ms + 1000,
                     instant_lock_quorums,
-                    current_identities: Vec::new(),
+                    current_identities: identities,
                 },
-                strategy.clone(),
+                continue_strategy_no_operations.clone(),
                 config.clone(),
-                StrategyRandomness::SeedEntropy(3),
+                StrategyRandomness::SeedEntropy(4),
             );
 
-            let withdrawal_documents_pooled = outcome
+            let withdrawal_documents_completed = outcome
                 .abci_app
                 .platform
                 .drive
                 .fetch_oldest_withdrawal_documents_by_status(
-                    withdrawals_contract::WithdrawalStatus::POOLED.into(),
+                    withdrawals_contract::WithdrawalStatus::COMPLETE.into(),
                     DEFAULT_QUERY_LIMIT,
                     None,
                     platform_version,
                 )
                 .unwrap();
 
-            let withdrawal_documents_broadcasted = outcome
+            // things have not changed
+            assert!(withdrawal_documents_completed.is_empty());
+
+            let locked_amount = outcome
                 .abci_app
                 .platform
                 .drive
-                .fetch_oldest_withdrawal_documents_by_status(
-                    withdrawals_contract::WithdrawalStatus::BROADCASTED.into(),
-                    DEFAULT_QUERY_LIMIT,
+                .grove_get_sum_tree_total_value(
+                    (&get_withdrawal_root_path()).into(),
+                    &WITHDRAWAL_TRANSACTIONS_SUM_AMOUNT_TREE_KEY,
+                    DirectQueryType::StatefulDirectQuery,
                     None,
-                    platform_version,
+                    &mut vec![],
+                    &platform_version.drive,
                 )
-                .unwrap();
-
-            // In this block we should have new withdrawals pooled
-            assert!(!withdrawal_documents_pooled.is_empty());
+                .expect("expected to get locked amount");
 
-            // And extra withdrawals broadcasted
-            let withdrawals_broadcasted_expected =
-                last_block_broadcasted_withdrawals_amount + outcome.withdrawals.len();
-            assert_eq!(
-                withdrawal_documents_broadcasted.len(),
-                withdrawals_broadcasted_expected
-            );
+            assert_eq!(locked_amount, dash_to_credits!(0.1) as i64);
 
-            (outcome, withdrawal_documents_broadcasted.len())
+            outcome
         };
 
         // Update core state for newly broadcasted transactions
@@ -408,29 +578,26 @@ mod tests {
             drop(core_state);
         }
 
-        // Run block 5
+        // Run block 6
         // Previously broadcasted transactions should be settled after block 5,
         // and their corresponding statuses should be changed to COMPLETED
-        let (
-            ChainExecutionOutcome {
-                abci_app,
-                proposers,
-                validator_quorums: quorums,
-                current_validator_quorum_hash: current_quorum_hash,
-                current_proposer_versions,
-                end_time_ms,
-                withdrawals: last_block_withdrawals,
-                identity_nonce_counter,
-                identity_contract_nonce_counter,
-                instant_lock_quorums,
-                ..
-            },
-            last_block_withdrawals_completed_amount,
-        ) = {
+        let ChainExecutionOutcome {
+            abci_app,
+            proposers,
+            validator_quorums: quorums,
+            current_validator_quorum_hash: current_quorum_hash,
+            current_proposer_versions,
+            end_time_ms,
+            identity_nonce_counter,
+            identity_contract_nonce_counter,
+            instant_lock_quorums,
+            identities,
+            ..
+        } = {
             let outcome = continue_chain_for_strategy(
                 abci_app,
                 ChainExecutionParameters {
-                    block_start: 5,
+                    block_start: 6,
                     core_height_start: 1,
                     block_count: 1,
                     proposers,
@@ -443,11 +610,11 @@ mod tests {
                     start_time_ms: GENESIS_TIME_MS,
                     current_time_ms: end_time_ms + 1000,
                     instant_lock_quorums,
-                    current_identities: Vec::new(),
+                    current_identities: identities,
                 },
-                strategy.clone(),
+                continue_strategy_no_operations.clone(),
                 config.clone(),
-                StrategyRandomness::SeedEntropy(4),
+                StrategyRandomness::SeedEntropy(5),
             );
 
             let withdrawal_documents_pooled = outcome
@@ -487,62 +654,1592 @@ mod tests {
                 .unwrap();
 
             // In this block we should have new withdrawals pooled
-            assert!(!withdrawal_documents_pooled.is_empty());
+            assert!(withdrawal_documents_pooled.is_empty());
+            assert!(withdrawal_documents_broadcasted.is_empty());
 
-            // And some withdrawals completed
-            let withdrawals_completed_expected =
-                // Withdrawals issued on {previous_block - 1} considered completed
-                last_block_broadcased_withdrawals_amount - last_block_withdrawals.len();
-            assert_eq!(
-                withdrawal_documents_completed.len(),
-                withdrawals_completed_expected
-            );
+            assert_eq!(withdrawal_documents_completed.len(), 1);
 
-            // And extra withdrawals broadcasted
-            let withdrawals_broadcasted_expected =
-                // Withdrawals issued on previous block + withdrawals from this block are still in broadcasted state
-                last_block_withdrawals.len() + outcome.withdrawals.len();
+            let locked_amount = outcome
+                .abci_app
+                .platform
+                .drive
+                .grove_get_sum_tree_total_value(
+                    (&get_withdrawal_root_path()).into(),
+                    &WITHDRAWAL_TRANSACTIONS_SUM_AMOUNT_TREE_KEY,
+                    DirectQueryType::StatefulDirectQuery,
+                    None,
+                    &mut vec![],
+                    &platform_version.drive,
+                )
+                .expect("expected to get locked amount");
 
-            assert_eq!(
-                withdrawal_documents_broadcasted.len(),
-                withdrawals_broadcasted_expected
-            );
+            assert_eq!(locked_amount, dash_to_credits!(0.1) as i64);
 
-            (outcome, withdrawal_documents_completed.len())
+            outcome
         };
 
-        // Update state of the core before proceeding to the next block
-        {
-            // Simulate transactions being added to the core mempool
-            let mut core_state = shared_core_state.lock().unwrap();
+        let outcome = continue_chain_for_strategy(
+            abci_app,
+            ChainExecutionParameters {
+                block_start: 7,
+                core_height_start: 1,
+                block_count: 24,
+                proposers,
+                validator_quorums: quorums,
+                current_validator_quorum_hash: current_quorum_hash,
+                current_proposer_versions: Some(current_proposer_versions),
+                current_identity_nonce_counter: identity_nonce_counter,
+                current_identity_contract_nonce_counter: identity_contract_nonce_counter,
+                current_votes: BTreeMap::default(),
+                start_time_ms: GENESIS_TIME_MS,
+                current_time_ms: end_time_ms + 1000,
+                instant_lock_quorums,
+                current_identities: identities,
+            },
+            continue_strategy_no_operations.clone(),
+            config.clone(),
+            StrategyRandomness::SeedEntropy(6),
+        );
 
-            let number_of_blocks_before_expiration: u32 = 48;
-            chain_locked_height += number_of_blocks_before_expiration;
+        // We should have unlocked the amounts by now
+        let locked_amount = outcome
+            .abci_app
+            .platform
+            .drive
+            .grove_get_sum_tree_total_value(
+                (&get_withdrawal_root_path()).into(),
+                &WITHDRAWAL_TRANSACTIONS_SUM_AMOUNT_TREE_KEY,
+                DirectQueryType::StatefulDirectQuery,
+                None,
+                &mut vec![],
+                &platform_version.drive,
+            )
+            .expect("expected to get locked amount");
 
-            core_state.chain_lock.block_height = chain_locked_height;
+        assert_eq!(locked_amount, 0);
+    }
 
-            last_block_withdrawals.iter().for_each(|tx| {
+    #[test]
+    fn run_chain_withdrawal_expired() {
+        // TEST_PLATFORM_V3 is like v4, but without the single quorum can sign withdrawals restriction
+        let platform_version = PlatformVersion::get(TEST_PLATFORM_V3.protocol_version)
+            .expect("expected to get platform version");
+        let start_strategy = NetworkStrategy {
+            strategy: Strategy {
+                start_contracts: vec![],
+                operations: vec![Operation {
+                    op_type: OperationType::IdentityTopUp(dash_to_duffs!(10)..=dash_to_duffs!(10)),
+                    frequency: Frequency {
+                        times_per_block_range: 1..4,
+                        chance_per_block: None,
+                    },
+                }],
+                start_identities: StartIdentities::default(),
+                identity_inserts: IdentityInsertInfo {
+                    frequency: Frequency {
+                        times_per_block_range: 1..2,
+                        chance_per_block: None,
+                    },
+                    start_keys: 3,
+                    extra_keys: [(
+                        Purpose::TRANSFER,
+                        [(SecurityLevel::CRITICAL, vec![KeyType::ECDSA_SECP256K1])].into(),
+                    )]
+                    .into(),
+                    start_balance_range: dash_to_duffs!(1)..=dash_to_duffs!(1),
+                },
+                identity_contract_nonce_gaps: None,
+                signer: None,
+            },
+            total_hpmns: 100,
+            extra_normal_mns: 0,
+            validator_quorum_count: 24,
+            chain_lock_quorum_count: 24,
+            upgrading_info: None,
+
+            proposer_strategy: Default::default(),
+            rotate_quorums: false,
+            failure_testing: None,
+            query_testing: None,
+            verify_state_transition_results: true,
+            ..Default::default()
+        };
+
+        let hour_in_ms = 1000 * 60 * 60;
+        let config = PlatformConfig {
+            validator_set: ValidatorSetConfig::default_100_67(),
+            chain_lock: ChainLockConfig::default_100_67(),
+            instant_lock: InstantLockConfig::default_100_67(),
+            execution: ExecutionConfig {
+                verify_sum_trees: true,
+
+                ..Default::default()
+            },
+            block_spacing_ms: hour_in_ms,
+            initial_protocol_version: TEST_PLATFORM_V3.protocol_version,
+            testing_configs: PlatformTestConfig::default_minimal_verifications(),
+            ..Default::default()
+        };
+
+        let mut platform = TestPlatformBuilder::new()
+            .with_config(config.clone())
+            .build_with_mock_rpc();
+
+        platform
+            .core_rpc
+            .expect_send_raw_transaction()
+            .returning(move |_| Ok(Txid::all_zeros()));
+
+        let mut chain_locked_height = 1;
+
+        // Have to go with a complicated shared object for the core state because we need to change
+        // rpc response along the way but we can't mutate `platform.core_rpc` later
+        // because platform reference is moved into the AbciApplication.
+        let shared_core_state = Arc::new(Mutex::new(CoreState {
+            asset_unlock_statuses: BTreeMap::new(),
+            chain_lock: ChainLock {
+                block_height: chain_locked_height,
+                block_hash: BlockHash::from_byte_array([1; 32]),
+                signature: BLSSignature::from([2; 96]),
+            },
+        }));
+
+        // Set up Core RPC responses
+        {
+            let core_state = shared_core_state.clone();
+
+            platform
+                .core_rpc
+                .expect_get_asset_unlock_statuses()
+                .returning(move |indices, _| {
+                    Ok(indices
+                        .iter()
+                        .map(|index| {
+                            core_state
+                                .lock()
+                                .unwrap()
+                                .asset_unlock_statuses
+                                .get(index)
+                                .cloned()
+                                .unwrap()
+                        })
+                        .collect())
+                });
+
+            let core_state = shared_core_state.clone();
+            platform
+                .core_rpc
+                .expect_get_best_chain_lock()
+                .returning(move || Ok(core_state.lock().unwrap().chain_lock.clone()));
+        }
+
+        // Run first two blocks:
+        // - Block 1: creates identity
+        // - Block 2: tops up identity
+        let ChainExecutionOutcome {
+            abci_app,
+            proposers,
+            validator_quorums: quorums,
+            current_validator_quorum_hash: current_quorum_hash,
+            current_proposer_versions,
+            end_time_ms,
+            identity_nonce_counter,
+            identity_contract_nonce_counter,
+            instant_lock_quorums,
+            identities,
+            signer,
+            ..
+        } = {
+            let outcome = run_chain_for_strategy(
+                &mut platform,
+                2,
+                start_strategy,
+                config.clone(),
+                1,
+                &mut None,
+            );
+
+            for tx_results_per_block in outcome.state_transition_results_per_block.values() {
+                for (state_transition, result) in tx_results_per_block {
+                    assert_eq!(
+                        result.code, 0,
+                        "state transition got code {} : {:?}",
+                        result.code, state_transition
+                    );
+                }
+            }
+
+            // Withdrawal transactions are not populated to block execution context yet
+            assert_eq!(outcome.withdrawals.len(), 0);
+
+            // Withdrawal documents with pooled status should exist.
+            let withdrawal_documents_pooled = outcome
+                .abci_app
+                .platform
+                .drive
+                .fetch_oldest_withdrawal_documents_by_status(
+                    withdrawals_contract::WithdrawalStatus::POOLED.into(),
+                    DEFAULT_QUERY_LIMIT,
+                    None,
+                    platform_version,
+                )
+                .unwrap();
+            assert!(withdrawal_documents_pooled.is_empty());
+
+            let locked_amount = outcome
+                .abci_app
+                .platform
+                .drive
+                .grove_get_sum_tree_total_value(
+                    (&get_withdrawal_root_path()).into(),
+                    &WITHDRAWAL_TRANSACTIONS_SUM_AMOUNT_TREE_KEY,
+                    DirectQueryType::StatefulDirectQuery,
+                    None,
+                    &mut vec![],
+                    &platform_version.drive,
+                )
+                .expect("expected to get locked amount");
+
+            assert_eq!(locked_amount, 0);
+
+            let pooled_withdrawals = withdrawal_documents_pooled.len();
+
+            assert_eq!(pooled_withdrawals, 0);
+
+            outcome
+        };
+
+        let continue_strategy_only_withdrawal = NetworkStrategy {
+            strategy: Strategy {
+                start_contracts: vec![],
+                operations: vec![Operation {
+                    op_type: OperationType::IdentityWithdrawal(
+                        dash_to_credits!(0.1)..=dash_to_credits!(0.1),
+                    ),
+                    frequency: Frequency {
+                        times_per_block_range: 1..2,
+                        chance_per_block: None,
+                    },
+                }],
+                start_identities: StartIdentities::default(),
+                identity_inserts: IdentityInsertInfo::default(),
+                identity_contract_nonce_gaps: None,
+                signer: Some(signer.clone()),
+            },
+            total_hpmns: 100,
+            extra_normal_mns: 0,
+            validator_quorum_count: 24,
+            chain_lock_quorum_count: 24,
+            upgrading_info: None,
+
+            proposer_strategy: Default::default(),
+            rotate_quorums: false,
+            failure_testing: None,
+            query_testing: None,
+            verify_state_transition_results: true,
+            ..Default::default()
+        };
+
+        let continue_strategy_no_operations = NetworkStrategy {
+            strategy: Strategy {
+                start_contracts: vec![],
+                operations: vec![],
+                start_identities: StartIdentities::default(),
+                identity_inserts: IdentityInsertInfo::default(),
+                identity_contract_nonce_gaps: None,
+                signer: Some(signer),
+            },
+            total_hpmns: 100,
+            extra_normal_mns: 0,
+            validator_quorum_count: 24,
+            chain_lock_quorum_count: 24,
+            upgrading_info: None,
+
+            proposer_strategy: Default::default(),
+            rotate_quorums: false,
+            failure_testing: None,
+            query_testing: None,
+            verify_state_transition_results: true,
+            ..Default::default()
+        };
+
+        // Run Block 3: initiates a withdrawal
+        let (
+            ChainExecutionOutcome {
+                abci_app,
+                proposers,
+                validator_quorums: quorums,
+                current_validator_quorum_hash: current_quorum_hash,
+                current_proposer_versions,
+                end_time_ms,
+                identity_nonce_counter,
+                identity_contract_nonce_counter,
+                instant_lock_quorums,
+                identities,
+                ..
+            },
+            last_block_pooled_withdrawals_amount,
+        ) = {
+            let outcome = continue_chain_for_strategy(
+                abci_app,
+                ChainExecutionParameters {
+                    block_start: 3,
+                    core_height_start: 1,
+                    block_count: 1,
+                    proposers,
+                    validator_quorums: quorums,
+                    current_validator_quorum_hash: current_quorum_hash,
+                    current_proposer_versions: Some(current_proposer_versions),
+                    current_identity_nonce_counter: identity_nonce_counter,
+                    current_identity_contract_nonce_counter: identity_contract_nonce_counter,
+                    current_votes: BTreeMap::default(),
+                    start_time_ms: GENESIS_TIME_MS,
+                    current_time_ms: end_time_ms,
+                    instant_lock_quorums,
+                    current_identities: identities,
+                },
+                continue_strategy_only_withdrawal.clone(),
+                config.clone(),
+                StrategyRandomness::SeedEntropy(2),
+            );
+
+            for tx_results_per_block in outcome.state_transition_results_per_block.values() {
+                assert_eq!(tx_results_per_block.len(), 1);
+                for (state_transition, result) in tx_results_per_block {
+                    assert_eq!(
+                        result.code, 0,
+                        "state transition got code {} : {:?}",
+                        result.code, state_transition
+                    );
+                }
+            }
+
+            // Withdrawal transactions are not populated to block execution context yet
+            assert_eq!(outcome.withdrawals.len(), 0);
+
+            // Withdrawal documents with pooled status should exist.
+            let withdrawal_documents_pooled = outcome
+                .abci_app
+                .platform
+                .drive
+                .fetch_oldest_withdrawal_documents_by_status(
+                    withdrawals_contract::WithdrawalStatus::POOLED.into(),
+                    DEFAULT_QUERY_LIMIT,
+                    None,
+                    platform_version,
+                )
+                .unwrap();
+            assert!(!withdrawal_documents_pooled.is_empty());
+
+            let locked_amount = outcome
+                .abci_app
+                .platform
+                .drive
+                .grove_get_sum_tree_total_value(
+                    (&get_withdrawal_root_path()).into(),
+                    &WITHDRAWAL_TRANSACTIONS_SUM_AMOUNT_TREE_KEY,
+                    DirectQueryType::StatefulDirectQuery,
+                    None,
+                    &mut vec![],
+                    &platform_version.drive,
+                )
+                .expect("expected to get locked amount");
+
+            assert_eq!(locked_amount, dash_to_credits!(0.1) as i64);
+
+            let pooled_withdrawals = withdrawal_documents_pooled.len();
+
+            (outcome, pooled_withdrawals)
+        };
+
+        // Run block 4
+        // Should broadcast previously pooled withdrawals to core
+        let ChainExecutionOutcome {
+            abci_app,
+            proposers,
+            validator_quorums: quorums,
+            current_validator_quorum_hash: current_quorum_hash,
+            current_proposer_versions,
+            end_time_ms,
+            withdrawals: last_block_withdrawals,
+            identity_nonce_counter,
+            identity_contract_nonce_counter,
+            instant_lock_quorums,
+            identities,
+            ..
+        } = {
+            let outcome = continue_chain_for_strategy(
+                abci_app,
+                ChainExecutionParameters {
+                    block_start: 4,
+                    core_height_start: 1,
+                    block_count: 1,
+                    proposers,
+                    validator_quorums: quorums,
+                    current_validator_quorum_hash: current_quorum_hash,
+                    current_proposer_versions: Some(current_proposer_versions),
+                    current_identity_nonce_counter: identity_nonce_counter,
+                    current_identity_contract_nonce_counter: identity_contract_nonce_counter,
+                    current_votes: BTreeMap::default(),
+                    start_time_ms: GENESIS_TIME_MS,
+                    current_time_ms: end_time_ms,
+                    instant_lock_quorums,
+                    current_identities: identities,
+                },
+                continue_strategy_no_operations.clone(),
+                config.clone(),
+                StrategyRandomness::SeedEntropy(2),
+            );
+
+            // Withdrawal documents with pooled status should exist.
+            let withdrawal_documents_broadcasted = outcome
+                .abci_app
+                .platform
+                .drive
+                .fetch_oldest_withdrawal_documents_by_status(
+                    withdrawals_contract::WithdrawalStatus::BROADCASTED.into(),
+                    DEFAULT_QUERY_LIMIT,
+                    None,
+                    platform_version,
+                )
+                .unwrap();
+
+            // In this block all previously pooled withdrawals should be broadcasted
+            assert_eq!(
+                outcome.withdrawals.len(),
+                last_block_pooled_withdrawals_amount
+            );
+            assert_eq!(
+                withdrawal_documents_broadcasted.len(),
+                last_block_pooled_withdrawals_amount
+            );
+
+            outcome
+        };
+
+        // Update state of the core before proceeding to the next block
+        {
+            // Simulate transactions being added to the core mempool
+            let mut core_state = shared_core_state.lock().unwrap();
+
+            let number_of_blocks_before_expiration: u32 = 48;
+            chain_locked_height += number_of_blocks_before_expiration;
+
+            core_state.chain_lock.block_height = chain_locked_height;
+
+            last_block_withdrawals.iter().for_each(|tx| {
                 let index = asset_unlock_index(tx);
 
-                core_state.asset_unlock_statuses.insert(
-                    index,
-                    AssetUnlockStatusResult {
-                        index,
-                        status: AssetUnlockStatus::Unknown,
-                    },
-                );
-            });
+                core_state.asset_unlock_statuses.insert(
+                    index,
+                    AssetUnlockStatusResult {
+                        index,
+                        status: AssetUnlockStatus::Unknown,
+                    },
+                );
+            });
+        }
+
+        // Run block 5
+        // Tests withdrawal expiration
+        let ChainExecutionOutcome { .. } = {
+            let outcome = continue_chain_for_strategy(
+                abci_app,
+                ChainExecutionParameters {
+                    block_start: 5,
+                    core_height_start: 2,
+                    block_count: 1,
+                    proposers,
+                    validator_quorums: quorums,
+                    current_validator_quorum_hash: current_quorum_hash,
+                    current_proposer_versions: Some(current_proposer_versions),
+                    current_identity_nonce_counter: identity_nonce_counter,
+                    current_identity_contract_nonce_counter: identity_contract_nonce_counter,
+                    current_votes: BTreeMap::default(),
+                    start_time_ms: GENESIS_TIME_MS,
+                    current_time_ms: end_time_ms + 1000,
+                    instant_lock_quorums,
+                    current_identities: identities,
+                },
+                continue_strategy_no_operations.clone(),
+                config.clone(),
+                StrategyRandomness::SeedEntropy(5),
+            );
+
+            let withdrawal_documents_pooled = outcome
+                .abci_app
+                .platform
+                .drive
+                .fetch_oldest_withdrawal_documents_by_status(
+                    withdrawals_contract::WithdrawalStatus::POOLED.into(),
+                    DEFAULT_QUERY_LIMIT,
+                    None,
+                    platform_version,
+                )
+                .unwrap();
+
+            let withdrawal_documents_broadcasted = outcome
+                .abci_app
+                .platform
+                .drive
+                .fetch_oldest_withdrawal_documents_by_status(
+                    withdrawals_contract::WithdrawalStatus::BROADCASTED.into(),
+                    DEFAULT_QUERY_LIMIT,
+                    None,
+                    platform_version,
+                )
+                .unwrap();
+
+            let withdrawal_documents_completed = outcome
+                .abci_app
+                .platform
+                .drive
+                .fetch_oldest_withdrawal_documents_by_status(
+                    withdrawals_contract::WithdrawalStatus::COMPLETE.into(),
+                    DEFAULT_QUERY_LIMIT,
+                    None,
+                    platform_version,
+                )
+                .unwrap();
+
+            let withdrawal_documents_expired = outcome
+                .abci_app
+                .platform
+                .drive
+                .fetch_oldest_withdrawal_documents_by_status(
+                    withdrawals_contract::WithdrawalStatus::EXPIRED.into(),
+                    DEFAULT_QUERY_LIMIT,
+                    None,
+                    platform_version,
+                )
+                .unwrap();
+
+            assert!(withdrawal_documents_pooled.is_empty());
+            assert!(withdrawal_documents_completed.is_empty());
+
+            assert_eq!(withdrawal_documents_expired.len(), 1);
+
+            assert!(withdrawal_documents_broadcasted.is_empty());
+
+            outcome
+        };
+    }
+
+    #[test]
+    fn run_chain_withdraw_from_identities_too_many_withdrawals_within_a_day_hitting_limit() {
+        // TEST_PLATFORM_V3 is like v4, but without the single quorum can sign withdrawals restriction
+        let platform_version = PlatformVersion::get(TEST_PLATFORM_V3.protocol_version)
+            .expect("expected to get platform version");
+        let start_strategy = NetworkStrategy {
+            strategy: Strategy {
+                start_contracts: vec![],
+                operations: vec![Operation {
+                    op_type: OperationType::IdentityTopUp(dash_to_duffs!(10)..=dash_to_duffs!(10)),
+                    frequency: Frequency {
+                        times_per_block_range: 10..11,
+                        chance_per_block: None,
+                    },
+                }],
+                start_identities: StartIdentities::default(),
+                identity_inserts: IdentityInsertInfo {
+                    frequency: Frequency {
+                        times_per_block_range: 10..11,
+                        chance_per_block: None,
+                    },
+                    start_keys: 3,
+                    extra_keys: [(
+                        Purpose::TRANSFER,
+                        [(SecurityLevel::CRITICAL, vec![KeyType::ECDSA_SECP256K1])].into(),
+                    )]
+                    .into(),
+                    start_balance_range: dash_to_duffs!(200)..=dash_to_duffs!(200),
+                },
+                identity_contract_nonce_gaps: None,
+                signer: None,
+            },
+            total_hpmns: 100,
+            extra_normal_mns: 0,
+            validator_quorum_count: 24,
+            chain_lock_quorum_count: 24,
+            upgrading_info: None,
+
+            proposer_strategy: Default::default(),
+            rotate_quorums: false,
+            failure_testing: None,
+            query_testing: None,
+            verify_state_transition_results: true,
+            ..Default::default()
+        };
+
+        let minute_in_ms = 1000 * 60;
+        let config = PlatformConfig {
+            validator_set: ValidatorSetConfig::default_100_67(),
+            chain_lock: ChainLockConfig::default_100_67(),
+            instant_lock: InstantLockConfig::default_100_67(),
+            execution: ExecutionConfig {
+                verify_sum_trees: true,
+
+                ..Default::default()
+            },
+            block_spacing_ms: minute_in_ms,
+            initial_protocol_version: TEST_PLATFORM_V3.protocol_version,
+            testing_configs: PlatformTestConfig::default_minimal_verifications(),
+            ..Default::default()
+        };
+
+        let mut platform = TestPlatformBuilder::new()
+            .with_config(config.clone())
+            .build_with_mock_rpc();
+
+        platform
+            .core_rpc
+            .expect_send_raw_transaction()
+            .returning(move |_| Ok(Txid::all_zeros()));
+
+        let chain_locked_height = 1;
+
+        // Have to go with a complicated shared object for the core state because we need to change
+        // rpc response along the way but we can't mutate `platform.core_rpc` later
+        // because platform reference is moved into the AbciApplication.
+        let shared_core_state = Arc::new(Mutex::new(CoreState {
+            asset_unlock_statuses: BTreeMap::new(),
+            chain_lock: ChainLock {
+                block_height: chain_locked_height,
+                block_hash: BlockHash::from_byte_array([1; 32]),
+                signature: BLSSignature::from([2; 96]),
+            },
+        }));
+
+        // Set up Core RPC responses
+        {
+            let core_state = shared_core_state.clone();
+
+            platform
+                .core_rpc
+                .expect_get_asset_unlock_statuses()
+                .returning(move |indices, _| {
+                    Ok(indices
+                        .iter()
+                        .map(|index| {
+                            core_state
+                                .lock()
+                                .unwrap()
+                                .asset_unlock_statuses
+                                .get(index)
+                                .cloned()
+                                .unwrap()
+                        })
+                        .collect())
+                });
+
+            let core_state = shared_core_state.clone();
+            platform
+                .core_rpc
+                .expect_get_best_chain_lock()
+                .returning(move || Ok(core_state.lock().unwrap().chain_lock.clone()));
+        }
+
+        // Run first two blocks:
+        // - Block 1: creates identity
+        // - Block 2: tops up identity
+        let ChainExecutionOutcome {
+            abci_app,
+            proposers,
+            validator_quorums: quorums,
+            current_validator_quorum_hash: current_quorum_hash,
+            current_proposer_versions,
+            end_time_ms,
+            identity_nonce_counter,
+            identity_contract_nonce_counter,
+            instant_lock_quorums,
+            identities,
+            signer,
+            ..
+        } = {
+            let outcome = run_chain_for_strategy(
+                &mut platform,
+                2,
+                start_strategy,
+                config.clone(),
+                1,
+                &mut None,
+            );
+
+            for tx_results_per_block in outcome.state_transition_results_per_block.values() {
+                for (state_transition, result) in tx_results_per_block {
+                    assert_eq!(
+                        result.code, 0,
+                        "state transition got code {} : {:?}",
+                        result.code, state_transition
+                    );
+                }
+            }
+
+            // Withdrawal transactions are not populated to block execution context yet
+            assert_eq!(outcome.withdrawals.len(), 0);
+
+            // Withdrawal documents with pooled status should exist.
+            let withdrawal_documents_pooled = outcome
+                .abci_app
+                .platform
+                .drive
+                .fetch_oldest_withdrawal_documents_by_status(
+                    withdrawals_contract::WithdrawalStatus::POOLED.into(),
+                    DEFAULT_QUERY_LIMIT,
+                    None,
+                    platform_version,
+                )
+                .unwrap();
+            assert!(withdrawal_documents_pooled.is_empty());
+
+            let locked_amount = outcome
+                .abci_app
+                .platform
+                .drive
+                .grove_get_sum_tree_total_value(
+                    (&get_withdrawal_root_path()).into(),
+                    &WITHDRAWAL_TRANSACTIONS_SUM_AMOUNT_TREE_KEY,
+                    DirectQueryType::StatefulDirectQuery,
+                    None,
+                    &mut vec![],
+                    &platform_version.drive,
+                )
+                .expect("expected to get locked amount");
+
+            assert_eq!(locked_amount, 0);
+
+            let pooled_withdrawals = withdrawal_documents_pooled.len();
+
+            assert_eq!(pooled_withdrawals, 0);
+
+            let total_credits_balance = outcome
+                .abci_app
+                .platform
+                .drive
+                .calculate_total_credits_balance(None, &platform_version.drive)
+                .expect("expected to get total credits balance");
+
+            assert_eq!(
+                total_credits_balance.total_identity_balances,
+                409997575280380
+            ); // Around 4100 Dash.
+
+            assert_eq!(
+                total_credits_balance.total_in_trees().unwrap(),
+                410000000000000
+            ); // Around 4100 Dash.
+
+            let total_credits_in_platform = outcome
+                .abci_app
+                .platform
+                .drive
+                .grove_get_raw_value_u64_from_encoded_var_vec(
+                    (&misc_path()).into(),
+                    TOTAL_SYSTEM_CREDITS_STORAGE_KEY,
+                    DirectQueryType::StatefulDirectQuery,
+                    None,
+                    &mut vec![],
+                    &platform_version.drive,
+                )
+                .expect("expected to get total credits in platform");
+
+            assert_eq!(total_credits_in_platform, Some(410000000000000));
+
+            outcome
+        };
+
+        let continue_strategy_only_withdrawal = NetworkStrategy {
+            strategy: Strategy {
+                start_contracts: vec![],
+                operations: vec![Operation {
+                    op_type: OperationType::IdentityWithdrawal(
+                        dash_to_credits!(25)..=dash_to_credits!(25),
+                    ),
+                    frequency: Frequency {
+                        times_per_block_range: 4..5, // 25 Dash x 4 Withdrawals = 100 Dash
+                        chance_per_block: None,
+                    },
+                }],
+                start_identities: StartIdentities::default(),
+                identity_inserts: IdentityInsertInfo::default(),
+                identity_contract_nonce_gaps: None,
+                signer: Some(signer.clone()),
+            },
+            total_hpmns: 100,
+            extra_normal_mns: 0,
+            validator_quorum_count: 24,
+            chain_lock_quorum_count: 24,
+            upgrading_info: None,
+
+            proposer_strategy: Default::default(),
+            rotate_quorums: false,
+            failure_testing: None,
+            query_testing: None,
+            verify_state_transition_results: true,
+            ..Default::default()
+        };
+
+        let continue_strategy_no_operations = NetworkStrategy {
+            strategy: Strategy {
+                start_contracts: vec![],
+                operations: vec![],
+                start_identities: StartIdentities::default(),
+                identity_inserts: IdentityInsertInfo::default(),
+                identity_contract_nonce_gaps: None,
+                signer: Some(signer),
+            },
+            total_hpmns: 100,
+            extra_normal_mns: 0,
+            validator_quorum_count: 24,
+            chain_lock_quorum_count: 24,
+            upgrading_info: None,
+
+            proposer_strategy: Default::default(),
+            rotate_quorums: false,
+            failure_testing: None,
+            query_testing: None,
+            verify_state_transition_results: true,
+            ..Default::default()
+        };
+
+        // Run Block 3 onwards: initiates withdrawals
+        let ChainExecutionOutcome {
+            abci_app,
+            proposers,
+            validator_quorums: quorums,
+            current_validator_quorum_hash: current_quorum_hash,
+            current_proposer_versions,
+            end_time_ms,
+            identity_nonce_counter,
+            identity_contract_nonce_counter,
+            instant_lock_quorums,
+            identities,
+            ..
+        } = {
+            let outcome = continue_chain_for_strategy(
+                abci_app,
+                ChainExecutionParameters {
+                    block_start: 3,
+                    core_height_start: 1,
+                    block_count: 20,
+                    proposers,
+                    validator_quorums: quorums,
+                    current_validator_quorum_hash: current_quorum_hash,
+                    current_proposer_versions: Some(current_proposer_versions),
+                    current_identity_nonce_counter: identity_nonce_counter,
+                    current_identity_contract_nonce_counter: identity_contract_nonce_counter,
+                    current_votes: BTreeMap::default(),
+                    start_time_ms: GENESIS_TIME_MS,
+                    current_time_ms: end_time_ms,
+                    instant_lock_quorums,
+                    current_identities: identities,
+                },
+                continue_strategy_only_withdrawal.clone(),
+                config.clone(),
+                StrategyRandomness::SeedEntropy(2),
+            );
+
+            for tx_results_per_block in outcome.state_transition_results_per_block.values() {
+                assert_eq!(tx_results_per_block.len(), 4);
+                for (state_transition, result) in tx_results_per_block {
+                    assert_eq!(
+                        result.code, 0,
+                        "state transition got code {} : {:?}",
+                        result.code, state_transition
+                    );
+                }
+            }
+
+            // Withdrawal documents with pooled status should not exist.
+            let withdrawal_documents_pooled = outcome
+                .abci_app
+                .platform
+                .drive
+                .fetch_oldest_withdrawal_documents_by_status(
+                    withdrawals_contract::WithdrawalStatus::POOLED.into(),
+                    DEFAULT_QUERY_LIMIT,
+                    None,
+                    platform_version,
+                )
+                .unwrap();
+
+            // None are currently pooled since we have no more room
+            assert!(withdrawal_documents_pooled.is_empty());
+
+            // Withdrawal documents with queued status should exist.
+            let withdrawal_documents_queued = outcome
+                .abci_app
+                .platform
+                .drive
+                .fetch_oldest_withdrawal_documents_by_status(
+                    withdrawals_contract::WithdrawalStatus::QUEUED.into(),
+                    DEFAULT_QUERY_LIMIT,
+                    None,
+                    platform_version,
+                )
+                .unwrap();
+
+            // We have 66 out of 100 queued
+            assert_eq!(withdrawal_documents_queued.len(), 66);
+
+            // Withdrawal documents with queued status should exist.
+            let withdrawal_documents_completed = outcome
+                .abci_app
+                .platform
+                .drive
+                .fetch_oldest_withdrawal_documents_by_status(
+                    withdrawals_contract::WithdrawalStatus::COMPLETE.into(),
+                    DEFAULT_QUERY_LIMIT,
+                    None,
+                    platform_version,
+                )
+                .unwrap();
+
+            // None have completed because core didn't acknowledge them
+            assert_eq!(withdrawal_documents_completed.len(), 0);
+
+            // Withdrawal documents with EXPIRED status should not exist yet.
+            let withdrawal_documents_expired = outcome
+                .abci_app
+                .platform
+                .drive
+                .fetch_oldest_withdrawal_documents_by_status(
+                    withdrawals_contract::WithdrawalStatus::EXPIRED.into(),
+                    DEFAULT_QUERY_LIMIT,
+                    None,
+                    platform_version,
+                )
+                .unwrap();
+
+            // We have none expired yet
+            assert_eq!(withdrawal_documents_expired.len(), 0);
+
+            // Withdrawal documents with broadcasted status should exist.
+            let withdrawal_documents_broadcasted = outcome
+                .abci_app
+                .platform
+                .drive
+                .fetch_oldest_withdrawal_documents_by_status(
+                    withdrawals_contract::WithdrawalStatus::BROADCASTED.into(),
+                    DEFAULT_QUERY_LIMIT,
+                    None,
+                    platform_version,
+                )
+                .unwrap();
+
+            // We have 14 broadcasted (66 queued + 14 broadcasted = 80 total)
+            // 14 broadcasted = 14 * 25 Dash = 350 Dash
+            // This is explained by
+            // withdrawal block 1 : 4 withdrawals taking us to 4000 Dash and 100 Dash Taken
+            //   limit is 100, 4 broadcasted
+            // withdrawal block 2 : 4 withdrawals taking us to 3900 Dash and 100 Dash Taken
+            //   limit is 390 - 100 = 290, 4 broadcasted
+            // withdrawal block 3 : 4 withdrawals taking us to 3800 Dash and 100 Dash Taken
+            //   limit is 380 - 200 = 180, 4 broadcasted
+            // withdrawal block 4 : 4 withdrawals taking us to 3700 Dash and 100 Dash Taken
+            //   limit is 370 - 300 = 70, 2 broadcasted
+            assert_eq!(withdrawal_documents_broadcasted.len(), 14);
+
+            let locked_amount = outcome
+                .abci_app
+                .platform
+                .drive
+                .grove_get_sum_tree_total_value(
+                    (&get_withdrawal_root_path()).into(),
+                    &WITHDRAWAL_TRANSACTIONS_SUM_AMOUNT_TREE_KEY,
+                    DirectQueryType::StatefulDirectQuery,
+                    None,
+                    &mut vec![],
+                    &platform_version.drive,
+                )
+                .expect("expected to get locked amount");
+
+            assert_eq!(locked_amount, dash_to_credits!(350) as i64);
+
+            outcome
+        };
+
+        let hour_in_ms = 1000 * 60 * 60;
+
+        let ChainExecutionOutcome {
+            abci_app,
+            proposers,
+            validator_quorums: quorums,
+            current_validator_quorum_hash: current_quorum_hash,
+            current_proposer_versions,
+            end_time_ms,
+            identity_nonce_counter,
+            identity_contract_nonce_counter,
+            instant_lock_quorums,
+            identities,
+            ..
+        } = {
+            let outcome = continue_chain_for_strategy(
+                abci_app,
+                ChainExecutionParameters {
+                    block_start: 23,
+                    core_height_start: 1,
+                    block_count: 48,
+                    proposers,
+                    validator_quorums: quorums,
+                    current_validator_quorum_hash: current_quorum_hash,
+                    current_proposer_versions: Some(current_proposer_versions),
+                    current_identity_nonce_counter: identity_nonce_counter,
+                    current_identity_contract_nonce_counter: identity_contract_nonce_counter,
+                    current_votes: BTreeMap::default(),
+                    start_time_ms: GENESIS_TIME_MS,
+                    current_time_ms: end_time_ms + 1000,
+                    instant_lock_quorums,
+                    current_identities: identities,
+                },
+                continue_strategy_no_operations.clone(),
+                PlatformConfig {
+                    validator_set: ValidatorSetConfig::default_100_67(),
+                    chain_lock: ChainLockConfig::default_100_67(),
+                    instant_lock: InstantLockConfig::default_100_67(),
+                    execution: ExecutionConfig {
+                        verify_sum_trees: true,
+
+                        ..Default::default()
+                    },
+                    block_spacing_ms: hour_in_ms,
+                    initial_protocol_version: TEST_PLATFORM_V3.protocol_version,
+                    testing_configs: PlatformTestConfig::default_minimal_verifications(),
+                    ..Default::default()
+                },
+                StrategyRandomness::SeedEntropy(9),
+            );
+
+            // We should have unlocked the amounts by now
+            let locked_amount = outcome
+                .abci_app
+                .platform
+                .drive
+                .grove_get_sum_tree_total_value(
+                    (&get_withdrawal_root_path()).into(),
+                    &WITHDRAWAL_TRANSACTIONS_SUM_AMOUNT_TREE_KEY,
+                    DirectQueryType::StatefulDirectQuery,
+                    None,
+                    &mut vec![],
+                    &platform_version.drive,
+                )
+                .expect("expected to get locked amount");
+
+            // There's about 2000 Dash left in platform, it's normal we are locking 1/10th of it
+            assert_eq!(locked_amount, 20000000000000);
+
+            // Withdrawal documents with pooled status should not exist.
+            let withdrawal_documents_pooled = outcome
+                .abci_app
+                .platform
+                .drive
+                .fetch_oldest_withdrawal_documents_by_status(
+                    withdrawals_contract::WithdrawalStatus::POOLED.into(),
+                    DEFAULT_QUERY_LIMIT,
+                    None,
+                    platform_version,
+                )
+                .unwrap();
+
+            // None are currently pooled since we have no more room
+            assert!(withdrawal_documents_pooled.is_empty());
+
+            // Withdrawal documents with queued status should exist.
+            let withdrawal_documents_queued = outcome
+                .abci_app
+                .platform
+                .drive
+                .fetch_oldest_withdrawal_documents_by_status(
+                    withdrawals_contract::WithdrawalStatus::QUEUED.into(),
+                    DEFAULT_QUERY_LIMIT,
+                    None,
+                    platform_version,
+                )
+                .unwrap();
+
+            // We have 58 out of 100 queued
+            assert_eq!(withdrawal_documents_queued.len(), 58);
+
+            // Withdrawal documents with queued status should exist.
+            let withdrawal_documents_completed = outcome
+                .abci_app
+                .platform
+                .drive
+                .fetch_oldest_withdrawal_documents_by_status(
+                    withdrawals_contract::WithdrawalStatus::COMPLETE.into(),
+                    DEFAULT_QUERY_LIMIT,
+                    None,
+                    platform_version,
+                )
+                .unwrap();
+
+            // None have completed because core didn't acknowledge them
+            assert_eq!(withdrawal_documents_completed.len(), 0);
+
+            // Withdrawal documents with EXPIRED status should not exist yet.
+            let withdrawal_documents_expired = outcome
+                .abci_app
+                .platform
+                .drive
+                .fetch_oldest_withdrawal_documents_by_status(
+                    withdrawals_contract::WithdrawalStatus::EXPIRED.into(),
+                    DEFAULT_QUERY_LIMIT,
+                    None,
+                    platform_version,
+                )
+                .unwrap();
+
+            // We have none expired yet, because the core height never went up
+            assert_eq!(withdrawal_documents_expired.len(), 0);
+
+            // Withdrawal documents with broadcasted status should exist.
+            let withdrawal_documents_broadcasted = outcome
+                .abci_app
+                .platform
+                .drive
+                .fetch_oldest_withdrawal_documents_by_status(
+                    withdrawals_contract::WithdrawalStatus::BROADCASTED.into(),
+                    DEFAULT_QUERY_LIMIT,
+                    None,
+                    platform_version,
+                )
+                .unwrap();
+
+            assert_eq!(withdrawal_documents_broadcasted.len(), 22);
+            outcome
+        };
+
+        let outcome = continue_chain_for_strategy(
+            abci_app,
+            ChainExecutionParameters {
+                block_start: 71,
+                core_height_start: 1,
+                block_count: 250,
+                proposers,
+                validator_quorums: quorums,
+                current_validator_quorum_hash: current_quorum_hash,
+                current_proposer_versions: Some(current_proposer_versions),
+                current_identity_nonce_counter: identity_nonce_counter,
+                current_identity_contract_nonce_counter: identity_contract_nonce_counter,
+                current_votes: BTreeMap::default(),
+                start_time_ms: GENESIS_TIME_MS,
+                current_time_ms: end_time_ms + 1000,
+                instant_lock_quorums,
+                current_identities: identities,
+            },
+            continue_strategy_no_operations.clone(),
+            PlatformConfig {
+                validator_set: ValidatorSetConfig::default_100_67(),
+                chain_lock: ChainLockConfig::default_100_67(),
+                instant_lock: InstantLockConfig::default_100_67(),
+                execution: ExecutionConfig {
+                    verify_sum_trees: true,
+
+                    ..Default::default()
+                },
+                block_spacing_ms: hour_in_ms,
+                initial_protocol_version: TEST_PLATFORM_V3.protocol_version,
+                testing_configs: PlatformTestConfig::default_minimal_verifications(),
+                ..Default::default()
+            },
+            StrategyRandomness::SeedEntropy(9),
+        );
+
+        // We should have unlocked the amounts by now
+        let locked_amount = outcome
+            .abci_app
+            .platform
+            .drive
+            .grove_get_sum_tree_total_value(
+                (&get_withdrawal_root_path()).into(),
+                &WITHDRAWAL_TRANSACTIONS_SUM_AMOUNT_TREE_KEY,
+                DirectQueryType::StatefulDirectQuery,
+                None,
+                &mut vec![],
+                &platform_version.drive,
+            )
+            .expect("expected to get locked amount");
+
+        // We have nothing locked left
+        assert_eq!(locked_amount, 0);
+
+        // Withdrawal documents with pooled status should not exist.
+        let withdrawal_documents_pooled = outcome
+            .abci_app
+            .platform
+            .drive
+            .fetch_oldest_withdrawal_documents_by_status(
+                withdrawals_contract::WithdrawalStatus::POOLED.into(),
+                DEFAULT_QUERY_LIMIT,
+                None,
+                platform_version,
+            )
+            .unwrap();
+
+        // None are currently pooled since we have no more room
+        assert!(withdrawal_documents_pooled.is_empty());
+
+        // Withdrawal documents with queued status should exist.
+        let withdrawal_documents_queued = outcome
+            .abci_app
+            .platform
+            .drive
+            .fetch_oldest_withdrawal_documents_by_status(
+                withdrawals_contract::WithdrawalStatus::QUEUED.into(),
+                DEFAULT_QUERY_LIMIT,
+                None,
+                platform_version,
+            )
+            .unwrap();
+
+        // Nothing is left in the queue
+        assert_eq!(withdrawal_documents_queued.len(), 0);
+
+        // Withdrawal documents with queued status should exist.
+        let withdrawal_documents_completed = outcome
+            .abci_app
+            .platform
+            .drive
+            .fetch_oldest_withdrawal_documents_by_status(
+                withdrawals_contract::WithdrawalStatus::COMPLETE.into(),
+                DEFAULT_QUERY_LIMIT,
+                None,
+                platform_version,
+            )
+            .unwrap();
+
+        // None have completed because core didn't acknowledge them
+        assert_eq!(withdrawal_documents_completed.len(), 0);
+
+        // Withdrawal documents with EXPIRED status should not exist yet.
+        let withdrawal_documents_expired = outcome
+            .abci_app
+            .platform
+            .drive
+            .fetch_oldest_withdrawal_documents_by_status(
+                withdrawals_contract::WithdrawalStatus::EXPIRED.into(),
+                DEFAULT_QUERY_LIMIT,
+                None,
+                platform_version,
+            )
+            .unwrap();
+
+        // We have none expired yet, because the core height never went up
+        assert_eq!(withdrawal_documents_expired.len(), 0);
+
+        // Withdrawal documents with broadcasted status should exist.
+        let withdrawal_documents_broadcasted = outcome
+            .abci_app
+            .platform
+            .drive
+            .fetch_oldest_withdrawal_documents_by_status(
+                withdrawals_contract::WithdrawalStatus::BROADCASTED.into(),
+                DEFAULT_QUERY_LIMIT,
+                None,
+                platform_version,
+            )
+            .unwrap();
+
+        assert_eq!(withdrawal_documents_broadcasted.len(), 80);
+    }
+
+    #[test]
+    fn run_chain_withdraw_from_identities_many_small_withdrawals() {
+        // TEST_PLATFORM_V3 is like v4, but without the single quorum can sign withdrawals restriction
+        let platform_version = PlatformVersion::get(TEST_PLATFORM_V3.protocol_version)
+            .expect("expected to get platform version");
+        let start_strategy = NetworkStrategy {
+            strategy: Strategy {
+                start_contracts: vec![],
+                operations: vec![Operation {
+                    op_type: OperationType::IdentityTopUp(dash_to_duffs!(10)..=dash_to_duffs!(10)),
+                    frequency: Frequency {
+                        times_per_block_range: 10..11,
+                        chance_per_block: None,
+                    },
+                }],
+                start_identities: StartIdentities::default(),
+                identity_inserts: IdentityInsertInfo {
+                    frequency: Frequency {
+                        times_per_block_range: 10..11,
+                        chance_per_block: None,
+                    },
+                    start_keys: 3,
+                    extra_keys: [(
+                        Purpose::TRANSFER,
+                        [(SecurityLevel::CRITICAL, vec![KeyType::ECDSA_SECP256K1])].into(),
+                    )]
+                    .into(),
+                    start_balance_range: dash_to_duffs!(200)..=dash_to_duffs!(200),
+                },
+                identity_contract_nonce_gaps: None,
+                signer: None,
+            },
+            total_hpmns: 100,
+            extra_normal_mns: 0,
+            validator_quorum_count: 24,
+            chain_lock_quorum_count: 24,
+            upgrading_info: None,
+
+            proposer_strategy: Default::default(),
+            rotate_quorums: false,
+            failure_testing: None,
+            query_testing: None,
+            verify_state_transition_results: true,
+            ..Default::default()
+        };
+
+        let minute_in_ms = 1000 * 60;
+        let config = PlatformConfig {
+            validator_set: ValidatorSetConfig::default_100_67(),
+            chain_lock: ChainLockConfig::default_100_67(),
+            instant_lock: InstantLockConfig::default_100_67(),
+            execution: ExecutionConfig {
+                verify_sum_trees: true,
+
+                ..Default::default()
+            },
+            block_spacing_ms: minute_in_ms,
+            initial_protocol_version: TEST_PLATFORM_V3.protocol_version,
+            testing_configs: PlatformTestConfig::default_minimal_verifications(),
+            ..Default::default()
+        };
+
+        let mut platform = TestPlatformBuilder::new()
+            .with_config(config.clone())
+            .build_with_mock_rpc();
+
+        platform
+            .core_rpc
+            .expect_send_raw_transaction()
+            .returning(move |_| Ok(Txid::all_zeros()));
+
+        let chain_locked_height = 1;
+
+        // Have to go with a complicated shared object for the core state because we need to change
+        // rpc response along the way but we can't mutate `platform.core_rpc` later
+        // because platform reference is moved into the AbciApplication.
+        let shared_core_state = Arc::new(Mutex::new(CoreState {
+            asset_unlock_statuses: BTreeMap::new(),
+            chain_lock: ChainLock {
+                block_height: chain_locked_height,
+                block_hash: BlockHash::from_byte_array([1; 32]),
+                signature: BLSSignature::from([2; 96]),
+            },
+        }));
+
+        // Set up Core RPC responses
+        {
+            let core_state = shared_core_state.clone();
+
+            platform
+                .core_rpc
+                .expect_get_asset_unlock_statuses()
+                .returning(move |indices, _| {
+                    Ok(indices
+                        .iter()
+                        .map(|index| {
+                            core_state
+                                .lock()
+                                .unwrap()
+                                .asset_unlock_statuses
+                                .get(index)
+                                .cloned()
+                                .unwrap()
+                        })
+                        .collect())
+                });
+
+            let core_state = shared_core_state.clone();
+            platform
+                .core_rpc
+                .expect_get_best_chain_lock()
+                .returning(move || Ok(core_state.lock().unwrap().chain_lock.clone()));
         }
 
-        // Run block 6.
-        // Tests withdrawal expiration
-        let ChainExecutionOutcome { .. } = {
+        // Run first two blocks:
+        // - Block 1: creates identity
+        // - Block 2: tops up identity
+        let ChainExecutionOutcome {
+            abci_app,
+            proposers,
+            validator_quorums: quorums,
+            current_validator_quorum_hash: current_quorum_hash,
+            current_proposer_versions,
+            end_time_ms,
+            identity_nonce_counter,
+            identity_contract_nonce_counter,
+            instant_lock_quorums,
+            identities,
+            signer,
+            ..
+        } = {
+            let outcome = run_chain_for_strategy(
+                &mut platform,
+                2,
+                start_strategy,
+                config.clone(),
+                1,
+                &mut None,
+            );
+
+            for tx_results_per_block in outcome.state_transition_results_per_block.values() {
+                for (state_transition, result) in tx_results_per_block {
+                    assert_eq!(
+                        result.code, 0,
+                        "state transition got code {} : {:?}",
+                        result.code, state_transition
+                    );
+                }
+            }
+
+            // Withdrawal transactions are not populated to block execution context yet
+            assert_eq!(outcome.withdrawals.len(), 0);
+
+            // Withdrawal documents with pooled status should exist.
+            let withdrawal_documents_pooled = outcome
+                .abci_app
+                .platform
+                .drive
+                .fetch_oldest_withdrawal_documents_by_status(
+                    withdrawals_contract::WithdrawalStatus::POOLED.into(),
+                    DEFAULT_QUERY_LIMIT,
+                    None,
+                    platform_version,
+                )
+                .unwrap();
+            assert!(withdrawal_documents_pooled.is_empty());
+
+            let locked_amount = outcome
+                .abci_app
+                .platform
+                .drive
+                .grove_get_sum_tree_total_value(
+                    (&get_withdrawal_root_path()).into(),
+                    &WITHDRAWAL_TRANSACTIONS_SUM_AMOUNT_TREE_KEY,
+                    DirectQueryType::StatefulDirectQuery,
+                    None,
+                    &mut vec![],
+                    &platform_version.drive,
+                )
+                .expect("expected to get locked amount");
+
+            assert_eq!(locked_amount, 0);
+
+            let pooled_withdrawals = withdrawal_documents_pooled.len();
+
+            assert_eq!(pooled_withdrawals, 0);
+
+            let total_credits_balance = outcome
+                .abci_app
+                .platform
+                .drive
+                .calculate_total_credits_balance(None, &platform_version.drive)
+                .expect("expected to get total credits balance");
+
+            assert_eq!(
+                total_credits_balance.total_identity_balances,
+                409997575280380
+            ); // Around 4100 Dash.
+
+            assert_eq!(
+                total_credits_balance.total_in_trees().unwrap(),
+                410000000000000
+            ); // Around 4100 Dash.
+
+            let total_credits_in_platform = outcome
+                .abci_app
+                .platform
+                .drive
+                .grove_get_raw_value_u64_from_encoded_var_vec(
+                    (&misc_path()).into(),
+                    TOTAL_SYSTEM_CREDITS_STORAGE_KEY,
+                    DirectQueryType::StatefulDirectQuery,
+                    None,
+                    &mut vec![],
+                    &platform_version.drive,
+                )
+                .expect("expected to get total credits in platform");
+
+            assert_eq!(total_credits_in_platform, Some(410000000000000));
+
+            outcome
+        };
+
+        let continue_strategy_only_withdrawal = NetworkStrategy {
+            strategy: Strategy {
+                start_contracts: vec![],
+                operations: vec![Operation {
+                    op_type: OperationType::IdentityWithdrawal(
+                        dash_to_credits!(0.0025)..=dash_to_credits!(0.0025),
+                    ),
+                    frequency: Frequency {
+                        times_per_block_range: 40..41, // 0.0025 Dash x 40 Withdrawals = 0.1 Dash
+                        chance_per_block: None,
+                    },
+                }],
+                start_identities: StartIdentities::default(),
+                identity_inserts: IdentityInsertInfo::default(),
+                identity_contract_nonce_gaps: None,
+                signer: Some(signer.clone()),
+            },
+            total_hpmns: 100,
+            extra_normal_mns: 0,
+            validator_quorum_count: 24,
+            chain_lock_quorum_count: 24,
+            upgrading_info: None,
+
+            proposer_strategy: Default::default(),
+            rotate_quorums: false,
+            failure_testing: None,
+            query_testing: None,
+            verify_state_transition_results: true,
+            ..Default::default()
+        };
+
+        let continue_strategy_no_operations = NetworkStrategy {
+            strategy: Strategy {
+                start_contracts: vec![],
+                operations: vec![],
+                start_identities: StartIdentities::default(),
+                identity_inserts: IdentityInsertInfo::default(),
+                identity_contract_nonce_gaps: None,
+                signer: Some(signer),
+            },
+            total_hpmns: 100,
+            extra_normal_mns: 0,
+            validator_quorum_count: 24,
+            chain_lock_quorum_count: 24,
+            upgrading_info: None,
+
+            proposer_strategy: Default::default(),
+            rotate_quorums: false,
+            failure_testing: None,
+            query_testing: None,
+            verify_state_transition_results: true,
+            ..Default::default()
+        };
+
+        // Run Block 3-23 onwards: initiates withdrawals
+        let ChainExecutionOutcome {
+            abci_app,
+            proposers,
+            validator_quorums: quorums,
+            current_validator_quorum_hash: current_quorum_hash,
+            current_proposer_versions,
+            end_time_ms,
+            identity_nonce_counter,
+            identity_contract_nonce_counter,
+            instant_lock_quorums,
+            identities,
+            ..
+        } = {
             let outcome = continue_chain_for_strategy(
                 abci_app,
                 ChainExecutionParameters {
-                    block_start: 6,
+                    block_start: 3,
                     core_height_start: 1,
-                    block_count: 1,
+                    block_count: 20,
                     proposers,
                     validator_quorums: quorums,
                     current_validator_quorum_hash: current_quorum_hash,
@@ -551,15 +2248,27 @@ mod tests {
                     current_identity_contract_nonce_counter: identity_contract_nonce_counter,
                     current_votes: BTreeMap::default(),
                     start_time_ms: GENESIS_TIME_MS,
-                    current_time_ms: end_time_ms + 1000,
+                    current_time_ms: end_time_ms,
                     instant_lock_quorums,
-                    current_identities: Vec::new(),
+                    current_identities: identities,
                 },
-                strategy.clone(),
+                continue_strategy_only_withdrawal.clone(),
                 config.clone(),
-                StrategyRandomness::SeedEntropy(5),
+                StrategyRandomness::SeedEntropy(2),
             );
 
+            for tx_results_per_block in outcome.state_transition_results_per_block.values() {
+                assert_eq!(tx_results_per_block.len(), 20);
+                for (state_transition, result) in tx_results_per_block {
+                    assert_eq!(
+                        result.code, 0,
+                        "state transition got code {} : {:?}",
+                        result.code, state_transition
+                    );
+                }
+            }
+
+            // Withdrawal documents with pooled status should not exist.
             let withdrawal_documents_pooled = outcome
                 .abci_app
                 .platform
@@ -572,18 +2281,26 @@ mod tests {
                 )
                 .unwrap();
 
-            let withdrawal_documents_broadcasted = outcome
+            // We should have 4 pooled, which is the limit per block
+            assert_eq!(withdrawal_documents_pooled.len(), 4);
+
+            // Withdrawal documents with queued status should exist.
+            let withdrawal_documents_queued = outcome
                 .abci_app
                 .platform
                 .drive
                 .fetch_oldest_withdrawal_documents_by_status(
-                    withdrawals_contract::WithdrawalStatus::BROADCASTED.into(),
-                    DEFAULT_QUERY_LIMIT,
+                    withdrawals_contract::WithdrawalStatus::QUEUED.into(),
+                    10000,
                     None,
                     platform_version,
                 )
                 .unwrap();
 
+            // We have 320 queued
+            assert_eq!(withdrawal_documents_queued.len(), 320);
+
+            // Withdrawal documents with queued status should exist.
             let withdrawal_documents_completed = outcome
                 .abci_app
                 .platform
@@ -596,6 +2313,10 @@ mod tests {
                 )
                 .unwrap();
 
+            // None have completed because core didn't acknowledge them
+            assert_eq!(withdrawal_documents_completed.len(), 0);
+
+            // Withdrawal documents with EXPIRED status should not exist yet.
             let withdrawal_documents_expired = outcome
                 .abci_app
                 .platform
@@ -608,36 +2329,177 @@ mod tests {
                 )
                 .unwrap();
 
-            // In this block we should have new withdrawals pooled
-            assert!(!withdrawal_documents_pooled.is_empty());
-
-            // Amount of completed withdrawals stays the same as in the last block
-            assert_eq!(
-                withdrawal_documents_completed.len(),
-                last_block_withdrawals_completed_amount
-            );
+            // We have none expired yet
+            assert_eq!(withdrawal_documents_expired.len(), 0);
 
-            // And some withdrawals got expired
-            let withdrawals_expired_expected =
-                // Withdrawals issued on {previous_block - 1}, but not chainlocked yet, considered expired
-                last_block_broadcased_withdrawals_amount - last_block_withdrawals.len();
+            // Withdrawal documents with broadcasted status should exist.
+            let withdrawal_documents_broadcasted = outcome
+                .abci_app
+                .platform
+                .drive
+                .fetch_oldest_withdrawal_documents_by_status(
+                    withdrawals_contract::WithdrawalStatus::BROADCASTED.into(),
+                    DEFAULT_QUERY_LIMIT,
+                    None,
+                    platform_version,
+                )
+                .unwrap();
 
-            assert_eq!(
-                withdrawal_documents_expired.len(),
-                withdrawals_expired_expected
-            );
+            // We have 76 broadcasted (4 pooled + 76 broadcasted = 80 total for 4 per block in 20 blocks)
+            assert_eq!(withdrawal_documents_broadcasted.len(), 76);
 
-            // And extra withdrawals broadcasted
-            let withdrawals_broadcasted_expected =
-                // Withdrawals issued on previous block + withdrawals from this block are still in broadcasted state
-                last_block_withdrawals.len() + outcome.withdrawals.len();
+            let locked_amount = outcome
+                .abci_app
+                .platform
+                .drive
+                .grove_get_sum_tree_total_value(
+                    (&get_withdrawal_root_path()).into(),
+                    &WITHDRAWAL_TRANSACTIONS_SUM_AMOUNT_TREE_KEY,
+                    DirectQueryType::StatefulDirectQuery,
+                    None,
+                    &mut vec![],
+                    &platform_version.drive,
+                )
+                .expect("expected to get locked amount");
 
-            assert_eq!(
-                withdrawal_documents_broadcasted.len(),
-                withdrawals_broadcasted_expected
-            );
+            // 0.2 is 0.0025 amount * 80 withdrawals
+            assert_eq!(locked_amount, dash_to_credits!(0.2) as i64);
 
             outcome
         };
+
+        let hour_in_ms = 1000 * 60 * 60;
+
+        let outcome = continue_chain_for_strategy(
+            abci_app,
+            ChainExecutionParameters {
+                block_start: 23,
+                core_height_start: 1,
+                block_count: 88,
+                proposers,
+                validator_quorums: quorums,
+                current_validator_quorum_hash: current_quorum_hash,
+                current_proposer_versions: Some(current_proposer_versions),
+                current_identity_nonce_counter: identity_nonce_counter,
+                current_identity_contract_nonce_counter: identity_contract_nonce_counter,
+                current_votes: BTreeMap::default(),
+                start_time_ms: GENESIS_TIME_MS,
+                current_time_ms: end_time_ms + 1000,
+                instant_lock_quorums,
+                current_identities: identities,
+            },
+            continue_strategy_no_operations.clone(),
+            PlatformConfig {
+                validator_set: ValidatorSetConfig::default_100_67(),
+                chain_lock: ChainLockConfig::default_100_67(),
+                instant_lock: InstantLockConfig::default_100_67(),
+                execution: ExecutionConfig {
+                    verify_sum_trees: true,
+
+                    ..Default::default()
+                },
+                block_spacing_ms: hour_in_ms,
+                initial_protocol_version: TEST_PLATFORM_V3.protocol_version,
+                testing_configs: PlatformTestConfig::default_minimal_verifications(),
+                ..Default::default()
+            },
+            StrategyRandomness::SeedEntropy(9),
+        );
+
+        // We should have unlocked the amounts by now
+        let locked_amount = outcome
+            .abci_app
+            .platform
+            .drive
+            .grove_get_sum_tree_total_value(
+                (&get_withdrawal_root_path()).into(),
+                &WITHDRAWAL_TRANSACTIONS_SUM_AMOUNT_TREE_KEY,
+                DirectQueryType::StatefulDirectQuery,
+                None,
+                &mut vec![],
+                &platform_version.drive,
+            )
+            .expect("expected to get locked amount");
+
+        assert_eq!(locked_amount, 18000000000);
+
+        // Withdrawal documents with pooled status should not exist.
+        let withdrawal_documents_pooled = outcome
+            .abci_app
+            .platform
+            .drive
+            .fetch_oldest_withdrawal_documents_by_status(
+                withdrawals_contract::WithdrawalStatus::POOLED.into(),
+                DEFAULT_QUERY_LIMIT,
+                None,
+                platform_version,
+            )
+            .unwrap();
+
+        // None are currently pooled since we finished the queue
+        assert!(withdrawal_documents_pooled.is_empty());
+
+        // Withdrawal documents with queued status should exist.
+        let withdrawal_documents_queued = outcome
+            .abci_app
+            .platform
+            .drive
+            .fetch_oldest_withdrawal_documents_by_status(
+                withdrawals_contract::WithdrawalStatus::QUEUED.into(),
+                DEFAULT_QUERY_LIMIT,
+                None,
+                platform_version,
+            )
+            .unwrap();
+
+        // Queue has been finished
+        assert_eq!(withdrawal_documents_queued.len(), 0);
+
+        // Withdrawal documents with queued status should exist.
+        let withdrawal_documents_completed = outcome
+            .abci_app
+            .platform
+            .drive
+            .fetch_oldest_withdrawal_documents_by_status(
+                withdrawals_contract::WithdrawalStatus::COMPLETE.into(),
+                DEFAULT_QUERY_LIMIT,
+                None,
+                platform_version,
+            )
+            .unwrap();
+
+        // None have completed because core didn't acknowledge them
+        assert_eq!(withdrawal_documents_completed.len(), 0);
+
+        // Withdrawal documents with EXPIRED status should not exist yet.
+        let withdrawal_documents_expired = outcome
+            .abci_app
+            .platform
+            .drive
+            .fetch_oldest_withdrawal_documents_by_status(
+                withdrawals_contract::WithdrawalStatus::EXPIRED.into(),
+                DEFAULT_QUERY_LIMIT,
+                None,
+                platform_version,
+            )
+            .unwrap();
+
+        // We have none expired yet, because the core height never went up
+        assert_eq!(withdrawal_documents_expired.len(), 0);
+
+        // Withdrawal documents with broadcasted status should exist.
+        let withdrawal_documents_broadcasted = outcome
+            .abci_app
+            .platform
+            .drive
+            .fetch_oldest_withdrawal_documents_by_status(
+                withdrawals_contract::WithdrawalStatus::BROADCASTED.into(),
+                1000,
+                None,
+                platform_version,
+            )
+            .unwrap();
+
+        assert_eq!(withdrawal_documents_broadcasted.len(), 400);
     }
 }
diff --git a/packages/rs-drive/Cargo.toml b/packages/rs-drive/Cargo.toml
index 15c207cc230..7c37d1ad6c2 100644
--- a/packages/rs-drive/Cargo.toml
+++ b/packages/rs-drive/Cargo.toml
@@ -77,6 +77,7 @@ dpp = { path = "../rs-dpp", features = [
 ], default-features = false }
 once_cell = "1.7"
 serde_json = { version = "1.0", features = ["preserve_order"] }
+assert_matches = "1.5.0"
 
 [[bench]]
 name = "benchmarks"
diff --git a/packages/rs-drive/src/drive/identity/withdrawals/calculate_current_withdrawal_limit/mod.rs b/packages/rs-drive/src/drive/identity/withdrawals/calculate_current_withdrawal_limit/mod.rs
new file mode 100644
index 00000000000..172eda82c97
--- /dev/null
+++ b/packages/rs-drive/src/drive/identity/withdrawals/calculate_current_withdrawal_limit/mod.rs
@@ -0,0 +1,53 @@
+use crate::drive::Drive;
+use crate::error::drive::DriveError;
+use crate::error::Error;
+use dpp::fee::Credits;
+use grovedb::TransactionArg;
+use platform_version::version::PlatformVersion;
+
+mod v0;
+
+impl Drive {
+    /// Calculates the current withdrawal limit based on the total credits available in the platform
+    /// and the amount already withdrawn in the last 24 hours, using the appropriate version-specific logic.
+    ///
+    /// This function selects the version-specific implementation based on the provided `platform_version`.
+    /// It currently supports only version 0 (`calculate_current_withdrawal_limit_v0`).
+    ///
+    /// # Parameters
+    ///
+    /// * `transaction`: The transaction context used for querying data.
+    /// * `platform_version`: The version of the platform being used, which contains configuration details and version-specific methods.
+    ///
+    /// # Returns
+    ///
+    /// * `Ok(Credits)`: The calculated current withdrawal limit, representing the maximum amount that can still be withdrawn in the current 24-hour window.
+    /// * `Err(Error)`: Returns an error if the version specified in `platform_version` is not supported or if there is an issue in the version-specific calculation.
+    ///
+    /// # Errors
+    ///
+    /// * `Error::Drive(DriveError::UnknownVersionMismatch)`:
+    ///   - If the platform version provided does not match any known versions supported by this function.
+    ///
+    /// * `Error`: Any error propagated from the version-specific implementation, such as issues in retrieving data or calculating the withdrawal limit.
+    pub fn calculate_current_withdrawal_limit(
+        &self,
+        transaction: TransactionArg,
+        platform_version: &PlatformVersion,
+    ) -> Result<Credits, Error> {
+        match platform_version
+            .drive
+            .methods
+            .identity
+            .withdrawals
+            .calculate_current_withdrawal_limit
+        {
+            0 => self.calculate_current_withdrawal_limit_v0(transaction, platform_version),
+            version => Err(Error::Drive(DriveError::UnknownVersionMismatch {
+                method: "calculate_current_withdrawal_limit".to_string(),
+                known_versions: vec![0],
+                received: version,
+            })),
+        }
+    }
+}
diff --git a/packages/rs-drive/src/drive/identity/withdrawals/calculate_current_withdrawal_limit/v0/mod.rs b/packages/rs-drive/src/drive/identity/withdrawals/calculate_current_withdrawal_limit/v0/mod.rs
new file mode 100644
index 00000000000..f7610ec941c
--- /dev/null
+++ b/packages/rs-drive/src/drive/identity/withdrawals/calculate_current_withdrawal_limit/v0/mod.rs
@@ -0,0 +1,86 @@
+use crate::drive::balances::TOTAL_SYSTEM_CREDITS_STORAGE_KEY;
+use crate::drive::identity::withdrawals::paths::{
+    get_withdrawal_root_path, WITHDRAWAL_TRANSACTIONS_SUM_AMOUNT_TREE_KEY,
+};
+use crate::drive::system::misc_path;
+use crate::drive::Drive;
+use crate::error::drive::DriveError;
+use crate::error::Error;
+use crate::util::grove_operations::DirectQueryType;
+use dpp::fee::Credits;
+use dpp::withdrawal::daily_withdrawal_limit::daily_withdrawal_limit;
+use grovedb::TransactionArg;
+use platform_version::version::PlatformVersion;
+
+impl Drive {
+    /// Calculates the current withdrawal limit based on the available credits in the platform
+    /// and the amount already withdrawn in the last 24 hours.
+    ///
+    /// This function considers two main components to calculate the current withdrawal limit:
+    /// 1. The total maximum withdrawal allowed in a 24-hour period (`daily_maximum`).
+    /// 2. The amount already withdrawn in the last 24 hours (`withdrawal_amount_in_last_day`).
+    ///
+    /// The formula used is: `daily_maximum - withdrawal_amount_in_last_day`.
+    /// If the withdrawal amount in the last 24 hours exceeds the daily maximum, the result will be 0.
+    ///
+    /// # Parameters
+    ///
+    /// * `transaction`: The transaction context to use for querying data.
+    /// * `platform_version`: The version of the platform being used, containing relevant configuration details.
+    ///
+    /// # Returns
+    ///
+    /// * `Ok(Credits)`: The calculated current withdrawal limit, which is the maximum amount that can still be withdrawn in the current 24-hour window.
+    /// * `Err(Error)`: Returns an error if there was an issue retrieving the total credits, the daily maximum, or the already withdrawn amount.
+    ///
+    /// # Errors
+    ///
+    /// * `Error::Drive(DriveError::CriticalCorruptedState)`:
+    ///   - If the total credits in the platform cannot be found, indicating a critical state corruption.
+    ///   - If the withdrawal amount in the last 24 hours is negative, which should not happen.
+    pub(super) fn calculate_current_withdrawal_limit_v0(
+        &self,
+        transaction: TransactionArg,
+        platform_version: &PlatformVersion,
+    ) -> Result<Credits, Error> {
+        // The current withdrawal limit has two components
+        // 1. The total maximum that we are allowed to do in 24 hours
+        // 2. The amount that we have already withdrawn in the last 24 hours
+        let mut drive_operations = vec![];
+
+        let path_holding_total_credits = misc_path();
+        let total_credits_in_platform = self
+            .grove_get_raw_value_u64_from_encoded_var_vec(
+                (&path_holding_total_credits).into(),
+                TOTAL_SYSTEM_CREDITS_STORAGE_KEY,
+                DirectQueryType::StatefulDirectQuery,
+                transaction,
+                &mut drive_operations,
+                &platform_version.drive,
+            )?
+            .ok_or(Error::Drive(DriveError::CriticalCorruptedState(
+                "Credits not found in Platform",
+            )))?;
+
+        // Let's get the amount that we are allowed to get in the last 24 hours.
+        let daily_maximum = daily_withdrawal_limit(total_credits_in_platform, platform_version)?;
+
+        let withdrawal_amount_in_last_day: u64 = self
+            .grove_get_sum_tree_total_value(
+                (&get_withdrawal_root_path()).into(),
+                &WITHDRAWAL_TRANSACTIONS_SUM_AMOUNT_TREE_KEY,
+                DirectQueryType::StatefulDirectQuery,
+                transaction,
+                &mut drive_operations,
+                &platform_version.drive,
+            )?
+            .try_into()
+            .map_err(|_| {
+                Error::Drive(DriveError::CriticalCorruptedState(
+                    "Withdrawal amount in last day is negative",
+                ))
+            })?;
+
+        Ok(daily_maximum.saturating_sub(withdrawal_amount_in_last_day))
+    }
+}
diff --git a/packages/rs-drive/src/drive/identity/withdrawals/document/find_withdrawal_documents_by_status_and_transaction_indices/mod.rs b/packages/rs-drive/src/drive/identity/withdrawals/document/find_withdrawal_documents_by_status_and_transaction_indices/mod.rs
index d4214a53e70..ca67af28bc7 100644
--- a/packages/rs-drive/src/drive/identity/withdrawals/document/find_withdrawal_documents_by_status_and_transaction_indices/mod.rs
+++ b/packages/rs-drive/src/drive/identity/withdrawals/document/find_withdrawal_documents_by_status_and_transaction_indices/mod.rs
@@ -25,7 +25,7 @@ impl Drive {
             .identity
             .withdrawals
             .document
-            .find_up_to_100_withdrawal_documents_by_status_and_transaction_indices
+            .find_withdrawal_documents_by_status_and_transaction_indices
         {
             0 => self.find_withdrawal_documents_by_status_and_transaction_indices_v0(
                 status,
@@ -35,8 +35,7 @@ impl Drive {
                 platform_version,
             ),
             version => Err(Error::Drive(DriveError::UnknownVersionMismatch {
-                method: "find_up_to_100_withdrawal_documents_by_status_and_transaction_indices"
-                    .to_string(),
+                method: "find_withdrawal_documents_by_status_and_transaction_indices".to_string(),
                 known_versions: vec![0],
                 received: version,
             })),
diff --git a/packages/rs-drive/src/drive/identity/withdrawals/document/find_withdrawal_documents_by_status_and_transaction_indices/v0/mod.rs b/packages/rs-drive/src/drive/identity/withdrawals/document/find_withdrawal_documents_by_status_and_transaction_indices/v0/mod.rs
index b04bc85b5b4..e6b4a8e6406 100644
--- a/packages/rs-drive/src/drive/identity/withdrawals/document/find_withdrawal_documents_by_status_and_transaction_indices/v0/mod.rs
+++ b/packages/rs-drive/src/drive/identity/withdrawals/document/find_withdrawal_documents_by_status_and_transaction_indices/v0/mod.rs
@@ -16,6 +16,7 @@ use std::collections::BTreeMap;
 impl Drive {
     // TODO(withdrawals): Currently it queries only up to 100 documents.
     //  It works while we don't have pooling
+    // This should be a pathquery directly instead of a drive query for efficiency
 
     pub(super) fn find_withdrawal_documents_by_status_and_transaction_indices_v0(
         &self,
diff --git a/packages/rs-drive/src/drive/identity/withdrawals/document/mod.rs b/packages/rs-drive/src/drive/identity/withdrawals/document/mod.rs
index 0f3e6aec298..e047ea5a45f 100644
--- a/packages/rs-drive/src/drive/identity/withdrawals/document/mod.rs
+++ b/packages/rs-drive/src/drive/identity/withdrawals/document/mod.rs
@@ -1,4 +1,4 @@
-/// This module dedicated for a versioned fetch_up_to_100_oldest_withdrawal_documents_by_status
+/// This module dedicated for a versioned fetch_oldest_withdrawal_documents_by_status
 pub mod fetch_oldest_withdrawal_documents_by_status;
-/// This module dedicated for a versioned find_up_to_100_withdrawal_documents_by_status_and_transaction_indices
+/// This module dedicated for a versioned find_withdrawal_documents_by_status_and_transaction_indices
 pub mod find_withdrawal_documents_by_status_and_transaction_indices;
diff --git a/packages/rs-drive/src/drive/identity/withdrawals/mod.rs b/packages/rs-drive/src/drive/identity/withdrawals/mod.rs
index 1571e0e230a..25296ff37a6 100644
--- a/packages/rs-drive/src/drive/identity/withdrawals/mod.rs
+++ b/packages/rs-drive/src/drive/identity/withdrawals/mod.rs
@@ -1,6 +1,7 @@
 /// Functions related to withdrawal documents
 pub mod document;
 
+mod calculate_current_withdrawal_limit;
 /// Functions and constants related to GroveDB paths
 pub mod paths;
 /// Functions related to withdrawal transactions
diff --git a/packages/rs-drive/src/drive/identity/withdrawals/paths.rs b/packages/rs-drive/src/drive/identity/withdrawals/paths.rs
index 0dfcb3c4c94..fd1e3d04058 100644
--- a/packages/rs-drive/src/drive/identity/withdrawals/paths.rs
+++ b/packages/rs-drive/src/drive/identity/withdrawals/paths.rs
@@ -1,17 +1,22 @@
-use grovedb::Element;
-
 use crate::drive::{Drive, RootTree};
 use crate::util::batch::grovedb_op_batch::GroveDbOpBatchV0Methods;
 use crate::util::batch::GroveDbOpBatch;
+use grovedb::Element;
+use platform_version::version::PlatformVersion;
 
 /// constant key for transaction counter
 pub const WITHDRAWAL_TRANSACTIONS_NEXT_INDEX_KEY: [u8; 1] = [0];
 /// constant id for subtree containing transactions queue
 pub const WITHDRAWAL_TRANSACTIONS_QUEUE_KEY: [u8; 1] = [1];
+/// constant id for subtree containing the sum of withdrawals
+pub const WITHDRAWAL_TRANSACTIONS_SUM_AMOUNT_TREE_KEY: [u8; 1] = [2];
 
 impl Drive {
     /// Add operations for creating initial withdrawal state structure
-    pub fn add_initial_withdrawal_state_structure_operations(batch: &mut GroveDbOpBatch) {
+    pub fn add_initial_withdrawal_state_structure_operations(
+        batch: &mut GroveDbOpBatch,
+        platform_version: &PlatformVersion,
+    ) {
         batch.add_insert(
             vec![vec![RootTree::WithdrawalTransactions as u8]],
             WITHDRAWAL_TRANSACTIONS_NEXT_INDEX_KEY.to_vec(),
@@ -22,6 +27,13 @@ impl Drive {
             vec![vec![RootTree::WithdrawalTransactions as u8]],
             WITHDRAWAL_TRANSACTIONS_QUEUE_KEY.to_vec(),
         );
+
+        if platform_version.protocol_version >= 4 {
+            batch.add_insert_empty_sum_tree(
+                vec![vec![RootTree::WithdrawalTransactions as u8]],
+                WITHDRAWAL_TRANSACTIONS_SUM_AMOUNT_TREE_KEY.to_vec(),
+            );
+        }
     }
 }
 
@@ -50,3 +62,19 @@ pub fn get_withdrawal_transactions_queue_path() -> [&'static [u8]; 2] {
         &WITHDRAWAL_TRANSACTIONS_QUEUE_KEY,
     ]
 }
+
+/// Helper function to get the withdrawal transactions sum tree path as Vec
+pub fn get_withdrawal_transactions_sum_tree_path_vec() -> Vec<Vec<u8>> {
+    vec![
+        vec![RootTree::WithdrawalTransactions as u8],
+        WITHDRAWAL_TRANSACTIONS_SUM_AMOUNT_TREE_KEY.to_vec(),
+    ]
+}
+
+/// Helper function to get the withdrawal transactions sum tree path as [u8]
+pub fn get_withdrawal_transactions_sum_tree_path() -> [&'static [u8]; 2] {
+    [
+        Into::<&[u8; 1]>::into(RootTree::WithdrawalTransactions),
+        &WITHDRAWAL_TRANSACTIONS_SUM_AMOUNT_TREE_KEY,
+    ]
+}
diff --git a/packages/rs-drive/src/drive/identity/withdrawals/transaction/queue/add_enqueue_untied_withdrawal_transaction_operations/mod.rs b/packages/rs-drive/src/drive/identity/withdrawals/transaction/queue/add_enqueue_untied_withdrawal_transaction_operations/mod.rs
index 1b6a52728c1..f23c60bba41 100644
--- a/packages/rs-drive/src/drive/identity/withdrawals/transaction/queue/add_enqueue_untied_withdrawal_transaction_operations/mod.rs
+++ b/packages/rs-drive/src/drive/identity/withdrawals/transaction/queue/add_enqueue_untied_withdrawal_transaction_operations/mod.rs
@@ -2,6 +2,7 @@ use crate::drive::Drive;
 use crate::error::drive::DriveError;
 use crate::error::Error;
 use crate::util::batch::DriveOperation;
+use dpp::fee::Credits;
 use dpp::withdrawal::WithdrawalTransactionIndexAndBytes;
 use platform_version::version::PlatformVersion;
 
@@ -12,6 +13,7 @@ impl Drive {
     pub fn add_enqueue_untied_withdrawal_transaction_operations(
         &self,
         withdrawal_transactions: Vec<WithdrawalTransactionIndexAndBytes>,
+        total_sum: Credits,
         drive_operation_types: &mut Vec<DriveOperation>,
         platform_version: &PlatformVersion,
     ) -> Result<(), Error> {
@@ -27,6 +29,7 @@ impl Drive {
             0 => {
                 self.add_enqueue_untied_withdrawal_transaction_operations_v0(
                     withdrawal_transactions,
+                    total_sum,
                     drive_operation_types,
                 );
 
diff --git a/packages/rs-drive/src/drive/identity/withdrawals/transaction/queue/add_enqueue_untied_withdrawal_transaction_operations/v0/mod.rs b/packages/rs-drive/src/drive/identity/withdrawals/transaction/queue/add_enqueue_untied_withdrawal_transaction_operations/v0/mod.rs
index a9d8a604e2d..1df5e12da9d 100644
--- a/packages/rs-drive/src/drive/identity/withdrawals/transaction/queue/add_enqueue_untied_withdrawal_transaction_operations/v0/mod.rs
+++ b/packages/rs-drive/src/drive/identity/withdrawals/transaction/queue/add_enqueue_untied_withdrawal_transaction_operations/v0/mod.rs
@@ -1,12 +1,16 @@
 use crate::drive::Drive;
 use crate::util::batch::drive_op_batch::WithdrawalOperationType;
 use crate::util::batch::DriveOperation;
+use dpp::fee::Credits;
+use dpp::prelude::TimestampMillis;
 use dpp::withdrawal::WithdrawalTransactionIndexAndBytes;
+const DAY_AND_A_HOUR_IN_MS: TimestampMillis = 90_000_000; //25 hours
 
 impl Drive {
     pub(super) fn add_enqueue_untied_withdrawal_transaction_operations_v0(
         &self,
         withdrawal_transactions: Vec<WithdrawalTransactionIndexAndBytes>,
+        total_sum: Credits,
         drive_operation_types: &mut Vec<DriveOperation>,
     ) {
         if !withdrawal_transactions.is_empty() {
@@ -15,6 +19,13 @@ impl Drive {
                     withdrawal_transactions,
                 },
             ));
+            drive_operation_types.push(DriveOperation::WithdrawalOperation(
+                WithdrawalOperationType::ReserveWithdrawalAmount {
+                    amount: total_sum,
+                    // Best to use a constant here and not a versioned item as this most likely will not change
+                    expiration_after: DAY_AND_A_HOUR_IN_MS,
+                },
+            ));
         }
     }
 }
diff --git a/packages/rs-drive/src/drive/identity/withdrawals/transaction/queue/mod.rs b/packages/rs-drive/src/drive/identity/withdrawals/transaction/queue/mod.rs
index e519f56bd50..8926ea5c0c3 100644
--- a/packages/rs-drive/src/drive/identity/withdrawals/transaction/queue/mod.rs
+++ b/packages/rs-drive/src/drive/identity/withdrawals/transaction/queue/mod.rs
@@ -37,6 +37,7 @@ mod tests {
         drive
             .add_enqueue_untied_withdrawal_transaction_operations(
                 withdrawals,
+                100,
                 &mut drive_operations,
                 platform_version,
             )
diff --git a/packages/rs-drive/src/drive/initialization/v0/mod.rs b/packages/rs-drive/src/drive/initialization/v0/mod.rs
index e1c4ee377d4..39387184e54 100644
--- a/packages/rs-drive/src/drive/initialization/v0/mod.rs
+++ b/packages/rs-drive/src/drive/initialization/v0/mod.rs
@@ -170,7 +170,7 @@ impl Drive {
         )?;
 
         // In Withdrawals
-        Drive::add_initial_withdrawal_state_structure_operations(&mut batch);
+        Drive::add_initial_withdrawal_state_structure_operations(&mut batch, platform_version);
 
         // For Versioning via forks
         Drive::add_initial_fork_update_structure_operations(&mut batch);
diff --git a/packages/rs-drive/src/state_transition_action/identity/identity_credit_withdrawal/v0/transformer.rs b/packages/rs-drive/src/state_transition_action/identity/identity_credit_withdrawal/v0/transformer.rs
index 8da24e5759a..e2c443d9ab7 100644
--- a/packages/rs-drive/src/state_transition_action/identity/identity_credit_withdrawal/v0/transformer.rs
+++ b/packages/rs-drive/src/state_transition_action/identity/identity_credit_withdrawal/v0/transformer.rs
@@ -16,7 +16,7 @@ use dpp::document::{Document, DocumentV0};
 use dpp::identity::core_script::CoreScript;
 use dpp::identity::hash::IdentityPublicKeyHashMethodsV0;
 use dpp::identity::identity_public_key::accessors::v0::IdentityPublicKeyGettersV0;
-use dpp::identity::IdentityPublicKey;
+use dpp::identity::{IdentityPublicKey, KeyType};
 use dpp::platform_value::platform_value;
 use dpp::state_transition::identity_credit_withdrawal_transition::v1::IdentityCreditWithdrawalTransitionV1;
 use dpp::state_transition::state_transitions::identity::identity_credit_withdrawal_transition::v0::IdentityCreditWithdrawalTransitionV0;
@@ -131,19 +131,27 @@ impl IdentityCreditWithdrawalTransitionActionV0 {
                     ));
                 }
             }
-            if !key.key_type().is_core_address_key_type() {
-                return Ok(ConsensusValidationResult::new_with_error(
-                    ConsensusError::StateError(
-                        StateError::NoTransferKeyForCoreWithdrawalAvailableError(
-                            NoTransferKeyForCoreWithdrawalAvailableError::new(
-                                identity_credit_withdrawal.identity_id,
+            match key.key_type() {
+                KeyType::ECDSA_HASH160 => {
+                    // We should get the withdrawal address
+                    CoreScript::new_p2pkh(key.public_key_hash()?).to_bytes()
+                }
+                KeyType::BIP13_SCRIPT_HASH => {
+                    // We should get the withdrawal address
+                    CoreScript::new_p2sh(key.public_key_hash()?).to_bytes()
+                }
+                _ => {
+                    return Ok(ConsensusValidationResult::new_with_error(
+                        ConsensusError::StateError(
+                            StateError::NoTransferKeyForCoreWithdrawalAvailableError(
+                                NoTransferKeyForCoreWithdrawalAvailableError::new(
+                                    identity_credit_withdrawal.identity_id,
+                                ),
                             ),
                         ),
-                    ),
-                ));
+                    ));
+                }
             }
-            // We should get the withdrawal address
-            CoreScript::new_p2pkh(key.public_key_hash()?).to_bytes()
         };
 
         let mut entropy = Vec::new();
diff --git a/packages/rs-drive/src/util/batch/drive_op_batch/withdrawals.rs b/packages/rs-drive/src/util/batch/drive_op_batch/withdrawals.rs
index 7ad820d6013..a24c5b8efe5 100644
--- a/packages/rs-drive/src/util/batch/drive_op_batch/withdrawals.rs
+++ b/packages/rs-drive/src/util/batch/drive_op_batch/withdrawals.rs
@@ -2,14 +2,17 @@ use std::collections::HashMap;
 
 use crate::drive::identity::withdrawals::paths::{
     get_withdrawal_root_path_vec, get_withdrawal_transactions_queue_path,
-    get_withdrawal_transactions_queue_path_vec, WITHDRAWAL_TRANSACTIONS_NEXT_INDEX_KEY,
+    get_withdrawal_transactions_queue_path_vec, get_withdrawal_transactions_sum_tree_path_vec,
+    WITHDRAWAL_TRANSACTIONS_NEXT_INDEX_KEY,
 };
-use crate::util::grove_operations::BatchDeleteApplyType;
+use crate::util::grove_operations::{BatchDeleteApplyType, BatchInsertApplyType};
 use crate::util::object_size_info::PathKeyElementInfo;
 use crate::{drive::Drive, error::Error, fees::op::LowLevelDriveOperation};
 use dpp::block::block_info::BlockInfo;
 
 use super::DriveLowLevelOperationConverter;
+use dpp::fee::{Credits, SignedCredits};
+use dpp::prelude::TimestampMillis;
 use dpp::version::PlatformVersion;
 use dpp::withdrawal::{WithdrawalTransactionIndex, WithdrawalTransactionIndexAndBytes};
 use grovedb::Element;
@@ -33,6 +36,13 @@ pub enum WithdrawalOperationType {
         /// withdrawal transaction tuple with id and bytes
         index: WithdrawalTransactionIndex,
     },
+    /// Reserve an amount in the system for withdrawals, the reservation will expire at the date given
+    ReserveWithdrawalAmount {
+        /// amount to reserve
+        amount: Credits,
+        /// expiration date
+        expiration_after: TimestampMillis,
+    },
 }
 
 impl DriveLowLevelOperationConverter for WithdrawalOperationType {
@@ -42,7 +52,7 @@ impl DriveLowLevelOperationConverter for WithdrawalOperationType {
         _estimated_costs_only_with_layer_info: &mut Option<
             HashMap<KeyInfoPath, EstimatedLayerInformation>,
         >,
-        _block_info: &BlockInfo,
+        block_info: &BlockInfo,
         transaction: TransactionArg,
         platform_version: &PlatformVersion,
     ) -> Result<Vec<LowLevelDriveOperation>, Error> {
@@ -73,7 +83,7 @@ impl DriveLowLevelOperationConverter for WithdrawalOperationType {
 
                 for (index, bytes) in withdrawal_transactions {
                     drive.batch_insert(
-                        PathKeyElementInfo::PathKeyElement::<'_, 1>((
+                        PathKeyElementInfo::PathKeyElement::<'_, 0>((
                             path.clone(),
                             index.to_be_bytes().to_vec(),
                             Element::Item(bytes, None),
@@ -85,6 +95,30 @@ impl DriveLowLevelOperationConverter for WithdrawalOperationType {
 
                 Ok(drive_operations)
             }
+            WithdrawalOperationType::ReserveWithdrawalAmount {
+                amount,
+                expiration_after,
+            } => {
+                let mut drive_operations = vec![];
+
+                let expiration_date = block_info.time_ms + expiration_after;
+
+                let sum_path = get_withdrawal_transactions_sum_tree_path_vec();
+
+                drive.batch_insert_sum_item_or_add_to_if_already_exists(
+                    PathKeyElementInfo::PathKeyElement::<'_, 0>((
+                        sum_path.clone(),
+                        expiration_date.to_be_bytes().to_vec(),
+                        Element::SumItem(amount as SignedCredits, None),
+                    )),
+                    BatchInsertApplyType::StatefulBatchInsert,
+                    transaction,
+                    &mut drive_operations,
+                    &platform_version.drive,
+                )?;
+
+                Ok(drive_operations)
+            }
             WithdrawalOperationType::DeleteWithdrawalTransaction { index } => {
                 let mut drive_operations = vec![];
 
diff --git a/packages/rs-drive/src/util/grove_operations/batch_delete_items_in_path_query/mod.rs b/packages/rs-drive/src/util/grove_operations/batch_delete_items_in_path_query/mod.rs
new file mode 100644
index 00000000000..24b03094ef4
--- /dev/null
+++ b/packages/rs-drive/src/util/grove_operations/batch_delete_items_in_path_query/mod.rs
@@ -0,0 +1,56 @@
+mod v0;
+
+use crate::drive::Drive;
+use crate::error::drive::DriveError;
+use crate::error::Error;
+use crate::fees::op::LowLevelDriveOperation;
+use crate::util::grove_operations::BatchDeleteApplyType;
+
+use dpp::version::drive_versions::DriveVersion;
+
+use grovedb::{PathQuery, TransactionArg};
+
+impl Drive {
+    /// Pushes multiple "delete element" operations for items in a given path based on a `PathQuery` to `drive_operations`.
+    ///
+    /// # Parameters
+    /// * `path_query`: The path query specifying the items to delete within the path.
+    /// * `error_if_intermediate_path_tree_not_present`: Tells the function to either error or do nothing if an intermediate tree is not present.
+    /// * `apply_type`: The apply type for the delete operations.
+    /// * `transaction`: The transaction argument.
+    /// * `drive_operations`: The vector containing low-level drive operations.
+    /// * `drive_version`: The drive version to select the correct function version to run.
+    ///
+    /// # Returns
+    /// * `Ok(())` if the operation was successful.
+    /// * `Err(DriveError::UnknownVersionMismatch)` if the drive version does not match known versions.
+    pub fn batch_delete_items_in_path_query(
+        &self,
+        path_query: &PathQuery,
+        error_if_intermediate_path_tree_not_present: bool,
+        apply_type: BatchDeleteApplyType,
+        transaction: TransactionArg,
+        drive_operations: &mut Vec<LowLevelDriveOperation>,
+        drive_version: &DriveVersion,
+    ) -> Result<(), Error> {
+        match drive_version
+            .grove_methods
+            .batch
+            .batch_delete_items_in_path_query
+        {
+            0 => self.batch_delete_items_in_path_query_v0(
+                path_query,
+                error_if_intermediate_path_tree_not_present,
+                apply_type,
+                transaction,
+                drive_operations,
+                drive_version,
+            ),
+            version => Err(Error::Drive(DriveError::UnknownVersionMismatch {
+                method: "batch_delete_items_in_path_query".to_string(),
+                known_versions: vec![0],
+                received: version,
+            })),
+        }
+    }
+}
diff --git a/packages/rs-drive/src/util/grove_operations/batch_delete_items_in_path_query/v0/mod.rs b/packages/rs-drive/src/util/grove_operations/batch_delete_items_in_path_query/v0/mod.rs
new file mode 100644
index 00000000000..a530776986d
--- /dev/null
+++ b/packages/rs-drive/src/util/grove_operations/batch_delete_items_in_path_query/v0/mod.rs
@@ -0,0 +1,588 @@
+use crate::drive::Drive;
+use crate::error::drive::DriveError;
+use crate::error::Error;
+use crate::fees::op::LowLevelDriveOperation;
+use crate::fees::op::LowLevelDriveOperation::GroveOperation;
+use crate::util::grove_operations::{push_drive_operation_result, BatchDeleteApplyType};
+use grovedb::batch::key_info::KeyInfo;
+use grovedb::batch::KeyInfoPath;
+use grovedb::operations::delete::DeleteOptions;
+use grovedb::query_result_type::QueryResultType;
+use grovedb::{GroveDb, PathQuery, TransactionArg};
+use grovedb_storage::rocksdb_storage::RocksDbStorage;
+use platform_version::version::drive_versions::DriveVersion;
+
+impl Drive {
+    /// Version 0 implementation of the "delete multiple elements" operation based on a `PathQuery`.
+    /// Deletes items in the specified path that match the given query.
+    ///
+    /// # Parameters
+    /// * `path_query`: The path query specifying the items to delete within the path.
+    /// * `error_if_intermediate_path_tree_not_present`: Tells the function to either error or do nothing if an intermediate tree is not present.
+    /// * `apply_type`: The apply type for the delete operations.
+    /// * `transaction`: The transaction argument.
+    /// * `drive_operations`: The vector containing low-level drive operations.
+    /// * `drive_version`: The drive version to select the correct function version to run.
+    ///
+    /// # Returns
+    /// * `Ok(())` if the operation was successful.
+    /// * `Err(DriveError::CorruptedCodeExecution)` if the operation is not supported.
+    pub(super) fn batch_delete_items_in_path_query_v0(
+        &self,
+        path_query: &PathQuery,
+        error_if_intermediate_path_tree_not_present: bool,
+        apply_type: BatchDeleteApplyType,
+        transaction: TransactionArg,
+        drive_operations: &mut Vec<LowLevelDriveOperation>,
+        drive_version: &DriveVersion,
+    ) -> Result<(), Error> {
+        if path_query.query.limit == None {
+            Error::Drive(DriveError::NotSupported(
+                "Limits are required for path_query",
+            ));
+        }
+        let query_result = if path_query
+            .query
+            .query
+            .items
+            .iter()
+            .all(|query_item| query_item.is_key())
+        {
+            // Fetch the elements that match the path query
+            let query_result = self.grove_get_raw_path_query_with_optional(
+                path_query,
+                error_if_intermediate_path_tree_not_present,
+                transaction,
+                drive_operations,
+                drive_version,
+            )?;
+
+            query_result
+                .into_iter()
+                .filter_map(|(path, key, maybe_element)| {
+                    maybe_element.map(|element| (path, key, element))
+                })
+                .collect()
+        } else {
+            self.grove_get_raw_path_query(
+                path_query,
+                transaction,
+                QueryResultType::QueryPathKeyElementTrioResultType,
+                drive_operations,
+                drive_version,
+            )?
+            .0
+            .to_path_key_elements()
+        };
+
+        // Iterate over each element and add a delete operation for it
+        for (path, key, _) in query_result {
+            let current_batch_operations =
+                LowLevelDriveOperation::grovedb_operations_batch(drive_operations);
+            let options = DeleteOptions {
+                allow_deleting_non_empty_trees: false,
+                deleting_non_empty_trees_returns_error: true,
+                base_root_storage_is_free: true,
+                validate_tree_at_path_exists: false,
+            };
+            let delete_operation = match apply_type {
+                BatchDeleteApplyType::StatelessBatchDelete {
+                    is_sum_tree,
+                    estimated_key_size,
+                    estimated_value_size,
+                } => GroveDb::average_case_delete_operation_for_delete::<RocksDbStorage>(
+                    &KeyInfoPath::from_known_owned_path(path.to_vec()),
+                    &KeyInfo::KnownKey(key.to_vec()),
+                    is_sum_tree,
+                    false,
+                    true,
+                    0,
+                    (estimated_key_size, estimated_value_size),
+                    &drive_version.grove_version,
+                )
+                .map(|r| r.map(Some)),
+                BatchDeleteApplyType::StatefulBatchDelete {
+                    is_known_to_be_subtree_with_sum,
+                } => self.grove.delete_operation_for_delete_internal(
+                    (path.as_slice()).into(),
+                    key.as_slice(),
+                    &options,
+                    is_known_to_be_subtree_with_sum,
+                    &current_batch_operations.operations,
+                    transaction,
+                    &drive_version.grove_version,
+                ),
+            };
+
+            if let Some(delete_operation) =
+                push_drive_operation_result(delete_operation, drive_operations)?
+            {
+                // Add the delete operation to the batch of drive operations
+                drive_operations.push(GroveOperation(delete_operation));
+            }
+        }
+
+        Ok(())
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::util::grove_operations::QueryType;
+    use crate::util::test_helpers::setup::setup_drive;
+    use crate::{
+        error::Error,
+        grovedb::{Element, PathQuery, Query},
+        util::grove_operations::BatchDeleteApplyType,
+    };
+    use assert_matches::assert_matches;
+    use grovedb::SizedQuery;
+    use grovedb_path::SubtreePath;
+    use platform_version::version::PlatformVersion;
+
+    #[test]
+    fn test_batch_delete_items_in_path_query_success() {
+        // Set up a test drive instance and transaction
+        let drive = setup_drive(None);
+        let platform_version = PlatformVersion::latest();
+        let transaction = drive.grove.start_transaction();
+
+        // Insert some elements that will be matched and deleted
+        let path = vec![b"root".to_vec()];
+        let key = b"key".to_vec();
+        let element = Element::new_item(b"value".to_vec());
+
+        drive
+            .grove_insert_empty_tree(
+                SubtreePath::empty(),
+                b"root",
+                Some(&transaction),
+                None,
+                &mut vec![],
+                &platform_version.drive,
+            )
+            .expect("expected insert root tree");
+
+        drive
+            .grove
+            .insert(
+                path.as_slice(),
+                &key,
+                element.clone(),
+                None,
+                Some(&transaction),
+                &platform_version.drive.grove_version,
+            )
+            .unwrap()
+            .expect("expected insert");
+
+        // Create a path query that matches the inserted elements
+        let mut query = Query::new();
+        query.insert_key(key.clone());
+        let path_query = PathQuery::new(path.clone(), SizedQuery::new(query, Some(1), None));
+
+        // Set up the apply type and drive operations vector
+        let apply_type = BatchDeleteApplyType::StatefulBatchDelete {
+            is_known_to_be_subtree_with_sum: None,
+        };
+        let mut drive_operations = Vec::new();
+
+        // Call the function
+        drive
+            .batch_delete_items_in_path_query_v0(
+                &path_query,
+                true,
+                apply_type,
+                Some(&transaction),
+                &mut drive_operations,
+                &platform_version.drive,
+            )
+            .expect("expected to batch delete items");
+
+        drive
+            .apply_batch_low_level_drive_operations(
+                None,
+                Some(&transaction),
+                drive_operations,
+                &mut vec![],
+                &platform_version.drive,
+            )
+            .expect("expected to apply operations");
+
+        // Commit the transaction
+        drive
+            .grove
+            .commit_transaction(transaction)
+            .unwrap()
+            .expect("expected to commit transaction");
+
+        // Verify the element has been deleted
+        let get_result = drive.grove_get(
+            path.as_slice().into(),
+            &key,
+            QueryType::StatefulQuery,
+            None,
+            &mut vec![],
+            &platform_version.drive,
+        );
+        assert_matches!(
+            get_result,
+            Err(Error::GroveDB(grovedb::Error::PathKeyNotFound(_)))
+        );
+    }
+
+    #[test]
+    fn test_batch_delete_items_in_path_query_range_query() {
+        // Set up a test drive instance and transaction
+        let drive = setup_drive(None);
+        let platform_version = PlatformVersion::latest();
+        let transaction = drive.grove.start_transaction();
+
+        // Insert the root tree
+        drive
+            .grove_insert_empty_tree(
+                SubtreePath::empty(),
+                b"root",
+                Some(&transaction),
+                None,
+                &mut vec![],
+                &platform_version.drive,
+            )
+            .expect("expected to insert root tree");
+
+        // Insert three elements with keys 1, 2, and 3
+        let path = vec![b"root".to_vec()];
+        let key1 = b"1".to_vec();
+        let key2 = b"2".to_vec();
+        let key3 = b"3".to_vec();
+        let element = Element::new_item(b"value".to_vec());
+
+        drive
+            .grove
+            .insert(
+                path.as_slice(),
+                &key1,
+                element.clone(),
+                None,
+                Some(&transaction),
+                &platform_version.drive.grove_version,
+            )
+            .unwrap()
+            .expect("expected insert for key 1");
+
+        drive
+            .grove
+            .insert(
+                path.as_slice(),
+                &key2,
+                element.clone(),
+                None,
+                Some(&transaction),
+                &platform_version.drive.grove_version,
+            )
+            .unwrap()
+            .expect("expected insert for key 2");
+
+        drive
+            .grove
+            .insert(
+                path.as_slice(),
+                &key3,
+                element.clone(),
+                None,
+                Some(&transaction),
+                &platform_version.drive.grove_version,
+            )
+            .unwrap()
+            .expect("expected insert for key 3");
+
+        // Create a range path query that matches keys less than 3
+        let mut query = Query::new();
+        query.insert_range_to(..b"3".to_vec());
+        let path_query = PathQuery::new(path.clone(), SizedQuery::new(query, Some(100), None));
+
+        // Set up the apply type and drive operations vector
+        let apply_type = BatchDeleteApplyType::StatefulBatchDelete {
+            is_known_to_be_subtree_with_sum: None,
+        };
+        let mut drive_operations = Vec::new();
+
+        // Call the function
+        drive
+            .batch_delete_items_in_path_query_v0(
+                &path_query,
+                true,
+                apply_type,
+                Some(&transaction),
+                &mut drive_operations,
+                &platform_version.drive,
+            )
+            .expect("expected to batch delete items");
+
+        // Apply batch operations
+        drive
+            .apply_batch_low_level_drive_operations(
+                None,
+                Some(&transaction),
+                drive_operations,
+                &mut vec![],
+                &platform_version.drive,
+            )
+            .expect("expected to apply operations");
+
+        // Commit the transaction
+        drive
+            .grove
+            .commit_transaction(transaction)
+            .unwrap()
+            .expect("expected to commit transaction");
+
+        // Verify that keys 1 and 2 have been deleted
+        let get_result_1 = drive.grove_get(
+            path.as_slice().into(),
+            &key1,
+            QueryType::StatefulQuery,
+            None,
+            &mut vec![],
+            &platform_version.drive,
+        );
+        assert_matches!(
+            get_result_1,
+            Err(Error::GroveDB(grovedb::Error::PathKeyNotFound(_)))
+        );
+
+        let get_result_2 = drive.grove_get(
+            path.as_slice().into(),
+            &key2,
+            QueryType::StatefulQuery,
+            None,
+            &mut vec![],
+            &platform_version.drive,
+        );
+        assert_matches!(
+            get_result_2,
+            Err(Error::GroveDB(grovedb::Error::PathKeyNotFound(_)))
+        );
+
+        // Verify that key 3 is still there
+        let get_result_3 = drive.grove_get(
+            path.as_slice().into(),
+            &key3,
+            QueryType::StatefulQuery,
+            None,
+            &mut vec![],
+            &platform_version.drive,
+        );
+        assert_matches!(get_result_3, Ok(Some(Element::Item(..))));
+    }
+
+    #[test]
+    fn test_batch_delete_items_in_path_query_no_elements() {
+        // Set up a test drive instance and transaction
+        let drive = setup_drive(None);
+        let platform_version = PlatformVersion::latest();
+        let transaction = drive.grove.start_transaction();
+
+        // Create the root tree to allow querying it
+        drive
+            .grove_insert_empty_tree(
+                SubtreePath::empty(),
+                b"root",
+                Some(&transaction),
+                None,
+                &mut vec![],
+                &platform_version.drive,
+            )
+            .expect("expected to insert root tree");
+
+        // Create a path query that does not match any elements
+        let path = vec![b"root".to_vec()];
+        let mut query = Query::new();
+        query.insert_key(b"non_existent_key".to_vec());
+        let path_query = PathQuery::new(path.clone(), SizedQuery::new(query, Some(1), None));
+
+        // Set up the apply type and drive operations vector
+        let apply_type = BatchDeleteApplyType::StatefulBatchDelete {
+            is_known_to_be_subtree_with_sum: None,
+        };
+        let mut drive_operations = Vec::new();
+
+        // Call the function
+        drive
+            .batch_delete_items_in_path_query_v0(
+                &path_query,
+                true,
+                apply_type,
+                Some(&transaction),
+                &mut drive_operations,
+                &platform_version.drive,
+            )
+            .expect("expected batch delete to succeed");
+
+        // Apply batch operations
+        drive
+            .apply_batch_low_level_drive_operations(
+                None,
+                Some(&transaction),
+                drive_operations,
+                &mut vec![],
+                &platform_version.drive,
+            )
+            .expect("expected to apply operations");
+
+        // Commit the transaction
+        drive
+            .grove
+            .commit_transaction(transaction)
+            .unwrap()
+            .expect("expected to commit transaction");
+
+        // Verify that no elements were deleted as no matching elements existed
+        let get_result = drive.grove_get(
+            path.as_slice().into(),
+            b"non_existent_key",
+            QueryType::StatefulQuery,
+            None,
+            &mut vec![],
+            &platform_version.drive,
+        );
+        assert_matches!(
+            get_result,
+            Err(Error::GroveDB(grovedb::Error::PathKeyNotFound(_)))
+        );
+    }
+
+    #[test]
+    fn test_batch_delete_items_in_path_query_intermediate_path_missing() {
+        // Set up a test drive instance and transaction
+        let drive = setup_drive(None);
+        let platform_version = PlatformVersion::latest();
+        let transaction = drive.grove.start_transaction();
+
+        // Create a path query with a missing intermediate path
+        let path = vec![b"missing_root".to_vec()];
+        let mut query = Query::new();
+        query.insert_key(b"key".to_vec());
+        let path_query = PathQuery::new(path.clone(), SizedQuery::new(query, Some(1), None));
+
+        // Set up the apply type and drive operations vector
+        let apply_type = BatchDeleteApplyType::StatefulBatchDelete {
+            is_known_to_be_subtree_with_sum: None,
+        };
+        let mut drive_operations = Vec::new();
+
+        // Call the function with error_if_intermediate_path_tree_not_present set to true
+        let result = drive.batch_delete_items_in_path_query_v0(
+            &path_query,
+            true,
+            apply_type,
+            Some(&transaction),
+            &mut drive_operations,
+            &platform_version.drive,
+        );
+
+        // Assert failure due to missing intermediate path
+        assert_matches!(
+            result,
+            Err(Error::GroveDB(grovedb::Error::PathParentLayerNotFound(_)))
+        );
+    }
+
+    #[test]
+    fn test_batch_delete_items_in_path_query_stateless_delete() {
+        // Set up a test drive instance and transaction
+        let drive = setup_drive(None);
+        let platform_version = PlatformVersion::latest();
+        let transaction = drive.grove.start_transaction();
+
+        // Insert some elements that will be matched and deleted
+        let path = vec![b"root".to_vec()];
+        let key = b"key".to_vec();
+        let element = Element::new_item(b"value".to_vec());
+
+        // Insert the root tree
+        drive
+            .grove_insert_empty_tree(
+                SubtreePath::empty(),
+                b"root",
+                Some(&transaction),
+                None,
+                &mut vec![],
+                &platform_version.drive,
+            )
+            .expect("expected to insert root tree");
+
+        // Insert an item in the tree
+        drive
+            .grove
+            .insert(
+                path.as_slice(),
+                &key,
+                element.clone(),
+                None,
+                Some(&transaction),
+                &platform_version.drive.grove_version,
+            )
+            .unwrap()
+            .expect("expected insert");
+
+        // Create a path query that matches the inserted elements
+        let mut query = Query::new();
+        query.insert_key(key.clone());
+        let path_query = PathQuery::new(path.clone(), SizedQuery::new(query, Some(1), None));
+
+        // Set up the stateless apply type with estimated sizes
+        let apply_type = BatchDeleteApplyType::StatelessBatchDelete {
+            is_sum_tree: false,
+            estimated_key_size: key.len() as u32,
+            estimated_value_size: element
+                .serialized_size(&platform_version.drive.grove_version)
+                .unwrap() as u32,
+        };
+        let mut drive_operations = Vec::new();
+
+        // Call the function
+        drive
+            .batch_delete_items_in_path_query_v0(
+                &path_query,
+                true,
+                apply_type,
+                Some(&transaction),
+                &mut drive_operations,
+                &platform_version.drive,
+            )
+            .expect("expected to batch delete items");
+
+        // Apply batch operations
+        drive
+            .apply_batch_low_level_drive_operations(
+                None,
+                Some(&transaction),
+                drive_operations,
+                &mut vec![],
+                &platform_version.drive,
+            )
+            .expect("expected to apply operations");
+
+        // Commit the transaction
+        drive
+            .grove
+            .commit_transaction(transaction)
+            .unwrap()
+            .expect("expected to commit transaction");
+
+        // Verify the element has been deleted
+        let get_result = drive.grove_get(
+            path.as_slice().into(),
+            &key,
+            QueryType::StatefulQuery,
+            None,
+            &mut vec![],
+            &platform_version.drive,
+        );
+        assert_matches!(
+            get_result,
+            Err(Error::GroveDB(grovedb::Error::PathKeyNotFound(_)))
+        );
+    }
+}
diff --git a/packages/rs-drive/src/util/grove_operations/batch_insert_if_not_exists/mod.rs b/packages/rs-drive/src/util/grove_operations/batch_insert_if_not_exists/mod.rs
index d14f1e2773b..c08390ba86f 100644
--- a/packages/rs-drive/src/util/grove_operations/batch_insert_if_not_exists/mod.rs
+++ b/packages/rs-drive/src/util/grove_operations/batch_insert_if_not_exists/mod.rs
@@ -23,7 +23,7 @@ impl Drive {
     /// * `drive_version`: The drive version to select the correct function version to run.
     ///
     /// # Returns
-    /// * `Ok(bool)` if the operation was successful. Returns true if the path key already exists without references.
+    /// * `Ok(bool)` if the operation was successful. Returns true if we were able to insert.
     /// * `Err(DriveError::UnknownVersionMismatch)` if the drive version does not match known versions.
     /// * `Err(DriveError::CorruptedCodeExecution)` if the operation is not supported.
     pub fn batch_insert_if_not_exists<const N: usize>(
diff --git a/packages/rs-drive/src/util/grove_operations/batch_insert_if_not_exists/v0/mod.rs b/packages/rs-drive/src/util/grove_operations/batch_insert_if_not_exists/v0/mod.rs
index 8bacd806062..8fc5c16c5b4 100644
--- a/packages/rs-drive/src/util/grove_operations/batch_insert_if_not_exists/v0/mod.rs
+++ b/packages/rs-drive/src/util/grove_operations/batch_insert_if_not_exists/v0/mod.rs
@@ -15,7 +15,7 @@ use grovedb::{GroveDb, TransactionArg};
 
 impl Drive {
     /// Pushes an "insert element if the path key does not yet exist" operation to `drive_operations`.
-    /// Returns true if the path key already exists without references.
+    /// Returns true if we inserted.
     pub(crate) fn batch_insert_if_not_exists_v0<const N: usize>(
         &self,
         path_key_element_info: PathKeyElementInfo<N>,
diff --git a/packages/rs-drive/src/util/grove_operations/batch_insert_if_not_exists_return_existing_element/mod.rs b/packages/rs-drive/src/util/grove_operations/batch_insert_if_not_exists_return_existing_element/mod.rs
new file mode 100644
index 00000000000..df437be5b29
--- /dev/null
+++ b/packages/rs-drive/src/util/grove_operations/batch_insert_if_not_exists_return_existing_element/mod.rs
@@ -0,0 +1,69 @@
+mod v0;
+
+use crate::util::grove_operations::BatchInsertApplyType;
+
+use crate::drive::Drive;
+use crate::error::drive::DriveError;
+use crate::error::Error;
+use crate::fees::op::LowLevelDriveOperation;
+use crate::util::object_size_info::PathKeyElementInfo;
+
+use dpp::version::drive_versions::DriveVersion;
+use grovedb::{Element, TransactionArg};
+
+impl Drive {
+    /// Pushes an "insert element if the path key does not yet exist" operation to `drive_operations`
+    /// and returns the existing element if it already exists.
+    ///
+    /// This function attempts to insert a new element into the database at a specified path and key.
+    /// If an element already exists at the given path and key, it returns the existing element without inserting the new one.
+    /// If the element does not exist, it inserts the new element and returns `Ok(None)`.
+    ///
+    /// The function dynamically selects the appropriate implementation version based on the provided `drive_version`.
+    ///
+    /// # Parameters
+    ///
+    /// * `path_key_element_info`: Information about the path, key, and element to be inserted.
+    ///   - Supports various configurations including direct references, owned elements, fixed-size keys, and estimated sizes.
+    /// * `apply_type`: Defines whether the operation is stateless or stateful, influencing how the insertion is handled.
+    /// * `transaction`: The transaction context in which the operation will be executed.
+    /// * `drive_operations`: A mutable reference to the list of drive operations where this operation will be appended.
+    /// * `drive_version`: The version of the drive being used, determining which function version to execute.
+    ///
+    /// # Returns
+    ///
+    /// * `Ok(Some(Element))`: If an element already exists at the specified path and key, it returns the existing element.
+    /// * `Ok(None)`: If the element was successfully inserted because it did not exist before.
+    /// * `Err(Error)`: Returns an error if:
+    ///   - The drive version provided is not supported (`Error::Drive(DriveError::UnknownVersionMismatch)`).
+    ///   - The operation is not supported in the current state due to invalid configurations or unsupported features (`Error::Drive(DriveError::NotSupportedPrivate)`).
+    ///
+    /// # Errors
+    ///
+    /// * `Error::Drive(DriveError::UnknownVersionMismatch)`: If the provided drive version is not supported by this function.
+    /// * `Error::Drive(DriveError::NotSupportedPrivate)`: If the function encounters unsupported configurations such as unknown element sizes for batch operations.
+    ///
+    pub fn batch_insert_if_not_exists_return_existing_element<const N: usize>(
+        &self,
+        path_key_element_info: PathKeyElementInfo<N>,
+        apply_type: BatchInsertApplyType,
+        transaction: TransactionArg,
+        drive_operations: &mut Vec<LowLevelDriveOperation>,
+        drive_version: &DriveVersion,
+    ) -> Result<Option<Element>, Error> {
+        match drive_version.grove_methods.batch.batch_insert_if_not_exists {
+            0 => self.batch_insert_if_not_exists_return_existing_element_v0(
+                path_key_element_info,
+                apply_type,
+                transaction,
+                drive_operations,
+                drive_version,
+            ),
+            version => Err(Error::Drive(DriveError::UnknownVersionMismatch {
+                method: "batch_insert_if_not_exists_return_existing_element".to_string(),
+                known_versions: vec![0],
+                received: version,
+            })),
+        }
+    }
+}
diff --git a/packages/rs-drive/src/util/grove_operations/batch_insert_if_not_exists_return_existing_element/v0/mod.rs b/packages/rs-drive/src/util/grove_operations/batch_insert_if_not_exists_return_existing_element/v0/mod.rs
new file mode 100644
index 00000000000..55f411caebc
--- /dev/null
+++ b/packages/rs-drive/src/util/grove_operations/batch_insert_if_not_exists_return_existing_element/v0/mod.rs
@@ -0,0 +1,165 @@
+use crate::util::grove_operations::BatchInsertApplyType;
+use crate::util::object_size_info::PathKeyElementInfo::{
+    PathFixedSizeKeyRefElement, PathKeyElement, PathKeyElementSize, PathKeyRefElement,
+    PathKeyUnknownElementSize,
+};
+
+use crate::drive::Drive;
+use crate::error::drive::DriveError;
+use crate::error::Error;
+use crate::fees::op::LowLevelDriveOperation;
+use crate::fees::op::LowLevelDriveOperation::CalculatedCostOperation;
+use crate::util::object_size_info::PathKeyElementInfo;
+use dpp::version::drive_versions::DriveVersion;
+use grovedb::{Element, GroveDb, TransactionArg};
+
+impl Drive {
+    /// Version 0 implementation of the "insert element if the path key does not yet exist" operation.
+    /// If the element already exists, it returns the existing element.
+    ///
+    /// This function checks whether an element exists at a specified path and key.
+    /// If the element exists, it returns the existing element. If not, it inserts the new element
+    /// into the database and returns `None`. This operation supports different types of path, key, and element configurations
+    /// and can be applied in either stateless or stateful contexts.
+    ///
+    /// # Parameters
+    ///
+    /// * `path_key_element_info`: Information about the path, key, and element to insert.
+    ///   - Supports multiple configurations: direct references, owned elements, fixed size keys, or estimated sizes.
+    /// * `apply_type`: The application type of the operation, defining whether the operation is stateless or stateful.
+    /// * `transaction`: The transaction context for the operation, allowing it to be atomic within a batch.
+    /// * `drive_operations`: A mutable reference to the list of drive operations to which this operation will be appended.
+    /// * `drive_version`: The version of the drive being used, ensuring compatibility with the function version.
+    ///
+    /// # Returns
+    ///
+    /// * `Ok(Some(Element))`: If the element already exists at the specified path and key, returning the existing element.
+    /// * `Ok(None)`: If the element was successfully inserted because it did not exist before.
+    /// * `Err(Error)`: Returns an error if:
+    ///   - The insertion operation is not supported in the current state.
+    ///   - The operation encounters any unexpected issues related to invalid configurations or unsupported features.
+    ///
+    /// # Errors
+    ///
+    /// * `Error::Drive(DriveError::NotSupportedPrivate)`: If the function encounters unsupported configurations, such as document sizes for stateful inserts.
+    /// * `Error::Drive(DriveError::UnknownVersionMismatch)`: If the drive version is not supported for the operation.
+    ///
+    /// # PathKeyElementInfo Variants
+    ///
+    /// The function supports various `PathKeyElementInfo` variants:
+    /// * `PathKeyRefElement`: Reference to the path, key, and element.
+    /// * `PathKeyElement`: Owned path, key, and element.
+    /// * `PathFixedSizeKeyRefElement`: Reference to the path with a fixed-size key and element.
+    /// * `PathKeyElementSize`: Path and key with known element size, used for estimation.
+    /// * `PathKeyUnknownElementSize`: Unsupported in this version and returns an error.
+    pub(super) fn batch_insert_if_not_exists_return_existing_element_v0<const N: usize>(
+        &self,
+        path_key_element_info: PathKeyElementInfo<N>,
+        apply_type: BatchInsertApplyType,
+        transaction: TransactionArg,
+        drive_operations: &mut Vec<LowLevelDriveOperation>,
+        drive_version: &DriveVersion,
+    ) -> Result<Option<Element>, Error> {
+        match path_key_element_info {
+            PathKeyRefElement((path, key, element)) => {
+                // Check if the element already exists
+                let existing_element = self.grove_get_raw_optional(
+                    path.as_slice().into(),
+                    key,
+                    apply_type.to_direct_query_type(),
+                    transaction,
+                    drive_operations,
+                    drive_version,
+                )?;
+                if let Some(existing_element) = existing_element {
+                    return Ok(Some(existing_element));
+                }
+
+                // Element does not exist, proceed with insertion
+                drive_operations.push(LowLevelDriveOperation::insert_for_known_path_key_element(
+                    path,
+                    key.to_vec(),
+                    element,
+                ));
+                Ok(None)
+            }
+            PathKeyElement((path, key, element)) => {
+                // Check if the element already exists
+                let existing_element = self.grove_get_raw_optional(
+                    path.as_slice().into(),
+                    key.as_slice(),
+                    apply_type.to_direct_query_type(),
+                    transaction,
+                    drive_operations,
+                    drive_version,
+                )?;
+                if let Some(existing_element) = existing_element {
+                    return Ok(Some(existing_element));
+                }
+
+                // Element does not exist, proceed with insertion
+                drive_operations.push(LowLevelDriveOperation::insert_for_known_path_key_element(
+                    path, key, element,
+                ));
+                Ok(None)
+            }
+            PathFixedSizeKeyRefElement((path, key, element)) => {
+                // Check if the element already exists
+                let existing_element = self.grove_get_raw_optional(
+                    path.as_slice().into(),
+                    key,
+                    apply_type.to_direct_query_type(),
+                    transaction,
+                    drive_operations,
+                    drive_version,
+                )?;
+                if let Some(existing_element) = existing_element {
+                    return Ok(Some(existing_element));
+                }
+
+                // Element does not exist, proceed with insertion
+                let path_items: Vec<Vec<u8>> = path.into_iter().map(Vec::from).collect();
+                drive_operations.push(LowLevelDriveOperation::insert_for_known_path_key_element(
+                    path_items,
+                    key.to_vec(),
+                    element,
+                ));
+                Ok(None)
+            }
+            PathKeyElementSize((key_info_path, key_info, element)) => {
+                match apply_type {
+                    BatchInsertApplyType::StatelessBatchInsert {
+                        in_tree_using_sums, ..
+                    } => {
+                        // Estimate if the element with the given size already exists
+                        drive_operations.push(CalculatedCostOperation(
+                            GroveDb::average_case_for_has_raw(
+                                &key_info_path,
+                                &key_info,
+                                element.serialized_size(&drive_version.grove_version)? as u32,
+                                in_tree_using_sums,
+                                &drive_version.grove_version,
+                            )?,
+                        ));
+                        drive_operations.push(
+                            LowLevelDriveOperation::insert_for_estimated_path_key_element(
+                                key_info_path,
+                                key_info,
+                                element,
+                            ),
+                        );
+                        Ok(None)
+                    }
+                    BatchInsertApplyType::StatefulBatchInsert => {
+                        Err(Error::Drive(DriveError::NotSupportedPrivate(
+                            "document sizes for stateful insert in batch operations not supported",
+                        )))
+                    }
+                }
+            }
+            PathKeyUnknownElementSize(_) => Err(Error::Drive(DriveError::NotSupportedPrivate(
+                "document sizes in batch operations not supported",
+            ))),
+        }
+    }
+}
diff --git a/packages/rs-drive/src/util/grove_operations/batch_insert_sum_item_or_add_to_if_already_exists/mod.rs b/packages/rs-drive/src/util/grove_operations/batch_insert_sum_item_or_add_to_if_already_exists/mod.rs
new file mode 100644
index 00000000000..87331adad07
--- /dev/null
+++ b/packages/rs-drive/src/util/grove_operations/batch_insert_sum_item_or_add_to_if_already_exists/mod.rs
@@ -0,0 +1,58 @@
+mod v0;
+
+use crate::util::grove_operations::BatchInsertApplyType;
+
+use crate::drive::Drive;
+use crate::error::drive::DriveError;
+use crate::error::Error;
+use crate::fees::op::LowLevelDriveOperation;
+use crate::util::object_size_info::PathKeyElementInfo;
+
+use dpp::version::drive_versions::DriveVersion;
+use grovedb::TransactionArg;
+
+impl Drive {
+    /// Pushes an "insert sum item or add to it if the item already exists" operation to `drive_operations`.
+    /// This operation either inserts a new sum item at the given path and key or adds the value to the existing sum item.
+    ///
+    /// # Parameters
+    /// * `path_key_element_info`: Information about the path, key, and element.
+    /// * `apply_type`: The apply type for the operation.
+    /// * `transaction`: The transaction argument for the operation.
+    /// * `drive_operations`: The list of drive operations to append to.
+    /// * `drive_version`: The drive version to select the correct function version to run.
+    ///
+    /// # Returns
+    /// * `Ok(())` if the operation was successful.
+    /// * `Err(DriveError::UnknownVersionMismatch)` if the drive version does not match known versions.
+    /// * `Err(DriveError::CorruptedCodeExecution)` (rare) if the operation is not supported.
+    /// * `Err(DriveError::CorruptedElementType)` (rare) if drive is in a corrupted state and
+    ///     gives back an incorrect element type.
+    pub fn batch_insert_sum_item_or_add_to_if_already_exists<const N: usize>(
+        &self,
+        path_key_element_info: PathKeyElementInfo<N>,
+        apply_type: BatchInsertApplyType,
+        transaction: TransactionArg,
+        drive_operations: &mut Vec<LowLevelDriveOperation>,
+        drive_version: &DriveVersion,
+    ) -> Result<(), Error> {
+        match drive_version
+            .grove_methods
+            .batch
+            .batch_insert_sum_item_or_add_to_if_already_exists
+        {
+            0 => self.batch_insert_sum_item_or_add_to_if_already_exists_v0(
+                path_key_element_info,
+                apply_type,
+                transaction,
+                drive_operations,
+                drive_version,
+            ),
+            version => Err(Error::Drive(DriveError::UnknownVersionMismatch {
+                method: "batch_insert_sum_item_or_add_to_if_already_exists".to_string(),
+                known_versions: vec![0],
+                received: version,
+            })),
+        }
+    }
+}
diff --git a/packages/rs-drive/src/util/grove_operations/batch_insert_sum_item_or_add_to_if_already_exists/v0/mod.rs b/packages/rs-drive/src/util/grove_operations/batch_insert_sum_item_or_add_to_if_already_exists/v0/mod.rs
new file mode 100644
index 00000000000..8294a6786b5
--- /dev/null
+++ b/packages/rs-drive/src/util/grove_operations/batch_insert_sum_item_or_add_to_if_already_exists/v0/mod.rs
@@ -0,0 +1,220 @@
+use crate::util::grove_operations::BatchInsertApplyType;
+use crate::util::object_size_info::PathKeyElementInfo::{
+    PathFixedSizeKeyRefElement, PathKeyElement, PathKeyElementSize, PathKeyRefElement,
+    PathKeyUnknownElementSize,
+};
+
+use crate::drive::Drive;
+use crate::error::drive::DriveError;
+use crate::error::Error;
+use crate::fees::op::LowLevelDriveOperation;
+use crate::fees::op::LowLevelDriveOperation::CalculatedCostOperation;
+use crate::util::object_size_info::PathKeyElementInfo;
+use dpp::version::drive_versions::DriveVersion;
+use dpp::ProtocolError;
+use grovedb::{Element, GroveDb, TransactionArg};
+
+impl Drive {
+    /// Version 0 implementation of the "insert sum item or add to it if the item already exists" operation.
+    /// This operation either inserts a new sum item at the given path and key or adds the value to the existing sum item.
+    ///
+    /// # Parameters
+    /// * `path_key_element_info`: Information about the path, key, and element.
+    /// * `apply_type`: The apply type for the operation.
+    /// * `transaction`: The transaction argument for the operation.
+    /// * `drive_operations`: The list of drive operations to append to.
+    /// * `drive_version`: The drive version to select the correct function version to run.
+    ///
+    /// # Returns
+    /// * `Ok(())` if the operation was successful.
+    /// * `Err(DriveError::CorruptedCodeExecution)` if the operation is not supported.
+    pub(crate) fn batch_insert_sum_item_or_add_to_if_already_exists_v0<const N: usize>(
+        &self,
+        path_key_element_info: PathKeyElementInfo<N>,
+        apply_type: BatchInsertApplyType,
+        transaction: TransactionArg,
+        drive_operations: &mut Vec<LowLevelDriveOperation>,
+        drive_version: &DriveVersion,
+    ) -> Result<(), Error> {
+        match path_key_element_info {
+            PathKeyRefElement((path, key, element)) => {
+                if let Element::SumItem(new_value, _) = element {
+                    // Check if the sum item already exists
+                    let existing_element = self.grove_get_raw_optional(
+                        path.as_slice().into(),
+                        key,
+                        apply_type.to_direct_query_type(),
+                        transaction,
+                        drive_operations,
+                        drive_version,
+                    )?;
+
+                    if let Some(Element::SumItem(existing_value, _)) = existing_element {
+                        // Add to the existing sum item
+                        let updated_value = existing_value
+                            .checked_add(new_value)
+                            .ok_or(ProtocolError::Overflow("overflow when adding to sum item"))?;
+                        drive_operations.push(
+                            LowLevelDriveOperation::insert_for_known_path_key_element(
+                                path,
+                                key.to_vec(),
+                                Element::new_sum_item(updated_value),
+                            ),
+                        );
+                    } else if existing_element.is_some() {
+                        return Err(Error::Drive(DriveError::CorruptedElementType(
+                            "expected sum item element type",
+                        )));
+                    } else {
+                        // Insert as a new sum item
+                        drive_operations.push(
+                            LowLevelDriveOperation::insert_for_known_path_key_element(
+                                path,
+                                key.to_vec(),
+                                Element::new_sum_item(new_value),
+                            ),
+                        );
+                    }
+                } else {
+                    return Err(Error::Drive(DriveError::CorruptedCodeExecution(
+                        "expected sum item element type",
+                    )));
+                }
+                Ok(())
+            }
+            PathKeyElement((path, key, element)) => {
+                if let Element::SumItem(new_value, _) = element {
+                    // Check if the sum item already exists
+                    let existing_element = self.grove_get_raw_optional(
+                        path.as_slice().into(),
+                        key.as_slice(),
+                        apply_type.to_direct_query_type(),
+                        transaction,
+                        drive_operations,
+                        drive_version,
+                    )?;
+
+                    if let Some(Element::SumItem(existing_value, _)) = existing_element {
+                        // Add to the existing sum item
+                        let updated_value = existing_value
+                            .checked_add(new_value)
+                            .ok_or(ProtocolError::Overflow("overflow when adding to sum item"))?;
+                        drive_operations.push(
+                            LowLevelDriveOperation::insert_for_known_path_key_element(
+                                path,
+                                key,
+                                Element::new_sum_item(updated_value),
+                            ),
+                        );
+                    } else if existing_element.is_some() {
+                        return Err(Error::Drive(DriveError::CorruptedElementType(
+                            "expected sum item element type",
+                        )));
+                    } else {
+                        // Insert as a new sum item
+                        drive_operations.push(
+                            LowLevelDriveOperation::insert_for_known_path_key_element(
+                                path,
+                                key,
+                                Element::new_sum_item(new_value),
+                            ),
+                        );
+                    }
+                } else {
+                    return Err(Error::Drive(DriveError::CorruptedCodeExecution(
+                        "expected sum item element type",
+                    )));
+                }
+                Ok(())
+            }
+            PathFixedSizeKeyRefElement((path, key, element)) => {
+                if let Element::SumItem(new_value, _) = element {
+                    // Check if the sum item already exists
+                    let existing_element = self.grove_get_raw_optional(
+                        path.as_slice().into(),
+                        key,
+                        apply_type.to_direct_query_type(),
+                        transaction,
+                        drive_operations,
+                        drive_version,
+                    )?;
+
+                    if let Some(Element::SumItem(existing_value, _)) = existing_element {
+                        // Add to the existing sum item
+                        let updated_value = existing_value
+                            .checked_add(new_value)
+                            .ok_or(ProtocolError::Overflow("overflow when adding to sum item"))?;
+                        let path_items: Vec<Vec<u8>> = path.into_iter().map(Vec::from).collect();
+                        drive_operations.push(
+                            LowLevelDriveOperation::insert_for_known_path_key_element(
+                                path_items,
+                                key.to_vec(),
+                                Element::new_sum_item(updated_value),
+                            ),
+                        );
+                    } else if existing_element.is_some() {
+                        return Err(Error::Drive(DriveError::CorruptedElementType(
+                            "expected sum item element type",
+                        )));
+                    } else {
+                        // Insert as a new sum item
+                        let path_items: Vec<Vec<u8>> = path.into_iter().map(Vec::from).collect();
+                        drive_operations.push(
+                            LowLevelDriveOperation::insert_for_known_path_key_element(
+                                path_items,
+                                key.to_vec(),
+                                Element::new_sum_item(new_value),
+                            ),
+                        );
+                    }
+                } else {
+                    return Err(Error::Drive(DriveError::CorruptedCodeExecution(
+                        "expected sum item element type",
+                    )));
+                }
+                Ok(())
+            }
+            PathKeyElementSize((key_info_path, key_info, element)) => {
+                if let Element::SumItem(new_value, _) = element {
+                    match apply_type {
+                        BatchInsertApplyType::StatelessBatchInsert {
+                            in_tree_using_sums, ..
+                        } => {
+                            // Estimate if the sum item with the given size already exists
+                            drive_operations.push(CalculatedCostOperation(
+                                GroveDb::average_case_for_has_raw(
+                                    &key_info_path,
+                                    &key_info,
+                                    element.serialized_size(&drive_version.grove_version)? as u32,
+                                    in_tree_using_sums,
+                                    &drive_version.grove_version,
+                                )?,
+                            ));
+
+                            drive_operations.push(
+                                LowLevelDriveOperation::insert_for_estimated_path_key_element(
+                                    key_info_path,
+                                    key_info,
+                                    Element::new_sum_item(new_value),
+                                ),
+                            );
+                            Ok(())
+                        }
+                        BatchInsertApplyType::StatefulBatchInsert => {
+                            Err(Error::Drive(DriveError::NotSupportedPrivate(
+                                "document sizes for stateful insert in batch operations not supported",
+                            )))
+                        }
+                    }
+                } else {
+                    Err(Error::Drive(DriveError::CorruptedCodeExecution(
+                        "expected sum item element type",
+                    )))
+                }
+            }
+            PathKeyUnknownElementSize(_) => Err(Error::Drive(DriveError::NotSupportedPrivate(
+                "document sizes in batch operations not supported",
+            ))),
+        }
+    }
+}
diff --git a/packages/rs-drive/src/util/grove_operations/grove_get_sum_tree_total_value/v0/mod.rs b/packages/rs-drive/src/util/grove_operations/grove_get_sum_tree_total_value/v0/mod.rs
index a2a27c7c641..65bd03f3e30 100644
--- a/packages/rs-drive/src/util/grove_operations/grove_get_sum_tree_total_value/v0/mod.rs
+++ b/packages/rs-drive/src/util/grove_operations/grove_get_sum_tree_total_value/v0/mod.rs
@@ -14,7 +14,7 @@ use platform_version::version::drive_versions::DriveVersion;
 impl Drive {
     /// Gets the element at the given path from groveDB.
     /// Pushes the `OperationCost` of getting the element to `drive_operations`.
-    pub(crate) fn grove_get_sum_tree_total_value_v0<B: AsRef<[u8]>>(
+    pub(super) fn grove_get_sum_tree_total_value_v0<B: AsRef<[u8]>>(
         &self,
         path: SubtreePath<'_, B>,
         key: &[u8],
diff --git a/packages/rs-drive/src/util/grove_operations/grove_insert_if_not_exists_return_existing_element/mod.rs b/packages/rs-drive/src/util/grove_operations/grove_insert_if_not_exists_return_existing_element/mod.rs
new file mode 100644
index 00000000000..8c5455a0380
--- /dev/null
+++ b/packages/rs-drive/src/util/grove_operations/grove_insert_if_not_exists_return_existing_element/mod.rs
@@ -0,0 +1,54 @@
+//todo: uncomment once version 2.1 of GroveDB is released.
+// mod v0;
+//
+// use crate::drive::Drive;
+// use crate::error::drive::DriveError;
+// use crate::error::Error;
+// use crate::fees::op::LowLevelDriveOperation;
+// use dpp::version::drive_versions::DriveVersion;
+// use grovedb::{Element, TransactionArg};
+// use grovedb_path::SubtreePath;
+//
+// impl Drive {
+//     /// Inserts an element into groveDB only if the specified path and key do not exist.
+//     /// This operation costs are then stored in `drive_operations`.
+//     ///
+//     /// # Parameters
+//     /// * `path`: The groveDB hierarchical authenticated structure path where the new element is to be inserted.
+//     /// * `key`: The key where the new element should be inserted in the subtree.
+//     /// * `element`: The element to be inserted.
+//     /// * `transaction`: The groveDB transaction associated with this operation.
+//     /// * `drive_operations`: A vector to collect the costs of operations for later computation. In this case,
+//     /// it collects the cost of this insert operation if the path and key did not exist.
+//     /// * `platform_version`: The platform version to select the correct function version to run.
+//     ///
+//     /// # Returns
+//     /// * `Ok(true)` if the insertion was successful.
+//     /// * `Ok(false)` if the path and key already existed.
+//     /// * `Err(DriveError::UnknownVersionMismatch)` if the platform version does not match known versions.
+//     pub fn grove_insert_if_not_exists_return_existing_element<B: AsRef<[u8]>>(
+//         &self,
+//         path: SubtreePath<'_, B>,
+//         key: &[u8],
+//         element: Element,
+//         transaction: TransactionArg,
+//         drive_operations: Option<&mut Vec<LowLevelDriveOperation>>,
+//         drive_version: &DriveVersion,
+//     ) -> Result<Option<Element>, Error> {
+//         match drive_version.grove_methods.basic.grove_insert_if_not_exists {
+//             0 => self.grove_insert_if_not_exists_return_existing_element_v0(
+//                 path,
+//                 key,
+//                 element,
+//                 transaction,
+//                 drive_operations,
+//                 drive_version,
+//             ),
+//             version => Err(Error::Drive(DriveError::UnknownVersionMismatch {
+//                 method: "grove_insert_if_not_exists_return_existing_element".to_string(),
+//                 known_versions: vec![0],
+//                 received: version,
+//             })),
+//         }
+//     }
+// }
diff --git a/packages/rs-drive/src/util/grove_operations/grove_insert_if_not_exists_return_existing_element/v0/mod.rs b/packages/rs-drive/src/util/grove_operations/grove_insert_if_not_exists_return_existing_element/v0/mod.rs
new file mode 100644
index 00000000000..5030cd6cc54
--- /dev/null
+++ b/packages/rs-drive/src/util/grove_operations/grove_insert_if_not_exists_return_existing_element/v0/mod.rs
@@ -0,0 +1,30 @@
+// use crate::drive::Drive;
+// use crate::error::Error;
+// use crate::fees::op::LowLevelDriveOperation;
+// use crate::util::grove_operations::push_drive_operation_result_optional;
+// use grovedb::{Element, TransactionArg};
+// use grovedb_path::SubtreePath;
+// use platform_version::version::drive_versions::DriveVersion;
+// 
+// impl Drive {
+//     /// Pushes the `OperationCost` of inserting an element in groveDB where the path key does not yet exist
+//     /// to `drive_operations`.
+//     pub(crate) fn grove_insert_if_not_exists_return_existing_element_v0<B: AsRef<[u8]>>(
+//         &self,
+//         path: SubtreePath<'_, B>,
+//         key: &[u8],
+//         element: Element,
+//         transaction: TransactionArg,
+//         drive_operations: Option<&mut Vec<LowLevelDriveOperation>>,
+//         drive_version: &DriveVersion,
+//     ) -> Result<bool, Error> {
+//         let cost_context = self.grove.insert_if_not_exists_return_existing_element(
+//             path,
+//             key,
+//             element,
+//             transaction,
+//             &drive_version.grove_version,
+//         );
+//         push_drive_operation_result_optional(cost_context, drive_operations)
+//     }
+// }
diff --git a/packages/rs-drive/src/util/grove_operations/mod.rs b/packages/rs-drive/src/util/grove_operations/mod.rs
index a328aa8efb1..9861e146481 100644
--- a/packages/rs-drive/src/util/grove_operations/mod.rs
+++ b/packages/rs-drive/src/util/grove_operations/mod.rs
@@ -108,10 +108,24 @@ pub mod grove_batch_operations_costs;
 /// Clear a subtree in grovedb
 pub mod grove_clear;
 
+/// Provides functionality to delete items in a path based on a query.
+pub mod batch_delete_items_in_path_query;
+
+/// Inserts an element if it does not exist and returns the existing element if it does.
+pub mod batch_insert_if_not_exists_return_existing_element;
+
+/// Inserts a sum item or adds to it if it already exists.
+pub mod batch_insert_sum_item_or_add_to_if_already_exists;
+
+/// Retrieves serialized or sum results from a path query in GroveDB.
 mod grove_get_path_query_serialized_or_sum_results;
-/// Proved path query in grovedb with a conditional query
+
+/// Executes a proved path query in GroveDB with an optional conditional query.
 pub mod grove_get_proved_path_query_with_conditional;
 
+/// Inserts an element if it does not exist and returns the existing element if it does in GroveDB.
+pub mod grove_insert_if_not_exists_return_existing_element;
+
 use grovedb_costs::CostContext;
 
 use grovedb::EstimatedLayerInformation;
diff --git a/packages/rs-json-schema-compatibility-validator/Cargo.toml b/packages/rs-json-schema-compatibility-validator/Cargo.toml
index d31332bb208..bc90fffebef 100644
--- a/packages/rs-json-schema-compatibility-validator/Cargo.toml
+++ b/packages/rs-json-schema-compatibility-validator/Cargo.toml
@@ -8,7 +8,7 @@ authors = ["Ivan Shumkov <ivan@shumkov.ru>"]
 [dependencies]
 json-patch = "1.2.0"
 serde_json = "1.0.115"
-thiserror = "1.0.58"
+thiserror = "1.0.64"
 once_cell = "1.19.0"
 
 [dev-dependencies]
diff --git a/packages/rs-platform-value/Cargo.toml b/packages/rs-platform-value/Cargo.toml
index 604a4dae4b9..f60a9a15a0f 100644
--- a/packages/rs-platform-value/Cargo.toml
+++ b/packages/rs-platform-value/Cargo.toml
@@ -10,7 +10,7 @@ license = "MIT"
 [dependencies]
 bincode = { version = "2.0.0-rc.3", features = ["serde"] }
 ciborium = { git = "https://github.com/qrayven/ciborium", branch = "feat-ser-null-as-undefined", optional = true }
-thiserror = "1.0.58"
+thiserror = "1.0.64"
 bs58 = "0.5.1"
 base64 = "0.22.1"
 hex = "0.4.3"
diff --git a/packages/rs-platform-version/src/version/dpp_versions.rs b/packages/rs-platform-version/src/version/dpp_versions.rs
index b42befd472b..7c84e660a49 100644
--- a/packages/rs-platform-version/src/version/dpp_versions.rs
+++ b/packages/rs-platform-version/src/version/dpp_versions.rs
@@ -271,4 +271,5 @@ pub struct DocumentMethodVersions {
 #[derive(Clone, Debug, Default)]
 pub struct DPPMethodVersions {
     pub epoch_core_reward_credits_for_distribution: FeatureVersion,
+    pub daily_withdrawal_limit: FeatureVersion,
 }
diff --git a/packages/rs-platform-version/src/version/drive_abci_versions.rs b/packages/rs-platform-version/src/version/drive_abci_versions.rs
index 3d60df57366..22905e5ce53 100644
--- a/packages/rs-platform-version/src/version/drive_abci_versions.rs
+++ b/packages/rs-platform-version/src/version/drive_abci_versions.rs
@@ -5,6 +5,7 @@ pub struct DriveAbciVersion {
     pub structs: DriveAbciStructureVersions,
     pub methods: DriveAbciMethodVersions,
     pub validation_and_processing: DriveAbciValidationVersions,
+    pub withdrawal_constants: DriveAbciWithdrawalConstants,
     pub query: DriveAbciQueryVersions,
 }
 
@@ -112,6 +113,12 @@ pub struct DriveAbciValidationVersions {
     pub event_constants: DriveAbciValidationConstants,
 }
 
+#[derive(Clone, Debug, Default)]
+pub struct DriveAbciWithdrawalConstants {
+    pub core_expiration_blocks: u32,
+    pub cleanup_expired_locks_of_withdrawal_amounts_limit: u16,
+}
+
 #[derive(Clone, Debug, Default)]
 pub struct DriveAbciValidationConstants {
     pub maximum_vote_polls_to_process: u16,
@@ -332,12 +339,14 @@ pub struct DriveAbciIdentityCreditWithdrawalMethodVersions {
     pub pool_withdrawals_into_transactions_queue: FeatureVersion,
     pub update_broadcasted_withdrawal_statuses: FeatureVersion,
     pub append_signatures_and_broadcast_withdrawal_transactions: FeatureVersion,
+    pub cleanup_expired_locks_of_withdrawal_amounts: FeatureVersion,
 }
 
 #[derive(Clone, Debug, Default)]
 pub struct DriveAbciProtocolUpgradeMethodVersions {
     pub check_for_desired_protocol_upgrade: FeatureVersion,
     pub upgrade_protocol_version_on_epoch_change: FeatureVersion,
+    pub perform_events_on_first_block_of_protocol_change: OptionalFeatureVersion,
     pub protocol_version_upgrade_percentage_needed: u64,
 }
 
diff --git a/packages/rs-platform-version/src/version/drive_versions.rs b/packages/rs-platform-version/src/version/drive_versions.rs
index 5e67347b443..c4bb57124c5 100644
--- a/packages/rs-platform-version/src/version/drive_versions.rs
+++ b/packages/rs-platform-version/src/version/drive_versions.rs
@@ -431,6 +431,7 @@ pub struct DriveGroveBasicMethodVersions {
     pub grove_insert_empty_tree: FeatureVersion,
     pub grove_insert_empty_sum_tree: FeatureVersion,
     pub grove_insert_if_not_exists: FeatureVersion,
+    pub grove_insert_if_not_exists_return_existing_element: FeatureVersion,
     pub grove_clear: FeatureVersion,
     pub grove_delete: FeatureVersion,
     pub grove_get_raw: FeatureVersion,
@@ -454,11 +455,13 @@ pub struct DriveGroveBatchMethodVersions {
     pub batch_insert_empty_tree: FeatureVersion,
     pub batch_insert_empty_tree_if_not_exists: FeatureVersion,
     pub batch_insert_empty_tree_if_not_exists_check_existing_operations: FeatureVersion,
+    pub batch_insert_sum_item_or_add_to_if_already_exists: FeatureVersion,
     pub batch_insert: FeatureVersion,
     pub batch_insert_if_not_exists: FeatureVersion,
     pub batch_insert_if_changed_value: FeatureVersion,
     pub batch_replace: FeatureVersion,
     pub batch_delete: FeatureVersion,
+    pub batch_delete_items_in_path_query: FeatureVersion,
     pub batch_remove_raw: FeatureVersion,
     pub batch_delete_up_tree_while_empty: FeatureVersion,
     pub batch_refresh_reference: FeatureVersion,
@@ -562,12 +565,13 @@ pub struct DriveIdentityMethodVersions {
 pub struct DriveIdentityWithdrawalMethodVersions {
     pub document: DriveIdentityWithdrawalDocumentMethodVersions,
     pub transaction: DriveIdentityWithdrawalTransactionMethodVersions,
+    pub calculate_current_withdrawal_limit: FeatureVersion,
 }
 
 #[derive(Clone, Debug, Default)]
 pub struct DriveIdentityWithdrawalDocumentMethodVersions {
     pub fetch_oldest_withdrawal_documents_by_status: FeatureVersion,
-    pub find_up_to_100_withdrawal_documents_by_status_and_transaction_indices: FeatureVersion,
+    pub find_withdrawal_documents_by_status_and_transaction_indices: FeatureVersion,
 }
 
 #[derive(Clone, Debug, Default)]
diff --git a/packages/rs-platform-version/src/version/limits.rs b/packages/rs-platform-version/src/version/limits.rs
index 207cde0d1ed..ef155395652 100644
--- a/packages/rs-platform-version/src/version/limits.rs
+++ b/packages/rs-platform-version/src/version/limits.rs
@@ -5,4 +5,5 @@ pub struct SystemLimits {
     pub max_state_transition_size: u64,
     pub max_transitions_in_documents_batch: u16,
     pub withdrawal_transactions_per_block_limit: u16,
+    pub max_withdrawal_amount: u64,
 }
diff --git a/packages/rs-platform-version/src/version/mocks/v2_test.rs b/packages/rs-platform-version/src/version/mocks/v2_test.rs
index bb83ecba960..813ac270dfd 100644
--- a/packages/rs-platform-version/src/version/mocks/v2_test.rs
+++ b/packages/rs-platform-version/src/version/mocks/v2_test.rs
@@ -32,7 +32,7 @@ use crate::version::drive_abci_versions::{
     DriveAbciStateTransitionValidationVersions, DriveAbciStructureVersions,
     DriveAbciValidationConstants, DriveAbciValidationDataTriggerAndBindingVersions,
     DriveAbciValidationDataTriggerVersions, DriveAbciValidationVersions, DriveAbciVersion,
-    DriveAbciVotingMethodVersions, PenaltyAmounts,
+    DriveAbciVotingMethodVersions, DriveAbciWithdrawalConstants, PenaltyAmounts,
 };
 use crate::version::drive_versions::{
     DriveAssetLockMethodVersions, DriveBalancesMethodVersions, DriveBatchOperationsMethodVersion,
@@ -465,7 +465,7 @@ pub const TEST_PLATFORM_V2: PlatformVersion = PlatformVersion {
                 withdrawals: DriveIdentityWithdrawalMethodVersions {
                     document: DriveIdentityWithdrawalDocumentMethodVersions {
                         fetch_oldest_withdrawal_documents_by_status: 0,
-                        find_up_to_100_withdrawal_documents_by_status_and_transaction_indices: 0,
+                        find_withdrawal_documents_by_status_and_transaction_indices: 0,
                     },
                     transaction: DriveIdentityWithdrawalTransactionMethodVersions {
                         index: DriveIdentityWithdrawalTransactionIndexMethodVersions {
@@ -477,6 +477,7 @@ pub const TEST_PLATFORM_V2: PlatformVersion = PlatformVersion {
                             dequeue_untied_withdrawal_transactions: 0,
                         },
                     },
+                    calculate_current_withdrawal_limit: 0,
                 },
             },
             platform_system: DrivePlatformSystemMethodVersions {
@@ -547,6 +548,7 @@ pub const TEST_PLATFORM_V2: PlatformVersion = PlatformVersion {
                 grove_insert_empty_tree: 0,
                 grove_insert_empty_sum_tree: 0,
                 grove_insert_if_not_exists: 0,
+                grove_insert_if_not_exists_return_existing_element: 0,
                 grove_clear: 0,
                 grove_delete: 0,
                 grove_get_raw: 0,
@@ -568,11 +570,13 @@ pub const TEST_PLATFORM_V2: PlatformVersion = PlatformVersion {
                 batch_insert_empty_tree: 0,
                 batch_insert_empty_tree_if_not_exists: 0,
                 batch_insert_empty_tree_if_not_exists_check_existing_operations: 0,
+                batch_insert_sum_item_or_add_to_if_already_exists: 0,
                 batch_insert: 0,
                 batch_insert_if_not_exists: 0,
                 batch_insert_if_changed_value: 0,
                 batch_replace: 0,
                 batch_delete: 0,
+                batch_delete_items_in_path_query: 0,
                 batch_remove_raw: 0,
                 batch_delete_up_tree_while_empty: 0,
                 batch_refresh_reference: 0,
@@ -636,6 +640,7 @@ pub const TEST_PLATFORM_V2: PlatformVersion = PlatformVersion {
             protocol_upgrade: DriveAbciProtocolUpgradeMethodVersions {
                 check_for_desired_protocol_upgrade: 0,
                 upgrade_protocol_version_on_epoch_change: 0,
+                perform_events_on_first_block_of_protocol_change: None,
                 protocol_version_upgrade_percentage_needed: 75,
             },
             block_fee_processing: DriveAbciBlockFeeProcessingMethodVersions {
@@ -670,6 +675,7 @@ pub const TEST_PLATFORM_V2: PlatformVersion = PlatformVersion {
                 pool_withdrawals_into_transactions_queue: 0,
                 update_broadcasted_withdrawal_statuses: 0,
                 append_signatures_and_broadcast_withdrawal_transactions: 0,
+                cleanup_expired_locks_of_withdrawal_amounts: 0,
             },
             voting: DriveAbciVotingMethodVersions {
                 keep_record_of_finished_contested_resource_vote_poll: 0,
@@ -843,6 +849,10 @@ pub const TEST_PLATFORM_V2: PlatformVersion = PlatformVersion {
                 maximum_contenders_to_consider: 100,
             },
         },
+        withdrawal_constants: DriveAbciWithdrawalConstants {
+            core_expiration_blocks: 24,
+            cleanup_expired_locks_of_withdrawal_amounts_limit: 0,
+        },
         query: DriveAbciQueryVersions {
             max_returned_elements: 100,
             response_metadata: 0,
@@ -1264,7 +1274,7 @@ pub const TEST_PLATFORM_V2: PlatformVersion = PlatformVersion {
                 default_current_version: 0,
             },
         },
-        methods: DPPMethodVersions { epoch_core_reward_credits_for_distribution: 0 },
+        methods: DPPMethodVersions { epoch_core_reward_credits_for_distribution: 0, daily_withdrawal_limit: 0 },
     },
     system_data_contracts: SystemDataContractVersions {
         withdrawals: 1,
@@ -1280,6 +1290,7 @@ pub const TEST_PLATFORM_V2: PlatformVersion = PlatformVersion {
         max_state_transition_size: 20000,
         max_transitions_in_documents_batch: 1,
         withdrawal_transactions_per_block_limit: 4,
+        max_withdrawal_amount: 50_000_000_000_000,
     },
     consensus: ConsensusVersions {
         tenderdash_consensus_version: 0,
diff --git a/packages/rs-platform-version/src/version/mocks/v3_test.rs b/packages/rs-platform-version/src/version/mocks/v3_test.rs
index d1366771292..a81949c4909 100644
--- a/packages/rs-platform-version/src/version/mocks/v3_test.rs
+++ b/packages/rs-platform-version/src/version/mocks/v3_test.rs
@@ -32,7 +32,7 @@ use crate::version::drive_abci_versions::{
     DriveAbciStateTransitionValidationVersions, DriveAbciStructureVersions,
     DriveAbciValidationConstants, DriveAbciValidationDataTriggerAndBindingVersions,
     DriveAbciValidationDataTriggerVersions, DriveAbciValidationVersions, DriveAbciVersion,
-    DriveAbciVotingMethodVersions, PenaltyAmounts,
+    DriveAbciVotingMethodVersions, DriveAbciWithdrawalConstants, PenaltyAmounts,
 };
 use crate::version::drive_versions::{
     DriveAssetLockMethodVersions, DriveBalancesMethodVersions, DriveBatchOperationsMethodVersion,
@@ -465,7 +465,7 @@ pub const TEST_PLATFORM_V3: PlatformVersion = PlatformVersion {
                 withdrawals: DriveIdentityWithdrawalMethodVersions {
                     document: DriveIdentityWithdrawalDocumentMethodVersions {
                         fetch_oldest_withdrawal_documents_by_status: 0,
-                        find_up_to_100_withdrawal_documents_by_status_and_transaction_indices: 0,
+                        find_withdrawal_documents_by_status_and_transaction_indices: 0,
                     },
                     transaction: DriveIdentityWithdrawalTransactionMethodVersions {
                         index: DriveIdentityWithdrawalTransactionIndexMethodVersions {
@@ -477,6 +477,7 @@ pub const TEST_PLATFORM_V3: PlatformVersion = PlatformVersion {
                             dequeue_untied_withdrawal_transactions: 0,
                         },
                     },
+                    calculate_current_withdrawal_limit: 0,
                 },
             },
             platform_system: DrivePlatformSystemMethodVersions {
@@ -547,6 +548,7 @@ pub const TEST_PLATFORM_V3: PlatformVersion = PlatformVersion {
                 grove_insert_empty_tree: 0,
                 grove_insert_empty_sum_tree: 0,
                 grove_insert_if_not_exists: 0,
+                grove_insert_if_not_exists_return_existing_element: 0,
                 grove_clear: 0,
                 grove_delete: 0,
                 grove_get_raw: 0,
@@ -568,11 +570,13 @@ pub const TEST_PLATFORM_V3: PlatformVersion = PlatformVersion {
                 batch_insert_empty_tree: 0,
                 batch_insert_empty_tree_if_not_exists: 0,
                 batch_insert_empty_tree_if_not_exists_check_existing_operations: 0,
+                batch_insert_sum_item_or_add_to_if_already_exists: 0,
                 batch_insert: 0,
                 batch_insert_if_not_exists: 0,
                 batch_insert_if_changed_value: 0,
                 batch_replace: 0,
                 batch_delete: 0,
+                batch_delete_items_in_path_query: 0,
                 batch_remove_raw: 0,
                 batch_delete_up_tree_while_empty: 0,
                 batch_refresh_reference: 0,
@@ -636,6 +640,7 @@ pub const TEST_PLATFORM_V3: PlatformVersion = PlatformVersion {
             protocol_upgrade: DriveAbciProtocolUpgradeMethodVersions {
                 check_for_desired_protocol_upgrade: 1,
                 upgrade_protocol_version_on_epoch_change: 0,
+                perform_events_on_first_block_of_protocol_change: None,
                 protocol_version_upgrade_percentage_needed: 67,
             },
             block_fee_processing: DriveAbciBlockFeeProcessingMethodVersions {
@@ -670,6 +675,7 @@ pub const TEST_PLATFORM_V3: PlatformVersion = PlatformVersion {
                 pool_withdrawals_into_transactions_queue: 1,
                 update_broadcasted_withdrawal_statuses: 0,
                 append_signatures_and_broadcast_withdrawal_transactions: 0,
+                cleanup_expired_locks_of_withdrawal_amounts: 0,
             },
             voting: DriveAbciVotingMethodVersions {
                 keep_record_of_finished_contested_resource_vote_poll: 0,
@@ -843,6 +849,10 @@ pub const TEST_PLATFORM_V3: PlatformVersion = PlatformVersion {
                 maximum_contenders_to_consider: 100,
             },
         },
+        withdrawal_constants: DriveAbciWithdrawalConstants {
+            core_expiration_blocks: 24,
+            cleanup_expired_locks_of_withdrawal_amounts_limit: 64,
+        },
         query: DriveAbciQueryVersions {
             max_returned_elements: 100,
             response_metadata: 0,
@@ -1266,6 +1276,7 @@ pub const TEST_PLATFORM_V3: PlatformVersion = PlatformVersion {
         },
         methods: DPPMethodVersions {
             epoch_core_reward_credits_for_distribution: 0,
+            daily_withdrawal_limit: 0,
         },
     },
     system_data_contracts: SystemDataContractVersions {
@@ -1282,6 +1293,7 @@ pub const TEST_PLATFORM_V3: PlatformVersion = PlatformVersion {
         max_state_transition_size: 20480, //20 KiB
         max_transitions_in_documents_batch: 1,
         withdrawal_transactions_per_block_limit: 4,
+        max_withdrawal_amount: 50_000_000_000_000,
     },
     consensus: ConsensusVersions {
         tenderdash_consensus_version: 1,
diff --git a/packages/rs-platform-version/src/version/v1.rs b/packages/rs-platform-version/src/version/v1.rs
index b9d9eda4648..9ea4fad720c 100644
--- a/packages/rs-platform-version/src/version/v1.rs
+++ b/packages/rs-platform-version/src/version/v1.rs
@@ -32,7 +32,7 @@ use crate::version::drive_abci_versions::{
     DriveAbciStateTransitionValidationVersions, DriveAbciStructureVersions,
     DriveAbciValidationConstants, DriveAbciValidationDataTriggerAndBindingVersions,
     DriveAbciValidationDataTriggerVersions, DriveAbciValidationVersions, DriveAbciVersion,
-    DriveAbciVotingMethodVersions, PenaltyAmounts,
+    DriveAbciVotingMethodVersions, DriveAbciWithdrawalConstants, PenaltyAmounts,
 };
 use crate::version::drive_versions::{
     DriveAssetLockMethodVersions, DriveBalancesMethodVersions, DriveBatchOperationsMethodVersion,
@@ -464,7 +464,7 @@ pub const PLATFORM_V1: PlatformVersion = PlatformVersion {
                 withdrawals: DriveIdentityWithdrawalMethodVersions {
                     document: DriveIdentityWithdrawalDocumentMethodVersions {
                         fetch_oldest_withdrawal_documents_by_status: 0,
-                        find_up_to_100_withdrawal_documents_by_status_and_transaction_indices: 0,
+                        find_withdrawal_documents_by_status_and_transaction_indices: 0,
                     },
                     transaction: DriveIdentityWithdrawalTransactionMethodVersions {
                         index: DriveIdentityWithdrawalTransactionIndexMethodVersions {
@@ -476,6 +476,7 @@ pub const PLATFORM_V1: PlatformVersion = PlatformVersion {
                             dequeue_untied_withdrawal_transactions: 0,
                         },
                     },
+                    calculate_current_withdrawal_limit: 0,
                 },
             },
             platform_system: DrivePlatformSystemMethodVersions {
@@ -546,6 +547,7 @@ pub const PLATFORM_V1: PlatformVersion = PlatformVersion {
                 grove_insert_empty_tree: 0,
                 grove_insert_empty_sum_tree: 0,
                 grove_insert_if_not_exists: 0,
+                grove_insert_if_not_exists_return_existing_element: 0,
                 grove_clear: 0,
                 grove_delete: 0,
                 grove_get_raw: 0,
@@ -567,11 +569,13 @@ pub const PLATFORM_V1: PlatformVersion = PlatformVersion {
                 batch_insert_empty_tree: 0,
                 batch_insert_empty_tree_if_not_exists: 0,
                 batch_insert_empty_tree_if_not_exists_check_existing_operations: 0,
+                batch_insert_sum_item_or_add_to_if_already_exists: 0,
                 batch_insert: 0,
                 batch_insert_if_not_exists: 0,
                 batch_insert_if_changed_value: 0,
                 batch_replace: 0,
                 batch_delete: 0,
+                batch_delete_items_in_path_query: 0,
                 batch_remove_raw: 0,
                 batch_delete_up_tree_while_empty: 0,
                 batch_refresh_reference: 0,
@@ -635,6 +639,7 @@ pub const PLATFORM_V1: PlatformVersion = PlatformVersion {
             protocol_upgrade: DriveAbciProtocolUpgradeMethodVersions {
                 check_for_desired_protocol_upgrade: 0,
                 upgrade_protocol_version_on_epoch_change: 0,
+                perform_events_on_first_block_of_protocol_change: None,
                 protocol_version_upgrade_percentage_needed: 75,
             },
             block_fee_processing: DriveAbciBlockFeeProcessingMethodVersions {
@@ -669,6 +674,7 @@ pub const PLATFORM_V1: PlatformVersion = PlatformVersion {
                 pool_withdrawals_into_transactions_queue: 0,
                 update_broadcasted_withdrawal_statuses: 0,
                 append_signatures_and_broadcast_withdrawal_transactions: 0,
+                cleanup_expired_locks_of_withdrawal_amounts: 0,
             },
             voting: DriveAbciVotingMethodVersions {
                 keep_record_of_finished_contested_resource_vote_poll: 0,
@@ -842,6 +848,10 @@ pub const PLATFORM_V1: PlatformVersion = PlatformVersion {
                 maximum_contenders_to_consider: 100,
             },
         },
+        withdrawal_constants: DriveAbciWithdrawalConstants {
+            core_expiration_blocks: 24,
+            cleanup_expired_locks_of_withdrawal_amounts_limit: 0,
+        },
         query: DriveAbciQueryVersions {
             max_returned_elements: 100,
             response_metadata: 0,
@@ -1265,6 +1275,7 @@ pub const PLATFORM_V1: PlatformVersion = PlatformVersion {
         },
         methods: DPPMethodVersions {
             epoch_core_reward_credits_for_distribution: 0,
+            daily_withdrawal_limit: 0,
         },
     },
     system_data_contracts: SystemDataContractVersions {
@@ -1281,6 +1292,7 @@ pub const PLATFORM_V1: PlatformVersion = PlatformVersion {
         max_state_transition_size: 20480, //20 KiB
         max_transitions_in_documents_batch: 1,
         withdrawal_transactions_per_block_limit: 4,
+        max_withdrawal_amount: 50_000_000_000_000,
     },
     consensus: ConsensusVersions {
         tenderdash_consensus_version: 0,
diff --git a/packages/rs-platform-version/src/version/v2.rs b/packages/rs-platform-version/src/version/v2.rs
index 2f80cedf64a..dc7b10212dc 100644
--- a/packages/rs-platform-version/src/version/v2.rs
+++ b/packages/rs-platform-version/src/version/v2.rs
@@ -32,7 +32,7 @@ use crate::version::drive_abci_versions::{
     DriveAbciStateTransitionValidationVersions, DriveAbciStructureVersions,
     DriveAbciValidationConstants, DriveAbciValidationDataTriggerAndBindingVersions,
     DriveAbciValidationDataTriggerVersions, DriveAbciValidationVersions, DriveAbciVersion,
-    DriveAbciVotingMethodVersions, PenaltyAmounts,
+    DriveAbciVotingMethodVersions, DriveAbciWithdrawalConstants, PenaltyAmounts,
 };
 use crate::version::drive_versions::{
     DriveAssetLockMethodVersions, DriveBalancesMethodVersions, DriveBatchOperationsMethodVersion,
@@ -464,7 +464,7 @@ pub const PLATFORM_V2: PlatformVersion = PlatformVersion {
                 withdrawals: DriveIdentityWithdrawalMethodVersions {
                     document: DriveIdentityWithdrawalDocumentMethodVersions {
                         fetch_oldest_withdrawal_documents_by_status: 0,
-                        find_up_to_100_withdrawal_documents_by_status_and_transaction_indices: 0,
+                        find_withdrawal_documents_by_status_and_transaction_indices: 0,
                     },
                     transaction: DriveIdentityWithdrawalTransactionMethodVersions {
                         index: DriveIdentityWithdrawalTransactionIndexMethodVersions {
@@ -476,6 +476,7 @@ pub const PLATFORM_V2: PlatformVersion = PlatformVersion {
                             dequeue_untied_withdrawal_transactions: 0,
                         },
                     },
+                    calculate_current_withdrawal_limit: 0,
                 },
             },
             platform_system: DrivePlatformSystemMethodVersions {
@@ -546,6 +547,7 @@ pub const PLATFORM_V2: PlatformVersion = PlatformVersion {
                 grove_insert_empty_tree: 0,
                 grove_insert_empty_sum_tree: 0,
                 grove_insert_if_not_exists: 0,
+                grove_insert_if_not_exists_return_existing_element: 0,
                 grove_clear: 0,
                 grove_delete: 0,
                 grove_get_raw: 0,
@@ -567,11 +569,13 @@ pub const PLATFORM_V2: PlatformVersion = PlatformVersion {
                 batch_insert_empty_tree: 0,
                 batch_insert_empty_tree_if_not_exists: 0,
                 batch_insert_empty_tree_if_not_exists_check_existing_operations: 0,
+                batch_insert_sum_item_or_add_to_if_already_exists: 0,
                 batch_insert: 0,
                 batch_insert_if_not_exists: 0,
                 batch_insert_if_changed_value: 0,
                 batch_replace: 0,
                 batch_delete: 0,
+                batch_delete_items_in_path_query: 0,
                 batch_remove_raw: 0,
                 batch_delete_up_tree_while_empty: 0,
                 batch_refresh_reference: 0,
@@ -635,6 +639,7 @@ pub const PLATFORM_V2: PlatformVersion = PlatformVersion {
             protocol_upgrade: DriveAbciProtocolUpgradeMethodVersions {
                 check_for_desired_protocol_upgrade: 0,
                 upgrade_protocol_version_on_epoch_change: 0,
+                perform_events_on_first_block_of_protocol_change: None,
                 protocol_version_upgrade_percentage_needed: 75,
             },
             block_fee_processing: DriveAbciBlockFeeProcessingMethodVersions {
@@ -669,6 +674,7 @@ pub const PLATFORM_V2: PlatformVersion = PlatformVersion {
                 pool_withdrawals_into_transactions_queue: 0,
                 update_broadcasted_withdrawal_statuses: 0,
                 append_signatures_and_broadcast_withdrawal_transactions: 0,
+                cleanup_expired_locks_of_withdrawal_amounts: 0,
             },
             voting: DriveAbciVotingMethodVersions {
                 keep_record_of_finished_contested_resource_vote_poll: 0,
@@ -842,6 +848,10 @@ pub const PLATFORM_V2: PlatformVersion = PlatformVersion {
                 maximum_contenders_to_consider: 100,
             },
         },
+        withdrawal_constants: DriveAbciWithdrawalConstants {
+            core_expiration_blocks: 24,
+            cleanup_expired_locks_of_withdrawal_amounts_limit: 0,
+        },
         query: DriveAbciQueryVersions {
             max_returned_elements: 100,
             response_metadata: 0,
@@ -1265,6 +1275,7 @@ pub const PLATFORM_V2: PlatformVersion = PlatformVersion {
         },
         methods: DPPMethodVersions {
             epoch_core_reward_credits_for_distribution: 0,
+            daily_withdrawal_limit: 0,
         },
     },
     system_data_contracts: SystemDataContractVersions {
@@ -1281,6 +1292,7 @@ pub const PLATFORM_V2: PlatformVersion = PlatformVersion {
         max_state_transition_size: 20480, //20 KiB
         max_transitions_in_documents_batch: 1,
         withdrawal_transactions_per_block_limit: 4,
+        max_withdrawal_amount: 50_000_000_000_000,
     },
     consensus: ConsensusVersions {
         tenderdash_consensus_version: 0,
diff --git a/packages/rs-platform-version/src/version/v3.rs b/packages/rs-platform-version/src/version/v3.rs
index 314e17aa191..ba527c98f62 100644
--- a/packages/rs-platform-version/src/version/v3.rs
+++ b/packages/rs-platform-version/src/version/v3.rs
@@ -32,7 +32,7 @@ use crate::version::drive_abci_versions::{
     DriveAbciStateTransitionValidationVersions, DriveAbciStructureVersions,
     DriveAbciValidationConstants, DriveAbciValidationDataTriggerAndBindingVersions,
     DriveAbciValidationDataTriggerVersions, DriveAbciValidationVersions, DriveAbciVersion,
-    DriveAbciVotingMethodVersions, PenaltyAmounts,
+    DriveAbciVotingMethodVersions, DriveAbciWithdrawalConstants, PenaltyAmounts,
 };
 use crate::version::drive_versions::{
     DriveAssetLockMethodVersions, DriveBalancesMethodVersions, DriveBatchOperationsMethodVersion,
@@ -471,7 +471,7 @@ pub const PLATFORM_V3: PlatformVersion = PlatformVersion {
                 withdrawals: DriveIdentityWithdrawalMethodVersions {
                     document: DriveIdentityWithdrawalDocumentMethodVersions {
                         fetch_oldest_withdrawal_documents_by_status: 0,
-                        find_up_to_100_withdrawal_documents_by_status_and_transaction_indices: 0,
+                        find_withdrawal_documents_by_status_and_transaction_indices: 0,
                     },
                     transaction: DriveIdentityWithdrawalTransactionMethodVersions {
                         index: DriveIdentityWithdrawalTransactionIndexMethodVersions {
@@ -483,6 +483,7 @@ pub const PLATFORM_V3: PlatformVersion = PlatformVersion {
                             dequeue_untied_withdrawal_transactions: 0,
                         },
                     },
+                    calculate_current_withdrawal_limit: 0,
                 },
             },
             platform_system: DrivePlatformSystemMethodVersions {
@@ -553,6 +554,7 @@ pub const PLATFORM_V3: PlatformVersion = PlatformVersion {
                 grove_insert_empty_tree: 0,
                 grove_insert_empty_sum_tree: 0,
                 grove_insert_if_not_exists: 0,
+                grove_insert_if_not_exists_return_existing_element: 0,
                 grove_clear: 0,
                 grove_delete: 0,
                 grove_get_raw: 0,
@@ -574,11 +576,13 @@ pub const PLATFORM_V3: PlatformVersion = PlatformVersion {
                 batch_insert_empty_tree: 0,
                 batch_insert_empty_tree_if_not_exists: 0,
                 batch_insert_empty_tree_if_not_exists_check_existing_operations: 0,
+                batch_insert_sum_item_or_add_to_if_already_exists: 0,
                 batch_insert: 0,
                 batch_insert_if_not_exists: 0,
                 batch_insert_if_changed_value: 0,
                 batch_replace: 0,
                 batch_delete: 0,
+                batch_delete_items_in_path_query: 0,
                 batch_remove_raw: 0,
                 batch_delete_up_tree_while_empty: 0,
                 batch_refresh_reference: 0,
@@ -642,6 +646,7 @@ pub const PLATFORM_V3: PlatformVersion = PlatformVersion {
             protocol_upgrade: DriveAbciProtocolUpgradeMethodVersions {
                 check_for_desired_protocol_upgrade: 1,
                 upgrade_protocol_version_on_epoch_change: 0,
+                perform_events_on_first_block_of_protocol_change: None,
                 protocol_version_upgrade_percentage_needed: 67,
             },
             block_fee_processing: DriveAbciBlockFeeProcessingMethodVersions {
@@ -676,6 +681,7 @@ pub const PLATFORM_V3: PlatformVersion = PlatformVersion {
                 pool_withdrawals_into_transactions_queue: 0,
                 update_broadcasted_withdrawal_statuses: 0,
                 append_signatures_and_broadcast_withdrawal_transactions: 0,
+                cleanup_expired_locks_of_withdrawal_amounts: 0,
             },
             voting: DriveAbciVotingMethodVersions {
                 keep_record_of_finished_contested_resource_vote_poll: 0,
@@ -849,6 +855,10 @@ pub const PLATFORM_V3: PlatformVersion = PlatformVersion {
                 maximum_contenders_to_consider: 100,
             },
         },
+        withdrawal_constants: DriveAbciWithdrawalConstants {
+            core_expiration_blocks: 24,
+            cleanup_expired_locks_of_withdrawal_amounts_limit: 0,
+        },
         query: DriveAbciQueryVersions {
             max_returned_elements: 100,
             response_metadata: 0,
@@ -1272,6 +1282,7 @@ pub const PLATFORM_V3: PlatformVersion = PlatformVersion {
         },
         methods: DPPMethodVersions {
             epoch_core_reward_credits_for_distribution: 0,
+            daily_withdrawal_limit: 0,
         },
     },
     system_data_contracts: SystemDataContractVersions {
@@ -1288,6 +1299,7 @@ pub const PLATFORM_V3: PlatformVersion = PlatformVersion {
         max_state_transition_size: 20480, //20 KiB
         max_transitions_in_documents_batch: 1,
         withdrawal_transactions_per_block_limit: 4,
+        max_withdrawal_amount: 50_000_000_000_000,
     },
     consensus: ConsensusVersions {
         tenderdash_consensus_version: 1,
diff --git a/packages/rs-platform-version/src/version/v4.rs b/packages/rs-platform-version/src/version/v4.rs
index 86c62b8e8f0..4d05a510c9f 100644
--- a/packages/rs-platform-version/src/version/v4.rs
+++ b/packages/rs-platform-version/src/version/v4.rs
@@ -32,7 +32,7 @@ use crate::version::drive_abci_versions::{
     DriveAbciStateTransitionValidationVersions, DriveAbciStructureVersions,
     DriveAbciValidationConstants, DriveAbciValidationDataTriggerAndBindingVersions,
     DriveAbciValidationDataTriggerVersions, DriveAbciValidationVersions, DriveAbciVersion,
-    DriveAbciVotingMethodVersions, PenaltyAmounts,
+    DriveAbciVotingMethodVersions, DriveAbciWithdrawalConstants, PenaltyAmounts,
 };
 use crate::version::drive_versions::{
     DriveAssetLockMethodVersions, DriveBalancesMethodVersions, DriveBatchOperationsMethodVersion,
@@ -466,7 +466,7 @@ pub const PLATFORM_V4: PlatformVersion = PlatformVersion {
                 withdrawals: DriveIdentityWithdrawalMethodVersions {
                     document: DriveIdentityWithdrawalDocumentMethodVersions {
                         fetch_oldest_withdrawal_documents_by_status: 0,
-                        find_up_to_100_withdrawal_documents_by_status_and_transaction_indices: 0,
+                        find_withdrawal_documents_by_status_and_transaction_indices: 0,
                     },
                     transaction: DriveIdentityWithdrawalTransactionMethodVersions {
                         index: DriveIdentityWithdrawalTransactionIndexMethodVersions {
@@ -478,6 +478,7 @@ pub const PLATFORM_V4: PlatformVersion = PlatformVersion {
                             dequeue_untied_withdrawal_transactions: 0,
                         },
                     },
+                    calculate_current_withdrawal_limit: 0,
                 },
             },
             platform_system: DrivePlatformSystemMethodVersions {
@@ -548,6 +549,7 @@ pub const PLATFORM_V4: PlatformVersion = PlatformVersion {
                 grove_insert_empty_tree: 0,
                 grove_insert_empty_sum_tree: 0,
                 grove_insert_if_not_exists: 0,
+                grove_insert_if_not_exists_return_existing_element: 0,
                 grove_clear: 0,
                 grove_delete: 0,
                 grove_get_raw: 0,
@@ -569,11 +571,13 @@ pub const PLATFORM_V4: PlatformVersion = PlatformVersion {
                 batch_insert_empty_tree: 0,
                 batch_insert_empty_tree_if_not_exists: 0,
                 batch_insert_empty_tree_if_not_exists_check_existing_operations: 0,
+                batch_insert_sum_item_or_add_to_if_already_exists: 0,
                 batch_insert: 0,
                 batch_insert_if_not_exists: 0,
                 batch_insert_if_changed_value: 0,
                 batch_replace: 0,
                 batch_delete: 0,
+                batch_delete_items_in_path_query: 0,
                 batch_remove_raw: 0,
                 batch_delete_up_tree_while_empty: 0,
                 batch_refresh_reference: 0,
@@ -637,6 +641,7 @@ pub const PLATFORM_V4: PlatformVersion = PlatformVersion {
             protocol_upgrade: DriveAbciProtocolUpgradeMethodVersions {
                 check_for_desired_protocol_upgrade: 1,
                 upgrade_protocol_version_on_epoch_change: 0,
+                perform_events_on_first_block_of_protocol_change: Some(4),
                 protocol_version_upgrade_percentage_needed: 67,
             },
             block_fee_processing: DriveAbciBlockFeeProcessingMethodVersions {
@@ -671,6 +676,7 @@ pub const PLATFORM_V4: PlatformVersion = PlatformVersion {
                 pool_withdrawals_into_transactions_queue: 0,
                 update_broadcasted_withdrawal_statuses: 0,
                 append_signatures_and_broadcast_withdrawal_transactions: 0,
+                cleanup_expired_locks_of_withdrawal_amounts: 0,
             },
             voting: DriveAbciVotingMethodVersions {
                 keep_record_of_finished_contested_resource_vote_poll: 0,
@@ -844,6 +850,10 @@ pub const PLATFORM_V4: PlatformVersion = PlatformVersion {
                 maximum_contenders_to_consider: 100,
             },
         },
+        withdrawal_constants: DriveAbciWithdrawalConstants {
+            core_expiration_blocks: 24,
+            cleanup_expired_locks_of_withdrawal_amounts_limit: 64,
+        },
         query: DriveAbciQueryVersions {
             max_returned_elements: 100,
             response_metadata: 0,
@@ -1267,6 +1277,7 @@ pub const PLATFORM_V4: PlatformVersion = PlatformVersion {
         },
         methods: DPPMethodVersions {
             epoch_core_reward_credits_for_distribution: 0,
+            daily_withdrawal_limit: 0,
         },
     },
     system_data_contracts: SystemDataContractVersions {
@@ -1283,6 +1294,7 @@ pub const PLATFORM_V4: PlatformVersion = PlatformVersion {
         max_state_transition_size: 20480, //20 KiB
         max_transitions_in_documents_batch: 1,
         withdrawal_transactions_per_block_limit: 4,
+        max_withdrawal_amount: 50_000_000_000_000, //500 Dash
     },
     consensus: ConsensusVersions {
         tenderdash_consensus_version: 1,
diff --git a/packages/rs-sdk/Cargo.toml b/packages/rs-sdk/Cargo.toml
index b2efa718059..b53cbdca568 100644
--- a/packages/rs-sdk/Cargo.toml
+++ b/packages/rs-sdk/Cargo.toml
@@ -15,10 +15,10 @@ drive = { path = "../rs-drive", default-features = false, features = [
 ] }
 drive-proof-verifier = { path = "../rs-drive-proof-verifier" }
 dapi-grpc-macros = { path = "../rs-dapi-grpc-macros" }
-thiserror = "1.0.58"
-tokio = { version = "1.36.0", features = ["macros"] }
-tokio-util = { version = "0.7.10" }
-async-trait = { version = "0.1.79" }
+thiserror = "1.0.64"
+tokio = { version = "1.40.0", features = ["macros"] }
+tokio-util = { version = "0.7.12" }
+async-trait = { version = "0.1.83" }
 http = { version = "0.2.12" }
 ciborium = { git = "https://github.com/qrayven/ciborium", branch = "feat-ser-null-as-undefined" }
 serde = { version = "1.0.197", default-features = false, features = [
@@ -38,7 +38,7 @@ bip37-bloom-filter = { git = "https://github.com/dashpay/rs-bip37-bloom-filter",
 pollster = { version = "0.3.0" }
 
 [dev-dependencies]
-tokio = { version = "1.36.0", features = ["macros", "rt-multi-thread"] }
+tokio = { version = "1.40.0", features = ["macros", "rt-multi-thread"] }
 rs-dapi-client = { path = "../rs-dapi-client", features = ["mocks"] }
 base64 = { version = "0.22.1" }
 tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
diff --git a/packages/strategy-tests/src/lib.rs b/packages/strategy-tests/src/lib.rs
index 126538a64a0..61395d99f2a 100644
--- a/packages/strategy-tests/src/lib.rs
+++ b/packages/strategy-tests/src/lib.rs
@@ -45,13 +45,15 @@ use rand::prelude::StdRng;
 use rand::seq::{IteratorRandom, SliceRandom};
 use rand::Rng;
 use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
+use std::ops::RangeInclusive;
 use bincode::{Decode, Encode};
 use dpp::data_contract::document_type::accessors::DocumentTypeV0Getters;
 use dpp::identifier::Identifier;
 use dpp::data_contract::document_type::DocumentType;
+use dpp::fee::Credits;
 use dpp::identity::accessors::IdentityGettersV0;
 use dpp::platform_value::{BinaryData, Bytes32, Value};
-use dpp::ProtocolError;
+use dpp::{dash_to_duffs, ProtocolError};
 use dpp::ProtocolError::{PlatformDeserializationError, PlatformSerializationError};
 use dpp::state_transition::documents_batch_transition::document_base_transition::v0::DocumentBaseTransitionV0;
 use dpp::state_transition::documents_batch_transition::document_create_transition::{DocumentCreateTransition, DocumentCreateTransitionV0};
@@ -153,6 +155,7 @@ pub struct IdentityInsertInfo {
     pub frequency: Frequency,
     pub start_keys: u8,
     pub extra_keys: KeyMaps,
+    pub start_balance_range: RangeInclusive<Credits>,
 }
 
 impl Default for IdentityInsertInfo {
@@ -161,6 +164,7 @@ impl Default for IdentityInsertInfo {
             frequency: Default::default(),
             start_keys: 5,
             extra_keys: Default::default(),
+            start_balance_range: dash_to_duffs!(1)..=dash_to_duffs!(1),
         }
     }
 }
@@ -1167,7 +1171,10 @@ impl Strategy {
                     }
 
                     // Generate state transition for identity top-up operation
-                    OperationType::IdentityTopUp if !current_identities.is_empty() => {
+                    OperationType::IdentityTopUp(_amount_range)
+                        if !current_identities.is_empty() =>
+                    {
+                        // todo: use amount ranges
                         // Use a cyclic iterator over the identities to ensure we can create 'count' transitions
                         let cyclic_identities = current_identities.iter().cycle();
 
@@ -1261,13 +1268,16 @@ impl Strategy {
                     }
 
                     // Generate state transition for identity withdrawal operation
-                    OperationType::IdentityWithdrawal if !current_identities.is_empty() => {
+                    OperationType::IdentityWithdrawal(amount_range)
+                        if !current_identities.is_empty() =>
+                    {
                         for i in 0..count {
                             let index = (i as usize) % current_identities.len();
                             let random_identity = &mut current_identities[index];
                             let state_transition =
                                 crate::transitions::create_identity_withdrawal_transition(
                                     random_identity,
+                                    amount_range.clone(),
                                     identity_nonce_counter,
                                     signer,
                                     rng,
@@ -1853,6 +1863,7 @@ mod tests {
     use crate::operations::{DocumentAction, DocumentOp, Operation, OperationType};
     use crate::transitions::create_state_transitions_for_identities;
     use crate::{StartIdentities, Strategy};
+    use dpp::dash_to_duffs;
     use dpp::data_contract::accessors::v0::DataContractV0Getters;
     use dpp::data_contract::document_type::random_document::{
         DocumentFieldFillSize, DocumentFieldFillType,
@@ -1902,6 +1913,7 @@ mod tests {
 
         let start_identities = create_state_transitions_for_identities(
             vec![identity1, identity2],
+            &(dash_to_duffs!(1)..=dash_to_duffs!(1)),
             &mut simple_signer,
             &mut rng,
             platform_version,
diff --git a/packages/strategy-tests/src/operations.rs b/packages/strategy-tests/src/operations.rs
index c3e52414cd1..675e9968433 100644
--- a/packages/strategy-tests/src/operations.rs
+++ b/packages/strategy-tests/src/operations.rs
@@ -9,6 +9,7 @@ use dpp::data_contract::document_type::v0::random_document_type::RandomDocumentT
 use dpp::data_contract::document_type::DocumentType;
 use dpp::data_contract::serialized_version::DataContractInSerializationFormat;
 use dpp::data_contract::{DataContract as Contract, DataContract};
+use dpp::fee::Credits;
 use dpp::identifier::Identifier;
 use dpp::identity::IdentityPublicKey;
 use dpp::platform_value::Value;
@@ -26,7 +27,7 @@ use platform_version::{TryFromPlatformVersioned, TryIntoPlatformVersioned};
 use rand::distributions::{Distribution, WeightedIndex};
 use rand::prelude::StdRng;
 use std::collections::BTreeMap;
-use std::ops::Range;
+use std::ops::{Range, RangeInclusive};
 
 #[derive(Clone, Debug, PartialEq, Encode, Decode)]
 pub enum DocumentAction {
@@ -494,12 +495,14 @@ impl VoteAction {
     }
 }
 
+pub type AmountRange = RangeInclusive<Credits>;
+
 #[derive(Clone, Debug, PartialEq)]
 pub enum OperationType {
     Document(DocumentOp),
-    IdentityTopUp,
+    IdentityTopUp(AmountRange),
     IdentityUpdate(IdentityUpdateOp),
-    IdentityWithdrawal,
+    IdentityWithdrawal(AmountRange),
     ContractCreate(RandomDocumentTypeParameters, DocumentTypeCount),
     ContractUpdate(DataContractUpdateOp),
     IdentityTransfer,
@@ -509,9 +512,9 @@ pub enum OperationType {
 #[derive(Clone, Debug, Encode, Decode)]
 enum OperationTypeInSerializationFormat {
     Document(Vec<u8>),
-    IdentityTopUp,
+    IdentityTopUp(AmountRange),
     IdentityUpdate(IdentityUpdateOp),
-    IdentityWithdrawal,
+    IdentityWithdrawal(AmountRange),
     ContractCreate(RandomDocumentTypeParameters, DocumentTypeCount),
     ContractUpdate(Vec<u8>),
     IdentityTransfer,
@@ -540,12 +543,14 @@ impl PlatformSerializableWithPlatformVersion for OperationType {
                     .serialize_consume_to_bytes_with_platform_version(platform_version)?;
                 OperationTypeInSerializationFormat::Document(document_op_in_serialization_format)
             }
-            OperationType::IdentityTopUp => OperationTypeInSerializationFormat::IdentityTopUp,
+            OperationType::IdentityTopUp(amount_range) => {
+                OperationTypeInSerializationFormat::IdentityTopUp(amount_range)
+            }
             OperationType::IdentityUpdate(identity_update_op) => {
                 OperationTypeInSerializationFormat::IdentityUpdate(identity_update_op)
             }
-            OperationType::IdentityWithdrawal => {
-                OperationTypeInSerializationFormat::IdentityWithdrawal
+            OperationType::IdentityWithdrawal(amount_range) => {
+                OperationTypeInSerializationFormat::IdentityWithdrawal(amount_range)
             }
             OperationType::ContractCreate(p, c) => {
                 OperationTypeInSerializationFormat::ContractCreate(p, c)
@@ -601,12 +606,14 @@ impl PlatformDeserializableWithPotentialValidationFromVersionedStructure for Ope
                 )?;
                 OperationType::Document(document_op)
             }
-            OperationTypeInSerializationFormat::IdentityTopUp => OperationType::IdentityTopUp,
+            OperationTypeInSerializationFormat::IdentityTopUp(amount_range) => {
+                OperationType::IdentityTopUp(amount_range)
+            }
             OperationTypeInSerializationFormat::IdentityUpdate(identity_update_op) => {
                 OperationType::IdentityUpdate(identity_update_op)
             }
-            OperationTypeInSerializationFormat::IdentityWithdrawal => {
-                OperationType::IdentityWithdrawal
+            OperationTypeInSerializationFormat::IdentityWithdrawal(amount_range) => {
+                OperationType::IdentityWithdrawal(amount_range)
             }
             OperationTypeInSerializationFormat::ContractCreate(p, c) => {
                 OperationType::ContractCreate(p, c)
diff --git a/packages/strategy-tests/src/transitions.rs b/packages/strategy-tests/src/transitions.rs
index 7b0f3d92c42..85d03eb333d 100644
--- a/packages/strategy-tests/src/transitions.rs
+++ b/packages/strategy-tests/src/transitions.rs
@@ -33,6 +33,7 @@ use dpp::withdrawal::Pooling;
 use rand::prelude::{IteratorRandom, StdRng};
 use simple_signer::signer::SimpleSigner;
 
+use crate::operations::AmountRange;
 use crate::KeyMaps;
 use dpp::dashcore::transaction::special_transaction::asset_lock::AssetLockPayload;
 use dpp::dashcore::transaction::special_transaction::TransactionPayload;
@@ -76,6 +77,24 @@ pub fn instant_asset_lock_proof_fixture(one_time_private_key: PrivateKey) -> Ass
     AssetLockProof::Instant(is_lock_proof)
 }
 
+pub fn instant_asset_lock_proof_fixture_with_dynamic_range(
+    one_time_private_key: PrivateKey,
+    amount_range: &AmountRange,
+    rng: &mut StdRng,
+) -> AssetLockProof {
+    let transaction = instant_asset_lock_proof_transaction_fixture_with_dynamic_amount(
+        one_time_private_key,
+        amount_range,
+        rng,
+    );
+
+    let instant_lock = instant_asset_lock_is_lock_fixture(transaction.txid());
+
+    let is_lock_proof = InstantAssetLockProof::new(instant_lock, transaction, 0);
+
+    AssetLockProof::Instant(is_lock_proof)
+}
+
 /// Constructs a fixture of a `Transaction` representing an instant asset lock proof.
 ///
 /// The `Transaction` structure is a basic unit of data in a blockchain, recording the transfer of assets between parties.
@@ -160,6 +179,73 @@ pub fn instant_asset_lock_proof_transaction_fixture(
     }
 }
 
+pub fn instant_asset_lock_proof_transaction_fixture_with_dynamic_amount(
+    one_time_private_key: PrivateKey,
+    amount_range: &AmountRange,
+    rng: &mut StdRng,
+) -> Transaction {
+    let secp = Secp256k1::new();
+
+    let private_key_hex = "cSBnVM4xvxarwGQuAfQFwqDg9k5tErHUHzgWsEfD4zdwUasvqRVY";
+    let private_key = PrivateKey::from_str(private_key_hex).unwrap();
+    let public_key = private_key.public_key(&secp);
+    let public_key_hash = public_key.pubkey_hash();
+    //let from_address = Address::p2pkh(&public_key, Network::Testnet);
+    let one_time_public_key = one_time_private_key.public_key(&secp);
+
+    // We are going to fund 1 Dash and
+    // assume that input has 100005000
+    // 5000 will be returned back
+
+    let input_txid =
+        Txid::from_str("a477af6b2667c29670467e4e0728b685ee07b240235771862318e29ddbe58458").unwrap();
+
+    let input_outpoint = OutPoint::new(input_txid, 0);
+
+    let input = TxIn {
+        previous_output: input_outpoint,
+        script_sig: ScriptBuf::new_p2pkh(&public_key_hash),
+        sequence: 0,
+        witness: Default::default(),
+    };
+
+    let one_time_key_hash = one_time_public_key.pubkey_hash();
+
+    let value_amount = if amount_range.start() == amount_range.end() {
+        *amount_range.start() //avoid using rng if possible
+    } else {
+        rng.gen_range(amount_range.clone())
+    };
+
+    let funding_output = TxOut {
+        value: value_amount,
+        script_pubkey: ScriptBuf::new_p2pkh(&one_time_key_hash),
+    };
+
+    let burn_output = TxOut {
+        value: value_amount,
+        script_pubkey: ScriptBuf::new_op_return(&[]),
+    };
+
+    let change_output = TxOut {
+        value: 5000,
+        script_pubkey: ScriptBuf::new_p2pkh(&public_key_hash),
+    };
+
+    let payload = TransactionPayload::AssetLockPayloadType(AssetLockPayload {
+        version: 0,
+        credit_outputs: vec![funding_output],
+    });
+
+    Transaction {
+        version: 0,
+        lock_time: 0,
+        input: vec![input],
+        output: vec![burn_output, change_output],
+        special_transaction_payload: Some(payload),
+    }
+}
+
 /// Constructs a fixture of `InstantLock` representing an instant asset lock.
 ///
 /// The `InstantLock` structure is often used in blockchain systems to represent a condition where funds (or assets) are locked instantly, making them non-spendable until a specified condition is met or the lock duration expires.
@@ -483,6 +569,7 @@ pub fn create_identity_update_transition_disable_keys(
 /// - If the identity does not have a suitable withdrawal address or key for signing.
 pub fn create_identity_withdrawal_transition(
     identity: &mut Identity,
+    amount_range: AmountRange,
     identity_nonce_counter: &mut BTreeMap<Identifier, u64>,
     signer: &mut SimpleSigner,
     rng: &mut StdRng,
@@ -499,12 +586,15 @@ pub fn create_identity_withdrawal_transition(
         // We can send it to the withdrawal address
         create_identity_withdrawal_transition_sent_to_identity_transfer_key(
             identity,
+            amount_range,
             identity_nonce_counter,
             signer,
+            rng,
         )
     } else {
         create_identity_withdrawal_transition_with_output_address(
             identity,
+            amount_range,
             identity_nonce_counter,
             signer,
             rng,
@@ -546,14 +636,16 @@ pub fn create_identity_withdrawal_transition(
 /// - If there's an error during the signing process.
 pub fn create_identity_withdrawal_transition_sent_to_identity_transfer_key(
     identity: &mut Identity,
+    amount_range: AmountRange,
     identity_nonce_counter: &mut BTreeMap<Identifier, u64>,
     signer: &mut SimpleSigner,
+    rng: &mut StdRng,
 ) -> StateTransition {
     let nonce = identity_nonce_counter.entry(identity.id()).or_default();
     *nonce += 1;
     let mut withdrawal: StateTransition = IdentityCreditWithdrawalTransitionV1 {
         identity_id: identity.id(),
-        amount: 1000000, // 1 duff
+        amount: rng.gen_range(amount_range),
         core_fee_per_byte: MIN_CORE_FEE_PER_BYTE,
         pooling: Pooling::Never,
         output_script: None,
@@ -627,6 +719,7 @@ pub fn create_identity_withdrawal_transition_sent_to_identity_transfer_key(
 /// - If there's an error during the signing process.
 pub fn create_identity_withdrawal_transition_with_output_address(
     identity: &mut Identity,
+    amount_range: AmountRange,
     identity_nonce_counter: &mut BTreeMap<Identifier, u64>,
     signer: &mut SimpleSigner,
     rng: &mut StdRng,
@@ -635,10 +728,14 @@ pub fn create_identity_withdrawal_transition_with_output_address(
     *nonce += 1;
     let mut withdrawal: StateTransition = IdentityCreditWithdrawalTransitionV1 {
         identity_id: identity.id(),
-        amount: 1000000, // 1 duff
+        amount: rng.gen_range(amount_range),
         core_fee_per_byte: MIN_CORE_FEE_PER_BYTE,
         pooling: Pooling::Never,
-        output_script: Some(CoreScript::random_p2sh(rng)),
+        output_script: if rng.gen_bool(0.5) {
+            Some(CoreScript::random_p2pkh(rng))
+        } else {
+            Some(CoreScript::random_p2sh(rng))
+        },
         nonce: *nonce,
         user_fee_increase: 0,
         signature_public_key_id: 0,
@@ -923,6 +1020,7 @@ pub fn create_identities_state_transitions(
 /// - Conversion and encoding errors related to the cryptographic data.
 pub fn create_state_transitions_for_identities(
     identities: Vec<Identity>,
+    amount_range: &AmountRange,
     signer: &SimpleSigner,
     rng: &mut StdRng,
     platform_version: &PlatformVersion,
@@ -935,8 +1033,11 @@ pub fn create_state_transitions_for_identities(
                 .unwrap();
             let sk: [u8; 32] = pk.try_into().unwrap();
             let secret_key = SecretKey::from_str(hex::encode(sk).as_str()).unwrap();
-            let asset_lock_proof =
-                instant_asset_lock_proof_fixture(PrivateKey::new(secret_key, Network::Dash));
+            let asset_lock_proof = instant_asset_lock_proof_fixture_with_dynamic_range(
+                PrivateKey::new(secret_key, Network::Dash),
+                amount_range,
+                rng,
+            );
             let identity_create_transition =
                 IdentityCreateTransition::try_from_identity_with_signer(
                     &identity.clone(),
diff --git a/packages/withdrawals-contract/Cargo.toml b/packages/withdrawals-contract/Cargo.toml
index 357bc6d42f3..2523ed32026 100644
--- a/packages/withdrawals-contract/Cargo.toml
+++ b/packages/withdrawals-contract/Cargo.toml
@@ -7,7 +7,7 @@ rust-version.workspace = true
 license = "MIT"
 
 [dependencies]
-thiserror = "1.0.58"
+thiserror = "1.0.64"
 platform-version = { path = "../rs-platform-version" }
 platform-value = { path = "../rs-platform-value" }
 num_enum = "0.5.7"
diff --git a/packages/withdrawals-contract/src/lib.rs b/packages/withdrawals-contract/src/lib.rs
index 0eece0b61b8..baa5887f8e8 100644
--- a/packages/withdrawals-contract/src/lib.rs
+++ b/packages/withdrawals-contract/src/lib.rs
@@ -32,10 +32,15 @@ pub const OWNER_ID: Identifier = Identifier(IdentifierBytes32(OWNER_ID_BYTES));
     IntoPrimitive,
 )]
 pub enum WithdrawalStatus {
+    /// The documents are in the state and waiting to be processed.
     QUEUED = 0,
+    /// Pooled happens when we are waiting for signing.
     POOLED = 1,
+    /// We have broadcasted the transaction to core.
     BROADCASTED = 2,
+    /// The transaction is now complete.
     COMPLETE = 3,
+    /// We broadcasted the transaction but core never saw it or rejected it.
     EXPIRED = 4,
 }