diff --git a/chainstate/test-framework/src/block_builder.rs b/chainstate/test-framework/src/block_builder.rs
index e3ebf14661..6bd53115dc 100644
--- a/chainstate/test-framework/src/block_builder.rs
+++ b/chainstate/test-framework/src/block_builder.rs
@@ -13,17 +13,18 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-use std::collections::BTreeSet;
+use std::collections::{BTreeMap, BTreeSet};
 
 use crate::framework::BlockOutputs;
-use crate::utils::{create_multiple_utxo_data, create_new_outputs, outputs_from_block};
+use crate::utils::{create_new_outputs, outputs_from_block};
 use crate::TestFramework;
 use chainstate::{BlockSource, ChainstateError};
+use chainstate_storage::BlockchainStorageRead;
 use chainstate_types::BlockIndex;
 use common::chain::block::block_body::BlockBody;
 use common::chain::block::signed_block_header::{BlockHeaderSignature, BlockHeaderSignatureData};
 use common::chain::block::BlockHeader;
-use common::chain::{OutPointSourceId, UtxoOutPoint};
+use common::chain::{AccountNonce, AccountType, OutPointSourceId, UtxoOutPoint};
 use common::{
     chain::{
         block::{timestamp::BlockTimestamp, BlockReward, ConsensusData},
@@ -37,6 +38,7 @@ use crypto::key::PrivateKey;
 use crypto::random::{CryptoRng, Rng};
 use itertools::Itertools;
 use serialization::Encode;
+use tokens_accounting::{InMemoryTokensAccounting, TokensAccountingDB};
 
 /// The block builder that allows construction and processing of a block.
 pub struct BlockBuilder<'f> {
@@ -47,8 +49,12 @@ pub struct BlockBuilder<'f> {
     consensus_data: ConsensusData,
     reward: BlockReward,
     block_source: BlockSource,
-    used_utxo: BTreeSet<UtxoOutPoint>,
     block_signing_key: Option<PrivateKey>,
+
+    // need these fields to track info across the txs
+    used_utxo: BTreeSet<UtxoOutPoint>,
+    account_nonce_tracker: BTreeMap<AccountType, AccountNonce>,
+    tokens_data: InMemoryTokensAccounting,
 }
 
 impl<'f> BlockBuilder<'f> {
@@ -61,6 +67,13 @@ impl<'f> BlockBuilder<'f> {
         let reward = BlockReward::new(Vec::new());
         let block_source = BlockSource::Local;
         let used_utxo = BTreeSet::new();
+        let account_nonce_tracker = BTreeMap::new();
+
+        let all_tokens_data = framework.storage.read_tokens_accounting_data().unwrap();
+        let tokens_data = InMemoryTokensAccounting::from_values(
+            all_tokens_data.token_data,
+            all_tokens_data.circulating_supply,
+        );
 
         Self {
             framework,
@@ -70,8 +83,10 @@ impl<'f> BlockBuilder<'f> {
             consensus_data,
             reward,
             block_source,
-            used_utxo,
             block_signing_key: None,
+            used_utxo,
+            account_nonce_tracker,
+            tokens_data,
         }
     }
 
@@ -87,36 +102,57 @@ impl<'f> BlockBuilder<'f> {
         self
     }
 
-    /// Adds a transaction that uses random utxos
+    /// Adds a transaction that uses random utxos and accounts
     pub fn add_test_transaction(mut self, rng: &mut (impl Rng + CryptoRng)) -> Self {
-        let utxo_set = self.framework.storage.read_utxo_set().unwrap();
-
-        if !utxo_set.is_empty() {
-            // TODO: get n utxos as inputs and create m new outputs
-            let index = rng.gen_range(0..utxo_set.len());
-            let (outpoint, utxo) = utxo_set.iter().nth(index).unwrap();
-            if !self.used_utxo.contains(outpoint) {
-                let new_utxo_data = create_multiple_utxo_data(
-                    &self.framework.chainstate,
-                    outpoint.source_id(),
-                    outpoint.output_index() as usize,
-                    utxo.output(),
-                    rng,
-                );
-
-                if let Some((witness, input, output)) = new_utxo_data {
-                    self.used_utxo.insert(outpoint.clone());
-                    return self.add_transaction(
-                        SignedTransaction::new(
-                            Transaction::new(0, vec![input], output).unwrap(),
-                            vec![witness],
-                        )
-                        .expect("invalid witness count"),
-                    );
-                }
-            }
+        let utxo_set = self
+            .framework
+            .storage
+            .read_utxo_set()
+            .unwrap()
+            .into_iter()
+            .filter(|(outpoint, _)| !self.used_utxo.contains(outpoint))
+            .collect();
+
+        let account_nonce_getter = Box::new(|account: AccountType| -> Option<AccountNonce> {
+            self.account_nonce_tracker
+                .get(&account)
+                .copied()
+                .or_else(|| self.framework.storage.get_account_nonce_count(account).unwrap())
+        });
+
+        let (tx, new_tokens_delta) = super::random_tx_maker::RandomTxMaker::new(
+            &self.framework.chainstate,
+            &utxo_set,
+            &self.tokens_data,
+            account_nonce_getter,
+        )
+        .make(rng);
+
+        if !tx.inputs().is_empty() && !tx.outputs().is_empty() {
+            // flush new tokens info to the in memory store
+            let mut tokens_db = TokensAccountingDB::new(&mut self.tokens_data);
+            tokens_db.merge_with_delta(new_tokens_delta).unwrap();
+
+            // update used utxo set because this function can be called multiple times without flushing data to storage
+            tx.inputs().iter().for_each(|input| {
+                match input {
+                    TxInput::Utxo(utxo_outpoint) => {
+                        self.used_utxo.insert(utxo_outpoint.clone());
+                    }
+                    TxInput::Account(account) => {
+                        self.account_nonce_tracker
+                            .insert((*account.account()).into(), account.nonce());
+                    }
+                };
+            });
+
+            let witnesses = tx.inputs().iter().map(|_| super::empty_witness(rng)).collect();
+            let tx = SignedTransaction::new(tx, witnesses).expect("invalid witness count");
+
+            self.add_transaction(tx)
+        } else {
+            self
         }
-        self
     }
 
     /// Returns regular transaction output(s) if any, otherwise returns block reward outputs
diff --git a/chainstate/test-framework/src/lib.rs b/chainstate/test-framework/src/lib.rs
index d03a3efea5..4863637532 100644
--- a/chainstate/test-framework/src/lib.rs
+++ b/chainstate/test-framework/src/lib.rs
@@ -19,6 +19,7 @@ mod block_builder;
 mod framework;
 mod framework_builder;
 mod pos_block_builder;
+mod random_tx_maker;
 mod transaction_builder;
 mod tx_verification_strategy;
 mod utils;
diff --git a/chainstate/test-framework/src/random_tx_maker.rs b/chainstate/test-framework/src/random_tx_maker.rs
new file mode 100644
index 0000000000..5ee055a6ef
--- /dev/null
+++ b/chainstate/test-framework/src/random_tx_maker.rs
@@ -0,0 +1,407 @@
+// Copyright (c) 2023 RBB S.r.l
+// opensource@mintlayer.org
+// SPDX-License-Identifier: MIT
+// Licensed under the MIT License;
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// https://github.com/mintlayer/mintlayer-core/blob/master/LICENSE
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+use std::{collections::BTreeMap, vec};
+
+use crate::TestChainstate;
+
+use chainstate::chainstate_interface::ChainstateInterface;
+use common::{
+    chain::{
+        output_value::OutputValue,
+        tokens::{make_token_id, NftIssuance, TokenId, TokenIssuance, TokenTotalSupply},
+        AccountNonce, AccountOp, AccountOutPoint, AccountType, Destination, Transaction, TxInput,
+        TxOutput, UtxoOutPoint,
+    },
+    primitives::Amount,
+};
+use crypto::random::{CryptoRng, Rng};
+use itertools::Itertools;
+use test_utils::nft_utils::*;
+use tokens_accounting::{
+    InMemoryTokensAccounting, TokensAccountingCache, TokensAccountingDB, TokensAccountingDeltaData,
+    TokensAccountingOperations, TokensAccountingView,
+};
+use utxo::Utxo;
+
+pub struct RandomTxMaker<'a> {
+    chainstate: &'a TestChainstate,
+    utxo_set: &'a BTreeMap<UtxoOutPoint, Utxo>,
+    tokens_in_memory_store: &'a InMemoryTokensAccounting,
+
+    account_nonce_getter: Box<dyn Fn(AccountType) -> Option<AccountNonce> + 'a>,
+    account_nonce_tracker: BTreeMap<AccountType, AccountNonce>,
+
+    // Transaction is composed of multiple inputs and outputs
+    // but tokens can be issued only using input0 so a flag to check is required
+    token_can_be_issued: bool,
+}
+
+impl<'a> RandomTxMaker<'a> {
+    pub fn new(
+        chainstate: &'a TestChainstate,
+        utxo_set: &'a BTreeMap<UtxoOutPoint, Utxo>,
+        tokens_in_memory_store: &'a InMemoryTokensAccounting,
+        account_nonce_getter: Box<dyn Fn(AccountType) -> Option<AccountNonce> + 'a>,
+    ) -> Self {
+        Self {
+            chainstate,
+            utxo_set,
+            tokens_in_memory_store,
+            account_nonce_getter,
+            account_nonce_tracker: BTreeMap::new(),
+            token_can_be_issued: true,
+        }
+    }
+
+    pub fn make(
+        mut self,
+        rng: &mut (impl Rng + CryptoRng),
+    ) -> (Transaction, TokensAccountingDeltaData) {
+        // TODO: ideally all inputs/outputs should be shuffled but it would mess up with token issuance
+        // because ids are build from input0
+
+        let tokens_db = TokensAccountingDB::new(self.tokens_in_memory_store);
+        let mut tokens_cache = TokensAccountingCache::new(&tokens_db);
+
+        // Select random number of utxos to spend
+        let inputs_with_utxos = self.select_utxos(rng);
+
+        // Spend selected utxos
+        let (mut inputs, mut outputs) =
+            self.create_utxo_spending(rng, &mut tokens_cache, inputs_with_utxos);
+
+        // Select random number of token accounts to spend from
+        let account_inputs = self.select_accounts(rng);
+
+        // Spending from a token account requires paying fee. Find sufficient utxo per account input.
+        let fee = self.chainstate.get_chain_config().token_min_supply_change_fee();
+        let fee_inputs = self
+            .utxo_set
+            .iter()
+            .filter(|(outpoint, utxo)| {
+                let input: TxInput = (**outpoint).clone().into();
+                !inputs.iter().contains(&input)
+                    && super::get_output_value(utxo.output())
+                        .map_or(false, |v| v.coin_amount().unwrap_or(Amount::ZERO) >= fee)
+            })
+            .map(|(outpoint, _)| outpoint.clone().into())
+            .take(inputs.len())
+            .collect::<Vec<TxInput>>();
+
+        // If enough utxos to pay fees
+        if fee_inputs.len() == account_inputs.len() {
+            let (account_inputs, account_outputs) =
+                self.create_account_spending(rng, &mut tokens_cache, &account_inputs, fee_inputs);
+
+            inputs.extend(account_inputs);
+            outputs.extend(account_outputs);
+        };
+
+        (
+            Transaction::new(0, inputs, outputs).unwrap(),
+            tokens_cache.consume(),
+        )
+    }
+
+    fn select_utxos(&self, rng: &mut impl Rng) -> Vec<(UtxoOutPoint, TxOutput)> {
+        // TODO: it take several items from the beginning of the collection assuming that outpoints
+        // are ids thus the order changes with new insertions. But more sophisticated random selection can be implemented here
+        let number_of_inputs = rng.gen_range(1..5);
+        self.utxo_set
+            .iter()
+            .take(number_of_inputs)
+            .map(|(outpoint, utxo)| (outpoint.clone(), utxo.output().clone()))
+            .collect()
+    }
+
+    fn select_accounts(&self, rng: &mut impl Rng) -> Vec<TokenId> {
+        // TODO: it take several items from the beginning of the collection assuming that outpoints
+        // are ids thus the order changes with new insertions. But more sophisticated random selection can be implemented here
+        let number_of_inputs = rng.gen_range(1..5);
+        self.tokens_in_memory_store
+            .tokens_data()
+            .iter()
+            .take(number_of_inputs)
+            .map(|(token_id, _)| *token_id)
+            .collect()
+    }
+
+    fn get_next_nonce(&mut self, account: AccountType) -> AccountNonce {
+        *self
+            .account_nonce_tracker
+            .entry(account)
+            .and_modify(|nonce| {
+                *nonce = nonce.increment().unwrap();
+            })
+            .or_insert_with(|| {
+                (self.account_nonce_getter)(account)
+                    .map_or(AccountNonce::new(0), |nonce| nonce.increment().unwrap())
+            })
+    }
+
+    fn create_account_spending(
+        mut self,
+        rng: &mut (impl Rng + CryptoRng),
+        tokens_cache: &mut (impl TokensAccountingView + TokensAccountingOperations),
+        inputs: &[TokenId],
+        fee_inputs: Vec<TxInput>,
+    ) -> (Vec<TxInput>, Vec<TxOutput>) {
+        assert_eq!(inputs.len(), fee_inputs.len());
+
+        let mut result_inputs = Vec::new();
+        let mut result_outputs = Vec::new();
+
+        let min_tx_fee = self.chainstate.get_chain_config().token_min_supply_change_fee();
+
+        for (i, token_id) in inputs.iter().copied().enumerate() {
+            if rng.gen_bool(0.9) {
+                let circulating_supply =
+                    tokens_cache.get_circulating_supply(&token_id).unwrap().unwrap_or(Amount::ZERO);
+                let token_data = tokens_cache.get_token_data(&token_id).unwrap();
+
+                if let Some(token_data) = token_data {
+                    let tokens_accounting::TokenData::FungibleToken(token_data) = token_data;
+
+                    if !token_data.is_locked() {
+                        // mint
+                        let supply_limit = match token_data.total_supply() {
+                            TokenTotalSupply::Fixed(v) => *v,
+                            TokenTotalSupply::Lockable | TokenTotalSupply::Unlimited => {
+                                Amount::from_atoms(i128::MAX as u128)
+                            }
+                        };
+                        let supply_left = (supply_limit - circulating_supply).unwrap();
+                        let to_mint =
+                            Amount::from_atoms(rng.gen_range(1..supply_left.into_atoms()));
+
+                        let new_nonce = self.get_next_nonce(AccountType::TokenSupply(token_id));
+                        let account_input = TxInput::Account(AccountOutPoint::new(
+                            new_nonce,
+                            AccountOp::MintTokens(token_id, to_mint),
+                        ));
+                        result_inputs.extend(vec![account_input, fee_inputs[i].clone()]);
+
+                        let outputs = vec![
+                            TxOutput::Transfer(
+                                OutputValue::TokenV1(token_id, to_mint),
+                                Destination::AnyoneCanSpend,
+                            ),
+                            TxOutput::Burn(OutputValue::Coin(min_tx_fee)),
+                        ];
+                        result_outputs.extend(outputs);
+
+                        let _ = tokens_cache.mint_tokens(token_id, to_mint).unwrap();
+                    }
+                }
+            } else {
+                let is_locked = match tokens_cache.get_token_data(&token_id).unwrap().unwrap() {
+                    tokens_accounting::TokenData::FungibleToken(data) => data.is_locked(),
+                };
+
+                if !is_locked {
+                    let new_nonce = self.get_next_nonce(AccountType::TokenSupply(token_id));
+                    let account_input = TxInput::Account(AccountOutPoint::new(
+                        new_nonce,
+                        AccountOp::LockTokenSupply(token_id),
+                    ));
+                    result_inputs.extend(vec![account_input, fee_inputs[i].clone()]);
+
+                    let outputs = vec![TxOutput::Burn(OutputValue::Coin(min_tx_fee))];
+                    result_outputs.extend(outputs);
+
+                    let _ = tokens_cache.lock_circulating_supply(token_id).unwrap();
+                }
+            }
+        }
+
+        (result_inputs, result_outputs)
+    }
+
+    /// Given an output as in input creates multiple new random outputs.
+    fn create_utxo_spending(
+        &mut self,
+        rng: &mut (impl Rng + CryptoRng),
+        tokens_cache: &mut (impl TokensAccountingView + TokensAccountingOperations),
+        inputs: Vec<(UtxoOutPoint, TxOutput)>,
+    ) -> (Vec<TxInput>, Vec<TxOutput>) {
+        let mut result_inputs = Vec::new();
+        let mut result_outputs = Vec::new();
+        let mut fee_input_to_change_supply: Option<TxInput> = None;
+
+        for (i, (outpoint, input_utxo)) in inputs.iter().enumerate() {
+            if i > 0 {
+                self.token_can_be_issued = false;
+            }
+
+            match super::get_output_value(input_utxo).unwrap() {
+                OutputValue::Coin(output_value) => {
+                    // save output for potential unmint fee
+                    if output_value
+                        >= self.chainstate.get_chain_config().token_min_supply_change_fee()
+                        && fee_input_to_change_supply.is_none()
+                        && inputs.len() > 1
+                    {
+                        fee_input_to_change_supply = Some(TxInput::Utxo(outpoint.clone()));
+                    } else {
+                        let new_outputs = self.spend_coins(rng, outpoint, output_value);
+                        result_inputs.push(TxInput::Utxo(outpoint.clone()));
+                        result_outputs.extend(new_outputs);
+                    }
+                }
+                OutputValue::TokenV0(_) => {
+                    unimplemented!("deprecated tokens version")
+                }
+                OutputValue::TokenV1(token_id, amount) => {
+                    let (new_inputs, new_outputs) = self.spend_tokens_v1(
+                        rng,
+                        tokens_cache,
+                        token_id,
+                        amount,
+                        &mut fee_input_to_change_supply,
+                    );
+                    result_inputs.push(TxInput::Utxo(outpoint.clone()));
+                    result_inputs.extend(new_inputs);
+                    result_outputs.extend(new_outputs);
+                }
+            };
+        }
+        (result_inputs, result_outputs)
+    }
+
+    fn spend_coins(
+        &mut self,
+        rng: &mut (impl Rng + CryptoRng),
+        outpoint: &UtxoOutPoint,
+        coins: Amount,
+    ) -> Vec<TxOutput> {
+        let num_outputs = rng.gen_range(1..5);
+        let switch = rng.gen_range(0..3);
+        if switch == 0 && self.token_can_be_issued {
+            // issue token v1
+            let min_tx_fee = self.chainstate.get_chain_config().token_min_issuance_fee();
+            if coins >= min_tx_fee {
+                self.token_can_be_issued = false;
+                let change = (coins - min_tx_fee).unwrap();
+                // Coin output is created intentionally besides issuance output in order to not waste utxo
+                // (e.g. single genesis output on issuance)
+                vec![
+                    TxOutput::IssueFungibleToken(Box::new(TokenIssuance::V1(
+                        random_token_issuance_v1(self.chainstate.get_chain_config(), rng),
+                    ))),
+                    TxOutput::Transfer(OutputValue::Coin(change), Destination::AnyoneCanSpend),
+                    TxOutput::Burn(OutputValue::Coin(min_tx_fee)),
+                ]
+            } else {
+                Vec::new()
+            }
+        } else if switch == 1 && self.token_can_be_issued {
+            // issue nft v1
+            let min_tx_fee = self.chainstate.get_chain_config().token_min_issuance_fee();
+            if coins >= min_tx_fee {
+                self.token_can_be_issued = false;
+                let change = (coins - min_tx_fee).unwrap();
+                // Coin output is created intentionally besides issuance output in order to not waste utxo
+                // (e.g. single genesis output on issuance)
+                vec![
+                    TxOutput::IssueNft(
+                        make_token_id(&[outpoint.clone().into()]).unwrap(),
+                        Box::new(NftIssuance::V0(random_nft_issuance(
+                            self.chainstate.get_chain_config(),
+                            rng,
+                        ))),
+                        Destination::AnyoneCanSpend,
+                    ),
+                    TxOutput::Transfer(OutputValue::Coin(change), Destination::AnyoneCanSpend),
+                    TxOutput::Burn(OutputValue::Coin(min_tx_fee)),
+                ]
+            } else {
+                Vec::new()
+            }
+        } else {
+            // transfer coins
+            (0..num_outputs)
+                .map(|_| {
+                    let new_value = Amount::from_atoms(coins.into_atoms() / num_outputs);
+                    debug_assert!(new_value >= Amount::from_atoms(1));
+                    TxOutput::Transfer(OutputValue::Coin(new_value), Destination::AnyoneCanSpend)
+                })
+                .collect()
+        }
+    }
+
+    fn spend_tokens_v1(
+        &mut self,
+        rng: &mut impl Rng,
+        tokens_cache: &mut (impl TokensAccountingView + TokensAccountingOperations),
+        token_id: TokenId,
+        amount: Amount,
+        fee_input: &mut Option<TxInput>,
+    ) -> (Vec<TxInput>, Vec<TxOutput>) {
+        let atoms_vec = test_utils::split_value(rng, amount.into_atoms());
+        let mut result_inputs = Vec::new();
+        let mut result_outputs = Vec::new();
+
+        for atoms in atoms_vec {
+            if rng.gen::<bool>() {
+                // transfer
+                result_outputs.push(TxOutput::Transfer(
+                    OutputValue::TokenV1(token_id, Amount::from_atoms(atoms)),
+                    Destination::AnyoneCanSpend,
+                ));
+            } else if rng.gen_bool(0.9) {
+                // unmint
+                let token_data = tokens_cache.get_token_data(&token_id).unwrap();
+
+                // check token_data as well because it can be an nft
+                if let (Some(fee_tx_input), Some(token_data)) = (&fee_input, token_data) {
+                    let circulating_supply =
+                        tokens_cache.get_circulating_supply(&token_id).unwrap();
+                    assert!(circulating_supply.is_some());
+
+                    let tokens_accounting::TokenData::FungibleToken(token_data) = token_data;
+                    if !token_data.is_locked() {
+                        let to_unmint = Amount::from_atoms(atoms);
+
+                        let new_nonce = self.get_next_nonce(AccountType::TokenSupply(token_id));
+                        let account_input = TxInput::Account(AccountOutPoint::new(
+                            new_nonce,
+                            AccountOp::UnmintTokens(token_id),
+                        ));
+                        result_inputs.extend(vec![account_input, fee_tx_input.clone()]);
+
+                        let min_tx_fee =
+                            self.chainstate.get_chain_config().token_min_supply_change_fee();
+                        let outputs = vec![
+                            TxOutput::Burn(OutputValue::TokenV1(token_id, to_unmint)),
+                            TxOutput::Burn(OutputValue::Coin(min_tx_fee)),
+                        ];
+                        result_outputs.extend(outputs);
+
+                        let _ = tokens_cache.unmint_tokens(token_id, to_unmint).unwrap();
+                    }
+                    *fee_input = None;
+                }
+            } else {
+                // burn
+                result_outputs.push(TxOutput::Burn(OutputValue::TokenV1(
+                    token_id,
+                    Amount::from_atoms(atoms),
+                )))
+            }
+        }
+        (result_inputs, result_outputs)
+    }
+}
diff --git a/chainstate/test-framework/src/utils.rs b/chainstate/test-framework/src/utils.rs
index 9fe04cc1f2..1ea06f102b 100644
--- a/chainstate/test-framework/src/utils.rs
+++ b/chainstate/test-framework/src/utils.rs
@@ -39,11 +39,10 @@ use common::{
 };
 use crypto::{
     key::{PrivateKey, PublicKey},
-    random::{CryptoRng, Rng},
+    random::Rng,
     vrf::{VRFPrivateKey, VRFPublicKey},
 };
 use pos_accounting::{PoSAccountingDB, PoSAccountingView};
-use test_utils::nft_utils::*;
 
 pub fn empty_witness(rng: &mut impl Rng) -> InputWitness {
     use crypto::random::SliceRandom;
@@ -65,8 +64,10 @@ pub fn get_output_value(output: &TxOutput) -> Option<OutputValue> {
         | TxOutput::ProduceBlockFromStake(_, _)
         | TxOutput::CreateDelegationId(_, _)
         | TxOutput::DelegateStaking(_, _)
-        | TxOutput::IssueFungibleToken(_)
-        | TxOutput::IssueNft(_, _, _) => None,
+        | TxOutput::IssueFungibleToken(_) => None,
+        TxOutput::IssueNft(token_id, _, _) => {
+            Some(OutputValue::TokenV1(*token_id, Amount::from_atoms(1)))
+        }
     }
 }
 
@@ -141,134 +142,7 @@ pub fn create_utxo_data(
     }
 }
 
-/// Given an output as in input creates multiple new random outputs.
-pub fn create_multiple_utxo_data(
-    chainstate: &TestChainstate,
-    outsrc: OutPointSourceId,
-    index: usize,
-    output: &TxOutput,
-    rng: &mut (impl Rng + CryptoRng),
-) -> Option<(InputWitness, TxInput, Vec<TxOutput>)> {
-    let num_outputs = rng.gen_range(1..10);
-    let new_outputs = match get_output_value(output)? {
-        OutputValue::Coin(output_value) => {
-            let switch = rng.gen_range(0..3);
-            if switch == 0 {
-                // issue nft
-                let min_tx_fee = chainstate.get_chain_config().token_min_issuance_fee();
-                if output_value >= min_tx_fee {
-                    // Coin output is created intentionally besides issuance output in order to not waste utxo
-                    // (e.g. single genesis output on issuance)
-                    vec![
-                        TxOutput::Transfer(
-                            random_nft_issuance(chainstate.get_chain_config(), rng).into(),
-                            Destination::AnyoneCanSpend,
-                        ),
-                        TxOutput::Burn(OutputValue::Coin(min_tx_fee)),
-                    ]
-                } else {
-                    return None;
-                }
-            } else if switch == 1 {
-                // issue token
-                let min_tx_fee = chainstate.get_chain_config().token_min_issuance_fee();
-                if output_value >= min_tx_fee {
-                    // Coin output is created intentionally besides issuance output in order to not waste utxo
-                    // (e.g. single genesis output on issuance)
-                    vec![
-                        TxOutput::Transfer(
-                            random_token_issuance(chainstate.get_chain_config(), rng).into(),
-                            Destination::AnyoneCanSpend,
-                        ),
-                        TxOutput::Burn(OutputValue::Coin(min_tx_fee)),
-                    ]
-                } else {
-                    return None;
-                }
-            } else {
-                // spend the coin with multiple outputs
-                (0..num_outputs)
-                    .map(|_| {
-                        let new_value = Amount::from_atoms(output_value.into_atoms() / num_outputs);
-                        debug_assert!(new_value >= Amount::from_atoms(1));
-                        TxOutput::Transfer(OutputValue::Coin(new_value), anyonecanspend_address())
-                    })
-                    .collect()
-            }
-        }
-        OutputValue::TokenV0(token_data) => match &*token_data {
-            TokenData::TokenTransfer(transfer) => {
-                if rng.gen::<bool>() {
-                    // burn transferred tokens
-                    let amount_to_burn = if transfer.amount.into_atoms() > 1 {
-                        Amount::from_atoms(rng.gen_range(1..transfer.amount.into_atoms()))
-                    } else {
-                        transfer.amount
-                    };
-                    vec![TxOutput::Burn(
-                        TokenTransfer {
-                            token_id: transfer.token_id,
-                            amount: amount_to_burn,
-                        }
-                        .into(),
-                    )]
-                } else {
-                    // transfer tokens again
-                    if transfer.amount.into_atoms() >= num_outputs {
-                        // transfer with multiple outputs
-                        (0..num_outputs)
-                            .map(|_| {
-                                let amount =
-                                    Amount::from_atoms(transfer.amount.into_atoms() / num_outputs);
-                                TxOutput::Transfer(
-                                    TokenTransfer {
-                                        token_id: transfer.token_id,
-                                        amount,
-                                    }
-                                    .into(),
-                                    anyonecanspend_address(),
-                                )
-                            })
-                            .collect()
-                    } else {
-                        // transfer with a single output
-                        vec![TxOutput::Transfer(
-                            OutputValue::TokenV0(token_data),
-                            anyonecanspend_address(),
-                        )]
-                    }
-                }
-            }
-            TokenData::TokenIssuance(issuance) => {
-                if rng.gen::<bool>() {
-                    vec![new_token_burn_output(
-                        chainstate,
-                        &outsrc,
-                        Amount::from_atoms(rng.gen_range(1..issuance.amount_to_issue.into_atoms())),
-                    )]
-                } else {
-                    vec![new_token_transfer_output(chainstate, &outsrc, issuance.amount_to_issue)]
-                }
-            }
-            TokenData::NftIssuance(_issuance) => {
-                if rng.gen::<bool>() {
-                    vec![new_token_burn_output(chainstate, &outsrc, Amount::from_atoms(1))]
-                } else {
-                    vec![new_token_transfer_output(chainstate, &outsrc, Amount::from_atoms(1))]
-                }
-            }
-        },
-        OutputValue::TokenV1(_, _) => unimplemented!(),
-    };
-
-    Some((
-        empty_witness(rng),
-        TxInput::from_utxo(outsrc, index as u32),
-        new_outputs,
-    ))
-}
-
-fn new_token_transfer_output(
+pub fn new_token_transfer_output(
     chainstate: &TestChainstate,
     outsrc: &OutPointSourceId,
     amount: Amount,
@@ -290,27 +164,6 @@ fn new_token_transfer_output(
     )
 }
 
-fn new_token_burn_output(
-    chainstate: &TestChainstate,
-    outsrc: &OutPointSourceId,
-    amount_to_burn: Amount,
-) -> TxOutput {
-    TxOutput::Burn(
-        TokenTransfer {
-            token_id: match outsrc {
-                OutPointSourceId::Transaction(prev_tx) => {
-                    chainstate.get_token_id_from_issuance_tx(prev_tx).expect("ok").expect("some")
-                }
-                OutPointSourceId::BlockReward(_) => {
-                    panic!("cannot issue token in block reward")
-                }
-            },
-            amount: amount_to_burn,
-        }
-        .into(),
-    )
-}
-
 pub fn outputs_from_genesis(genesis: &Genesis) -> BlockOutputs {
     [(
         OutPointSourceId::BlockReward(genesis.get_id().into()),
diff --git a/chainstate/test-suite/src/tests/fungible_tokens_v1.rs b/chainstate/test-suite/src/tests/fungible_tokens_v1.rs
index b80bc34f39..92457737fd 100644
--- a/chainstate/test-suite/src/tests/fungible_tokens_v1.rs
+++ b/chainstate/test-suite/src/tests/fungible_tokens_v1.rs
@@ -2723,7 +2723,6 @@ fn reorg_test_2_tokens(#[case] seed: Seed) {
         );
         // No reorg
         assert_eq!(tf.best_block_id(), block_c_id);
-        println!("token1: {}, token2: {}", token_id_1, token_id_2);
 
         // Mint some tokens
         let (block_e_id, _) = mint_tokens_in_block(
diff --git a/chainstate/test-suite/src/tests/tx_verification_simulation.rs b/chainstate/test-suite/src/tests/tx_verification_simulation.rs
index 56b05912c3..7432aa6889 100644
--- a/chainstate/test-suite/src/tests/tx_verification_simulation.rs
+++ b/chainstate/test-suite/src/tests/tx_verification_simulation.rs
@@ -15,10 +15,12 @@
 
 use super::*;
 use chainstate_test_framework::TxVerificationStrategy;
+use common::chain::{tokens::TokenIssuanceVersion, ChainstateUpgrade, Destination, NetUpgrades};
 
 #[rstest]
 #[trace]
 #[case(Seed::from_entropy(), 20, 50, false)]
+#[trace]
 #[case(Seed::from_entropy(), 20, 50, true)]
 fn simulation(
     #[case] seed: Seed,
@@ -37,6 +39,18 @@ fn simulation(
                 max_tip_age: Default::default(),
             })
             .with_tx_verification_strategy(TxVerificationStrategy::Randomized(seed))
+            .with_chain_config(
+                common::chain::config::Builder::test_chain()
+                    .chainstate_upgrades(
+                        NetUpgrades::initialize(vec![(
+                            BlockHeight::zero(),
+                            ChainstateUpgrade::new(TokenIssuanceVersion::V1),
+                        )])
+                        .unwrap(),
+                    )
+                    .genesis_unittest(Destination::AnyoneCanSpend)
+                    .build(),
+            )
             .build();
 
         for _ in 0..rng.gen_range(10..max_blocks) {
@@ -45,6 +59,7 @@ fn simulation(
             for _ in 0..rng.gen_range(10..max_tx_per_block) {
                 block_builder = block_builder.add_test_transaction(&mut rng);
             }
+
             block_builder.build_and_process().unwrap().unwrap();
         }
     });
diff --git a/test-utils/src/nft_utils.rs b/test-utils/src/nft_utils.rs
index 7a752a1b56..6848ad829b 100644
--- a/test-utils/src/nft_utils.rs
+++ b/test-utils/src/nft_utils.rs
@@ -17,7 +17,11 @@ use crate::random_ascii_alphanumeric_string;
 use common::{
     chain::{
         config::ChainConfig,
-        tokens::{Metadata, NftIssuanceV0, TokenCreator, TokenIssuanceV0},
+        tokens::{
+            Metadata, NftIssuanceV0, TokenCreator, TokenIssuanceV0, TokenIssuanceV1,
+            TokenTotalSupply,
+        },
+        Destination,
     },
     primitives::Amount,
 };
@@ -43,6 +47,20 @@ pub fn random_token_issuance(chain_config: &ChainConfig, rng: &mut impl Rng) ->
     }
 }
 
+pub fn random_token_issuance_v1(chain_config: &ChainConfig, rng: &mut impl Rng) -> TokenIssuanceV1 {
+    let max_ticker_len = chain_config.token_max_ticker_len();
+    let max_dec_count = chain_config.token_max_dec_count();
+    let max_uri_len = chain_config.token_max_uri_len();
+
+    TokenIssuanceV1 {
+        token_ticker: random_ascii_alphanumeric_string(rng, 1..max_ticker_len).as_bytes().to_vec(),
+        number_of_decimals: rng.gen_range(1..max_dec_count),
+        metadata_uri: random_ascii_alphanumeric_string(rng, 1..max_uri_len).as_bytes().to_vec(),
+        total_supply: TokenTotalSupply::Lockable,
+        reissuance_controller: Destination::AnyoneCanSpend,
+    }
+}
+
 pub fn random_nft_issuance(
     chain_config: &ChainConfig,
     rng: &mut (impl Rng + CryptoRng),
diff --git a/tokens-accounting/src/data.rs b/tokens-accounting/src/data.rs
index 07487b358f..f2e5a9d195 100644
--- a/tokens-accounting/src/data.rs
+++ b/tokens-accounting/src/data.rs
@@ -33,6 +33,15 @@ pub struct TokensAccountingData {
     pub circulating_supply: BTreeMap<TokenId, Amount>,
 }
 
+impl TokensAccountingData {
+    pub fn new() -> Self {
+        Self {
+            token_data: BTreeMap::new(),
+            circulating_supply: BTreeMap::new(),
+        }
+    }
+}
+
 #[derive(Clone, Encode, Decode, Debug, PartialEq, Eq)]
 pub struct TokensAccountingDeltaData {
     pub(crate) token_data: DeltaDataCollection<TokenId, TokenData>,
diff --git a/tokens-accounting/src/storage/db.rs b/tokens-accounting/src/storage/db.rs
index d4a7812773..3f5e3dc843 100644
--- a/tokens-accounting/src/storage/db.rs
+++ b/tokens-accounting/src/storage/db.rs
@@ -64,7 +64,7 @@ impl<S: TokensAccountingStorageWrite> TokensAccountingDB<S> {
             })
             .collect::<Result<BTreeMap<_, _>, _>>()?;
 
-        let balance_undo = other
+        let circulating_supply_undo = other
             .circulating_supply
             .consume()
             .into_iter()
@@ -98,7 +98,7 @@ impl<S: TokensAccountingStorageWrite> TokensAccountingDB<S> {
 
         Ok(TokensAccountingDeltaUndoData {
             token_data: DeltaDataUndoCollection::from_data(data_undo),
-            circulating_supply: DeltaAmountCollection::from_iter(balance_undo),
+            circulating_supply: DeltaAmountCollection::from_iter(circulating_supply_undo),
         })
     }
 }
diff --git a/tokens-accounting/src/storage/in_memory.rs b/tokens-accounting/src/storage/in_memory.rs
index cd4e8c0883..18743129ba 100644
--- a/tokens-accounting/src/storage/in_memory.rs
+++ b/tokens-accounting/src/storage/in_memory.rs
@@ -45,6 +45,14 @@ impl InMemoryTokensAccounting {
             circulating_supply,
         }
     }
+
+    pub fn tokens_data(&self) -> &BTreeMap<TokenId, TokenData> {
+        &self.tokens_data
+    }
+
+    pub fn circulating_supply(&self) -> &BTreeMap<TokenId, Amount> {
+        &self.circulating_supply
+    }
 }
 
 impl TokensAccountingStorageRead for InMemoryTokensAccounting {