diff --git a/Cargo.lock b/Cargo.lock
index e6c8605dd5f08e407a3675a5390fe23d650f2e25..057d05b2854a8226f99a5ee248795bbd9d1e9a59 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1172,6 +1172,7 @@ dependencies = [
  "anyhow",
  "dubp",
  "duniter-dbs",
+ "maplit",
  "resiter",
  "smallvec",
 ]
@@ -1183,6 +1184,7 @@ dependencies = [
  "anyhow",
  "dubp",
  "duniter-dbs",
+ "maplit",
  "resiter",
  "smallvec",
 ]
diff --git a/rust-libs/duniter-dbs/src/databases/gva_v1.rs b/rust-libs/duniter-dbs/src/databases/gva_v1.rs
index bc9ab413f23301f7131d18288e444ee6e62989bd..4990d0053774ab0d5a49af0d8b0e9b875029b0b8 100644
--- a/rust-libs/duniter-dbs/src/databases/gva_v1.rs
+++ b/rust-libs/duniter-dbs/src/databases/gva_v1.rs
@@ -21,8 +21,8 @@ db_schema!(
         ["blocks_with_ud", BlocksWithUd, U32BE, ()],
         ["blockchain_time", BlockchainTime, U32BE, u64],
         ["txs", Txs, HashKeyV2, TxDbV2],
-        ["txs_by_issuer", TxsByIssuer, PubKeyKeyV2, Vec<Hash>],
-        ["txs_by_recipient", TxsByRecipient, PubKeyKeyV2, Vec<Hash>],
+        ["txs_by_issuer", TxsByIssuer, WalletHashWithBnV1Db, BTreeSet<Hash>],
+        ["txs_by_recipient", TxsByRecipient, WalletHashWithBnV1Db, BTreeSet<Hash>],
         [
             "scripts_by_pubkey",
             ScriptsByPubkey,
diff --git a/rust-libs/duniter-dbs/src/keys.rs b/rust-libs/duniter-dbs/src/keys.rs
index b5d2db16bd1451130a4bca39686bba24cfd933a1..6f6e1f1d3aa1a404072429e1ca82eaf3cb162ecb 100644
--- a/rust-libs/duniter-dbs/src/keys.rs
+++ b/rust-libs/duniter-dbs/src/keys.rs
@@ -26,3 +26,4 @@ pub mod ud_id;
 pub mod uid;
 pub mod utxo_id;
 pub mod wallet_conditions;
+pub mod wallet_hash_with_bn;
diff --git a/rust-libs/duniter-dbs/src/keys/wallet_hash_with_bn.rs b/rust-libs/duniter-dbs/src/keys/wallet_hash_with_bn.rs
new file mode 100644
index 0000000000000000000000000000000000000000..25b3d32d9398ab330799a3b9cd22ea7a740b5367
--- /dev/null
+++ b/rust-libs/duniter-dbs/src/keys/wallet_hash_with_bn.rs
@@ -0,0 +1,113 @@
+//  Copyright (C) 2020 Éloïs SANCHEZ.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as
+// published by the Free Software Foundation, either version 3 of the
+// License, or (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+use crate::*;
+
+use std::fmt::Display;
+use uninit::prelude::*;
+
+#[derive(
+    Debug, Copy, Clone, Hash, PartialEq, Eq, PartialOrd, Ord, zerocopy::AsBytes, zerocopy::FromBytes,
+)]
+#[repr(transparent)]
+pub struct WalletHashWithBnV1Db([u8; 36]); // wallet_hash ++ block_number
+
+impl WalletHashWithBnV1Db {
+    pub fn new(hash: Hash, block_number: BlockNumber) -> Self {
+        let mut buffer = uninit_array![u8; 36];
+        let (hash_buffer, bn_buffer) = buffer.as_out().split_at_out(32);
+
+        hash_buffer.copy_from_slice(hash.as_ref());
+        bn_buffer.copy_from_slice(&block_number.0.to_be_bytes()[..]);
+
+        Self(unsafe { std::mem::transmute(buffer) })
+    }
+    pub fn get_wallet_hash(&self) -> Hash {
+        let mut buffer = uninit_array![u8; 32];
+
+        buffer.as_out().copy_from_slice(&self.0[..32]);
+        let bytes: [u8; 32] = unsafe { std::mem::transmute(buffer) };
+
+        Hash(bytes)
+    }
+    pub fn get_block_number(&self) -> u32 {
+        let mut buffer = uninit_array![u8; 4];
+
+        buffer.as_out().copy_from_slice(&self.0[32..]);
+
+        u32::from_be_bytes(unsafe { std::mem::transmute(buffer) })
+    }
+}
+
+impl Default for WalletHashWithBnV1Db {
+    fn default() -> Self {
+        WalletHashWithBnV1Db([0u8; 36])
+    }
+}
+
+impl Display for WalletHashWithBnV1Db {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}:{}", self.get_wallet_hash(), self.get_block_number())
+    }
+}
+
+impl KeyAsBytes for WalletHashWithBnV1Db {
+    fn as_bytes<T, F: FnMut(&[u8]) -> T>(&self, mut f: F) -> T {
+        f(self.0.as_ref())
+    }
+}
+
+impl kv_typed::prelude::FromBytes for WalletHashWithBnV1Db {
+    type Err = StringErr;
+
+    fn from_bytes(bytes: &[u8]) -> std::result::Result<Self, Self::Err> {
+        let layout = zerocopy::LayoutVerified::<_, WalletHashWithBnV1Db>::new(bytes)
+            .ok_or_else(|| StringErr("corrupted db".to_owned()))?;
+        Ok(*layout)
+    }
+}
+
+impl KeyZc for WalletHashWithBnV1Db {
+    type Ref = Self;
+}
+
+impl ToDumpString for WalletHashWithBnV1Db {
+    fn to_dump_string(&self) -> String {
+        todo!()
+    }
+}
+
+#[cfg(feature = "explorer")]
+impl ExplorableKey for WalletHashWithBnV1Db {
+    fn from_explorer_str(source: &str) -> std::result::Result<Self, StringErr> {
+        let mut source = source.split(':');
+        let hash_str = source
+            .next()
+            .ok_or_else(|| StringErr("missing hash".to_owned()))?;
+        let bn_str = source
+            .next()
+            .ok_or_else(|| StringErr("missing block number".to_owned()))?;
+
+        let hash = Hash::from_hex(hash_str).map_err(|e| StringErr(e.to_string()))?;
+        let block_number = bn_str
+            .parse()
+            .map_err(|e: std::num::ParseIntError| StringErr(e.to_string()))?;
+
+        Ok(WalletHashWithBnV1Db::new(hash, block_number))
+    }
+    fn to_explorer_string(&self) -> KvResult<String> {
+        Ok(self.to_string())
+    }
+}
diff --git a/rust-libs/duniter-dbs/src/lib.rs b/rust-libs/duniter-dbs/src/lib.rs
index 480936e7a82d02fb0129d4b5ccb509f4fcc8dfda..d7f69fd1d48e9dc1b099cdb7e81248800df2ac1b 100644
--- a/rust-libs/duniter-dbs/src/lib.rs
+++ b/rust-libs/duniter-dbs/src/lib.rs
@@ -66,6 +66,7 @@ pub use keys::ud_id::UdIdV2;
 pub use keys::uid::UidKeyV1;
 pub use keys::utxo_id::GvaUtxoIdDbV1;
 pub use keys::wallet_conditions::{WalletConditionsV1, WalletConditionsV2};
+pub use keys::wallet_hash_with_bn::WalletHashWithBnV1Db;
 pub use values::block_db::{BlockDbEnum, BlockDbV1, TransactionInBlockDbV1};
 pub use values::block_head_db::BlockHeadDbV1;
 pub use values::block_meta::BlockMetaV2;
diff --git a/rust-libs/modules/gva/db-writer/Cargo.toml b/rust-libs/modules/gva/db-writer/Cargo.toml
index 801f2f15ba2534460de32e53870635094cdc4735..9c2e2ec0e114656626ce8d6d01ec5d2bfe675189 100644
--- a/rust-libs/modules/gva/db-writer/Cargo.toml
+++ b/rust-libs/modules/gva/db-writer/Cargo.toml
@@ -18,4 +18,6 @@ dubp = { version = "0.34.0" }
 resiter = "0.4.0"
 
 [dev-dependencies]
+duniter-dbs = { path = "../../../duniter-dbs", features = ["mem"] }
+maplit = "1.0.2"
 smallvec = { version = "1.4.0", features = ["serde", "write"] }
diff --git a/rust-libs/modules/gva/db-writer/src/lib.rs b/rust-libs/modules/gva/db-writer/src/lib.rs
index 2c6a309d8c035321f25827d15fbb84e2f2836e4c..36df7c5a8ad531ef27f7c51f478e1d5d3feca01a 100644
--- a/rust-libs/modules/gva/db-writer/src/lib.rs
+++ b/rust-libs/modules/gva/db-writer/src/lib.rs
@@ -40,7 +40,7 @@ use duniter_dbs::{
     HashKeyV2, PubKeyKeyV2, SourceAmountValV2, TxDbV2, WalletConditionsV2,
 };
 use resiter::filter::Filter;
-use std::collections::HashMap;
+use std::collections::{BTreeSet, HashMap};
 
 pub struct UtxoV10<'s> {
     pub id: UtxoIdV10,
@@ -188,6 +188,8 @@ fn apply_block_txs<B: Backend>(
     txs: &[TransactionDocumentV10],
 ) -> KvResult<()> {
     let mut scripts_index = HashMap::new();
+    let mut txs_by_issuer_mem = HashMap::new();
+    let mut txs_by_recipient_mem = HashMap::new();
     for tx in txs {
         let tx_hash = tx.get_hash();
         // Write tx and update sources
@@ -198,8 +200,23 @@ fn apply_block_txs<B: Backend>(
             &mut scripts_index,
             tx_hash,
             tx,
+            &mut txs_by_issuer_mem,
+            &mut txs_by_recipient_mem,
         )?;
     }
+    (
+        gva_db.txs_by_issuer_write(),
+        gva_db.txs_by_recipient_write(),
+    )
+        .write(|(mut txs_by_issuer, mut txs_by_recipient)| {
+            for (k, v) in txs_by_issuer_mem {
+                txs_by_issuer.upsert(k, v);
+            }
+            for (k, v) in txs_by_recipient_mem {
+                txs_by_recipient.upsert(k, v);
+            }
+            Ok(())
+        })?;
     Ok(())
 }
 
diff --git a/rust-libs/modules/gva/db-writer/src/tx.rs b/rust-libs/modules/gva/db-writer/src/tx.rs
index 2a5e1836b43eeb29bc569be27eb0d72fce322f17..ba0b62eb61068a862ef2d354723440913215526f 100644
--- a/rust-libs/modules/gva/db-writer/src/tx.rs
+++ b/rust-libs/modules/gva/db-writer/src/tx.rs
@@ -14,7 +14,7 @@
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 use crate::*;
-use duniter_dbs::databases::gva_v1::BalancesEvent;
+use duniter_dbs::{databases::gva_v1::BalancesEvent, WalletHashWithBnV1Db};
 
 pub(crate) type ScriptsHash = HashMap<WalletScriptV10, Hash>;
 
@@ -28,6 +28,7 @@ fn get_script_hash(script: &WalletScriptV10, scripts_hash: &mut ScriptsHash) ->
     }
 }
 
+#[allow(clippy::too_many_arguments)]
 pub(crate) fn apply_tx<B: Backend>(
     current_blockstamp: Blockstamp,
     current_time: i64,
@@ -35,43 +36,22 @@ pub(crate) fn apply_tx<B: Backend>(
     scripts_hash: &mut ScriptsHash,
     tx_hash: Hash,
     tx: &TransactionDocumentV10,
+    txs_by_issuer_mem: &mut HashMap<WalletHashWithBnV1Db, BTreeSet<Hash>>,
+    txs_by_recipient_mem: &mut HashMap<WalletHashWithBnV1Db, BTreeSet<Hash>>,
 ) -> KvResult<()> {
     (
         gva_db.scripts_by_pubkey_write(),
-        gva_db.txs_by_issuer_write(),
-        gva_db.txs_by_recipient_write(),
         gva_db.txs_write(),
         gva_db.gva_utxos_write(),
         gva_db.balances_write(),
     )
         .write(
-            |(
-                mut scripts_by_pubkey,
-                mut txs_by_issuer,
-                mut txs_by_recipient,
-                mut txs,
-                mut gva_utxos,
-                mut balances,
-            )| {
-                // Insert on col `txs_by_issuer`
-                for pubkey in tx.issuers() {
-                    let mut hashs = txs_by_issuer.get(&PubKeyKeyV2(pubkey))?.unwrap_or_default();
-                    hashs.push(tx_hash);
-                    txs_by_issuer.upsert(PubKeyKeyV2(pubkey), hashs);
-                }
-                // Insert on col `txs_by_recipient`
-                for pubkey in tx.recipients_keys() {
-                    let mut hashs = txs_by_recipient
-                        .get(&PubKeyKeyV2(pubkey))?
-                        .unwrap_or_default();
-                    hashs.push(tx_hash);
-                    txs_by_recipient.upsert(PubKeyKeyV2(pubkey), hashs);
-                }
-
-                // Remove consumed UTXOs
+            |(mut scripts_by_pubkey, mut txs, mut gva_utxos, mut balances)| {
+                let mut issuers_scripts_hashs = BTreeSet::new();
                 for input in tx.get_inputs() {
-                    let account_script = match input.id {
+                    let (account_script_hash, account_script) = match input.id {
                         SourceIdV10::Utxo(utxo_id) => {
+                            // Get issuer script & written block
                             let db_tx_origin = gva_db
                                 .txs()
                                 .get(&HashKeyV2::from_ref(&utxo_id.tx_hash))?
@@ -85,26 +65,42 @@ pub(crate) fn apply_tx<B: Backend>(
                                 .conditions
                                 .script
                                 .clone();
+                            let utxo_script_hash = get_script_hash(&utxo_script, scripts_hash);
+
+                            // Remove consumed UTXOs
                             super::utxos::remove_utxo_v10::<B>(
                                 &mut scripts_by_pubkey,
                                 &mut gva_utxos,
                                 utxo_id,
                                 &utxo_script,
-                                get_script_hash(&utxo_script, scripts_hash),
+                                utxo_script_hash,
                                 db_tx_origin.written_block.number.0,
                             )?;
-                            utxo_script
+
+                            // Return utxo_script with hash
+                            (utxo_script_hash, utxo_script)
                         }
                         SourceIdV10::Ud(UdSourceIdV10 { issuer, .. }) => {
-                            WalletScriptV10::single_sig(issuer)
+                            let script = WalletScriptV10::single_sig(issuer);
+                            (Hash::compute(script.to_string().as_bytes()), script)
                         }
                     };
+                    issuers_scripts_hashs.insert(account_script_hash);
+                    // Insert on col `txs_by_issuer`
+                    txs_by_issuer_mem
+                        .entry(WalletHashWithBnV1Db::new(
+                            account_script_hash,
+                            current_blockstamp.number,
+                        ))
+                        .or_default()
+                        .insert(tx_hash);
                     // Decrease account balance
                     decrease_account_balance::<B>(account_script, &mut balances, input.amount)?;
                 }
 
-                // Insert created UTXOs
                 for (output_index, output) in tx.get_outputs().iter().enumerate() {
+                    let utxo_script_hash = get_script_hash(&output.conditions.script, scripts_hash);
+                    // Insert created UTXOs
                     super::utxos::write_utxo_v10::<B>(
                         &mut scripts_by_pubkey,
                         &mut gva_utxos,
@@ -117,9 +113,20 @@ pub(crate) fn apply_tx<B: Backend>(
                             script: &output.conditions.script,
                             written_block: current_blockstamp.number,
                         },
-                        get_script_hash(&output.conditions.script, scripts_hash),
+                        utxo_script_hash,
                     )?;
 
+                    // Insert on col `txs_by_recipient`
+                    if !issuers_scripts_hashs.contains(&utxo_script_hash) {
+                        txs_by_recipient_mem
+                            .entry(WalletHashWithBnV1Db::new(
+                                utxo_script_hash,
+                                current_blockstamp.number,
+                            ))
+                            .or_default()
+                            .insert(tx_hash);
+                    }
+
                     // Increase account balance
                     let balance = balances
                         .get(WalletConditionsV2::from_ref(&output.conditions.script))?
@@ -171,10 +178,12 @@ pub(crate) fn revert_tx<B: Backend>(
                     mut gva_utxos,
                     mut balances,
                 )| {
-                    // Remove UTXOs created by this tx
                     use dubp::documents::transaction::TransactionDocumentTrait as _;
                     for (output_index, output) in tx_db.tx.get_outputs().iter().enumerate() {
                         let script = &output.conditions.script;
+                        let utxo_script_hash = get_script_hash(&script, scripts_hash);
+
+                        // Remove UTXOs created by this tx
                         super::utxos::remove_utxo_v10::<B>(
                             &mut scripts_by_pubkey,
                             &mut gva_utxos,
@@ -183,9 +192,14 @@ pub(crate) fn revert_tx<B: Backend>(
                                 output_index,
                             },
                             script,
-                            get_script_hash(&script, scripts_hash),
+                            utxo_script_hash,
                             block_number.0,
                         )?;
+
+                        // Remove on col `txs_by_recipient`
+                        txs_by_recipient
+                            .remove(WalletHashWithBnV1Db::new(utxo_script_hash, block_number));
+
                         // Decrease account balance
                         decrease_account_balance::<B>(
                             script.clone(),
@@ -195,7 +209,7 @@ pub(crate) fn revert_tx<B: Backend>(
                     }
                     // Recreate UTXOs consumed by this tx (and update balance)
                     for input in tx_db.tx.get_inputs() {
-                        let account_script = match input.id {
+                        let (account_script_hash, account_script) = match input.id {
                             SourceIdV10::Utxo(utxo_id) => {
                                 let db_tx_origin = gva_db
                                     .txs()
@@ -211,6 +225,7 @@ pub(crate) fn revert_tx<B: Backend>(
                                     .conditions
                                     .script
                                     .clone();
+                                let utxo_script_hash = get_script_hash(&utxo_script, scripts_hash);
                                 super::utxos::write_utxo_v10::<B>(
                                     &mut scripts_by_pubkey,
                                     &mut gva_utxos,
@@ -220,14 +235,20 @@ pub(crate) fn revert_tx<B: Backend>(
                                         script: &utxo_script,
                                         written_block: db_tx_origin.written_block.number,
                                     },
-                                    get_script_hash(&utxo_script, scripts_hash),
+                                    utxo_script_hash,
                                 )?;
-                                utxo_script
+
+                                // Return utxo_script
+                                (utxo_script_hash, utxo_script)
                             }
                             SourceIdV10::Ud(UdSourceIdV10 { issuer, .. }) => {
-                                WalletScriptV10::single_sig(issuer)
+                                let script = WalletScriptV10::single_sig(issuer);
+                                (Hash::compute(script.to_string().as_bytes()), script)
                             }
                         };
+                        // Remove on col `txs_by_issuer`
+                        txs_by_issuer
+                            .remove(WalletHashWithBnV1Db::new(account_script_hash, block_number));
                         // Increase account balance
                         let balance = balances
                             .get(WalletConditionsV2::from_ref(&account_script))?
@@ -238,14 +259,10 @@ pub(crate) fn revert_tx<B: Backend>(
                             SourceAmountValV2(balance.0 + input.amount),
                         );
                     }
-                    // Remove tx
-                    remove_tx::<B>(
-                        &mut txs_by_issuer,
-                        &mut txs_by_recipient,
-                        &mut txs,
-                        tx_hash,
-                        &tx_db,
-                    )?;
+
+                    // Remove tx itself
+                    txs.remove(HashKeyV2(*tx_hash));
+
                     Ok(())
                 },
             )?;
@@ -256,32 +273,6 @@ pub(crate) fn revert_tx<B: Backend>(
     }
 }
 
-fn remove_tx<B: Backend>(
-    txs_by_issuer: &mut TxColRw<B::Col, TxsByIssuerEvent>,
-    txs_by_recipient: &mut TxColRw<B::Col, TxsByRecipientEvent>,
-    txs: &mut TxColRw<B::Col, TxsEvent>,
-    tx_hash: &Hash,
-    tx_db: &TxDbV2,
-) -> KvResult<()> {
-    // Remove tx hash in col `txs_by_issuer`
-    for pubkey in tx_db.tx.issuers() {
-        let mut hashs_ = txs_by_issuer.get(&PubKeyKeyV2(pubkey))?.unwrap_or_default();
-        hashs_.pop();
-        txs_by_issuer.upsert(PubKeyKeyV2(pubkey), hashs_)
-    }
-    // Remove tx hash in col `txs_by_recipient`
-    for pubkey in tx_db.tx.recipients_keys() {
-        let mut hashs_ = txs_by_recipient
-            .get(&PubKeyKeyV2(pubkey))?
-            .unwrap_or_default();
-        hashs_.pop();
-        txs_by_recipient.upsert(PubKeyKeyV2(pubkey), hashs_)
-    }
-    // Remove tx itself
-    txs.remove(HashKeyV2(*tx_hash));
-    Ok(())
-}
-
 fn decrease_account_balance<B: Backend>(
     account_script: WalletScriptV10,
     balances: &mut TxColRw<B::Col, BalancesEvent>,
@@ -313,6 +304,7 @@ mod tests {
         documents::transaction::UTXOConditions,
     };
     use duniter_dbs::BlockMetaV2;
+    use maplit::btreeset;
 
     #[test]
     fn test_apply_tx() -> KvResult<()> {
@@ -336,6 +328,8 @@ mod tests {
         //println!("TMP pk2={}", pk2);
         let script = WalletScriptV10::single_sig(pk);
         let script2 = WalletScriptV10::single_sig(pk2);
+        let script_hash = Hash::compute(script.to_string().as_bytes());
+        let script2_hash = Hash::compute(script2.to_string().as_bytes());
 
         gva_db.balances_write().upsert(
             WalletConditionsV2(script.clone()),
@@ -372,6 +366,9 @@ mod tests {
         let tx1_hash = tx1.get_hash();
 
         let mut scripts_hash = HashMap::new();
+
+        let mut txs_by_issuer_mem = HashMap::new();
+        let mut txs_by_recipient_mem = HashMap::new();
         apply_tx(
             current_blockstamp,
             b0.median_time as i64,
@@ -379,8 +376,21 @@ mod tests {
             &mut scripts_hash,
             tx1_hash,
             &tx1,
+            &mut txs_by_issuer_mem,
+            &mut txs_by_recipient_mem,
         )?;
 
+        assert_eq!(txs_by_issuer_mem.len(), 1);
+        assert_eq!(
+            txs_by_issuer_mem.get(&WalletHashWithBnV1Db::new(script_hash, BlockNumber(0))),
+            Some(&btreeset![tx1_hash])
+        );
+        assert_eq!(txs_by_recipient_mem.len(), 1);
+        assert_eq!(
+            txs_by_recipient_mem.get(&WalletHashWithBnV1Db::new(script2_hash, BlockNumber(0))),
+            Some(&btreeset![tx1_hash])
+        );
+
         assert_eq!(
             gva_db
                 .balances()
@@ -417,6 +427,8 @@ mod tests {
         .build_and_sign(vec![kp.generate_signator()]);
         let tx2_hash = tx2.get_hash();
 
+        let mut txs_by_issuer_mem = HashMap::new();
+        let mut txs_by_recipient_mem = HashMap::new();
         apply_tx(
             current_blockstamp,
             b0.median_time as i64,
@@ -424,8 +436,21 @@ mod tests {
             &mut scripts_hash,
             tx2_hash,
             &tx2,
+            &mut txs_by_issuer_mem,
+            &mut txs_by_recipient_mem,
         )?;
 
+        assert_eq!(txs_by_issuer_mem.len(), 1);
+        assert_eq!(
+            txs_by_issuer_mem.get(&WalletHashWithBnV1Db::new(script2_hash, BlockNumber(0))),
+            Some(&btreeset![tx2_hash])
+        );
+        assert_eq!(txs_by_recipient_mem.len(), 1);
+        assert_eq!(
+            txs_by_recipient_mem.get(&WalletHashWithBnV1Db::new(script_hash, BlockNumber(0))),
+            Some(&btreeset![tx2_hash])
+        );
+
         assert_eq!(
             gva_db
                 .balances()
diff --git a/rust-libs/modules/gva/dbs-reader/Cargo.toml b/rust-libs/modules/gva/dbs-reader/Cargo.toml
index e83081ca3fc54e80515f9d1eba9c456a876358b9..b5ea2a14b3abaeec091bba8b2f7f9604d57658ce 100644
--- a/rust-libs/modules/gva/dbs-reader/Cargo.toml
+++ b/rust-libs/modules/gva/dbs-reader/Cargo.toml
@@ -19,4 +19,5 @@ resiter = "0.4.0"
 
 [dev-dependencies]
 duniter-dbs = { path = "../../../duniter-dbs", features = ["mem"] }
+maplit = "1.0.2"
 smallvec = { version = "1.4.0", features = ["serde", "write"] }
diff --git a/rust-libs/modules/gva/dbs-reader/src/lib.rs b/rust-libs/modules/gva/dbs-reader/src/lib.rs
index 499f78042c6b00dd4304cb02daf214d4b5fbc8df..ca54b7f07fb7b198f003705311ec3ef233771183 100644
--- a/rust-libs/modules/gva/dbs-reader/src/lib.rs
+++ b/rust-libs/modules/gva/dbs-reader/src/lib.rs
@@ -50,6 +50,7 @@ use duniter_dbs::{
 };
 use resiter::filter::Filter;
 use resiter::filter_map::FilterMap;
+use resiter::flatten::Flatten;
 use resiter::map::Map;
 use std::{collections::BTreeSet, str::FromStr};
 
diff --git a/rust-libs/modules/gva/dbs-reader/src/txs_history.rs b/rust-libs/modules/gva/dbs-reader/src/txs_history.rs
index b51a23ddc38c08ff0407d56af7a1348016759f4a..36adfbc1f88e1bee9f7da326dfb65d0491fcb5e6 100644
--- a/rust-libs/modules/gva/dbs-reader/src/txs_history.rs
+++ b/rust-libs/modules/gva/dbs-reader/src/txs_history.rs
@@ -15,6 +15,78 @@
 
 use crate::*;
 
+impl DbsReader {
+    pub fn get_txs_history_bc_received(&self, script_hash: Hash) -> KvResult<Vec<TxDbV2>> {
+        let start_k = WalletHashWithBnV1Db::new(script_hash, BlockNumber(0));
+        let end_k = WalletHashWithBnV1Db::new(script_hash, BlockNumber(u32::MAX));
+
+        self.0
+            .txs_by_recipient()
+            .iter_ref_slice(start_k..end_k, |_k, hashs| {
+                let mut sent = SmallVec::<[TxDbV2; 8]>::new();
+                for hash in hashs {
+                    if let Some(tx_db) = self.0.txs().get(HashKeyV2::from_ref(hash))? {
+                        sent.push(tx_db);
+                    }
+                }
+                Ok(sent)
+            })
+            .flatten_ok()
+            .collect::<KvResult<Vec<_>>>()
+    }
+    pub fn get_txs_history_bc_sent(&self, script_hash: Hash) -> KvResult<Vec<TxDbV2>> {
+        let start_k = WalletHashWithBnV1Db::new(script_hash, BlockNumber(0));
+        let end_k = WalletHashWithBnV1Db::new(script_hash, BlockNumber(u32::MAX));
+
+        self.0
+            .txs_by_issuer()
+            .iter_ref_slice(start_k..end_k, |_k, hashs| {
+                let mut sent = SmallVec::<[TxDbV2; 8]>::new();
+                for hash in hashs {
+                    if let Some(tx_db) = self.0.txs().get(HashKeyV2::from_ref(hash))? {
+                        sent.push(tx_db);
+                    }
+                }
+                Ok(sent)
+            })
+            .flatten_ok()
+            .collect::<KvResult<Vec<_>>>()
+    }
+    pub fn get_txs_history_mempool<TxsMpDb: TxsMpV2DbReadable>(
+        &self,
+        txs_mp_db_ro: &TxsMpDb,
+        pubkey: PublicKey,
+    ) -> KvResult<(Vec<TransactionDocumentV10>, Vec<TransactionDocumentV10>)> {
+        let sending = txs_mp_db_ro
+            .txs_by_issuer()
+            .get_ref_slice(&PubKeyKeyV2(pubkey), |hashs| {
+                let mut sent = Vec::with_capacity(hashs.len());
+                for hash in hashs {
+                    if let Some(tx_db) = txs_mp_db_ro.txs().get(HashKeyV2::from_ref(hash))? {
+                        sent.push(tx_db.0);
+                    }
+                }
+                Ok(sent)
+            })?
+            .unwrap_or_default();
+        let pending = txs_mp_db_ro
+            .txs_by_recipient()
+            .get_ref_slice(&PubKeyKeyV2(pubkey), |hashs| {
+                let mut pending = Vec::with_capacity(hashs.len());
+                for hash in hashs {
+                    if let Some(tx_db) = txs_mp_db_ro.txs().get(HashKeyV2::from_ref(hash))? {
+                        pending.push(tx_db.0);
+                    }
+                }
+                Ok(pending)
+            })?
+            .unwrap_or_default();
+        Ok((sending, pending))
+    }
+}
+use duniter_dbs::{smallvec::SmallVec, WalletHashWithBnV1Db};
+
+// Needed for BMA only
 pub struct TxsHistory {
     pub sent: Vec<TxDbV2>,
     pub received: Vec<TxDbV2>,
@@ -28,48 +100,37 @@ pub fn get_transactions_history_for_bma<GvaDb: GvaV1DbReadable, TxsMpDb: TxsMpV2
     txs_mp_db_ro: &TxsMpDb,
     pubkey: PublicKey,
 ) -> KvResult<TxsHistory> {
-    get_transactions_history_inner(gva_db_ro, txs_mp_db_ro, pubkey)
-}
-
-impl DbsReader {
-    pub fn get_transactions_history<TxsMpDb: TxsMpV2DbReadable>(
-        &self,
-        txs_mp_db_ro: &TxsMpDb,
-        pubkey: PublicKey,
-    ) -> KvResult<TxsHistory> {
-        get_transactions_history_inner(self.0, txs_mp_db_ro, pubkey)
-    }
-}
+    let script_hash = Hash::compute(WalletScriptV10::single_sig(pubkey).to_string().as_bytes());
+    let start_k = WalletHashWithBnV1Db::new(script_hash, BlockNumber(0));
+    let end_k = WalletHashWithBnV1Db::new(script_hash, BlockNumber(u32::MAX));
 
-fn get_transactions_history_inner<GvaDb: GvaV1DbReadable, TxsMpDb: TxsMpV2DbReadable>(
-    gva_db_ro: &GvaDb,
-    txs_mp_db_ro: &TxsMpDb,
-    pubkey: PublicKey,
-) -> KvResult<TxsHistory> {
     let sent = gva_db_ro
         .txs_by_issuer()
-        .get_ref_slice(&PubKeyKeyV2(pubkey), |hashs| {
-            let mut sent = Vec::with_capacity(hashs.len());
+        .iter_ref_slice(start_k..end_k, |_k, hashs| {
+            let mut sent = SmallVec::<[TxDbV2; 2]>::new();
             for hash in hashs {
                 if let Some(tx_db) = gva_db_ro.txs().get(HashKeyV2::from_ref(hash))? {
                     sent.push(tx_db);
                 }
             }
             Ok(sent)
-        })?
-        .unwrap_or_default();
+        })
+        .flatten_ok()
+        .collect::<KvResult<Vec<_>>>()?;
+
     let received = gva_db_ro
         .txs_by_recipient()
-        .get_ref_slice(&PubKeyKeyV2(pubkey), |hashs| {
-            let mut received = Vec::with_capacity(hashs.len());
+        .iter_ref_slice(start_k..end_k, |_k, hashs| {
+            let mut sent = SmallVec::<[TxDbV2; 2]>::new();
             for hash in hashs {
                 if let Some(tx_db) = gva_db_ro.txs().get(HashKeyV2::from_ref(hash))? {
-                    received.push(tx_db);
+                    sent.push(tx_db);
                 }
             }
-            Ok(received)
-        })?
-        .unwrap_or_default();
+            Ok(sent)
+        })
+        .flatten_ok()
+        .collect::<KvResult<Vec<_>>>()?;
     let sending = txs_mp_db_ro
         .txs_by_issuer()
         .get_ref_slice(&PubKeyKeyV2(pubkey), |hashs| {
@@ -101,3 +162,35 @@ fn get_transactions_history_inner<GvaDb: GvaV1DbReadable, TxsMpDb: TxsMpV2DbRead
         pending,
     })
 }
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use dubp::crypto::keys::ed25519::PublicKey;
+    use duniter_dbs::databases::gva_v1::GvaV1DbWritable;
+    use maplit::btreeset;
+
+    #[test]
+    fn test_get_txs_history_bc() -> KvResult<()> {
+        let gva_db = duniter_dbs::databases::gva_v1::GvaV1Db::<Mem>::open(MemConf::default())?;
+        let db_reader = create_dbs_reader(unsafe { std::mem::transmute(&gva_db.get_ro_handler()) });
+
+        let s1 = WalletScriptV10::single_sig(PublicKey::default());
+        let s1_hash = Hash::compute_str(&s1.to_string());
+
+        gva_db
+            .txs_write()
+            .upsert(HashKeyV2(Hash::default()), TxDbV2::default())?;
+        gva_db.txs_by_issuer_write().upsert(
+            WalletHashWithBnV1Db::new(s1_hash, BlockNumber(0)),
+            btreeset![Hash::default()],
+        )?;
+
+        let _received = db_reader.get_txs_history_bc_received(s1_hash)?;
+        let sent = db_reader.get_txs_history_bc_sent(s1_hash)?;
+
+        assert_eq!(sent, vec![TxDbV2::default()]);
+
+        Ok(())
+    }
+}
diff --git a/rust-libs/modules/gva/src/entities.rs b/rust-libs/modules/gva/src/entities.rs
index 5fe069ae06ffb48be87d37f21360394dd7478fb9..0ab62d77e3c1d3244131a0dab05f8586b0768416 100644
--- a/rust-libs/modules/gva/src/entities.rs
+++ b/rust-libs/modules/gva/src/entities.rs
@@ -85,17 +85,21 @@ pub(crate) struct Sum {
 }
 
 #[derive(async_graphql::SimpleObject)]
-pub(crate) struct TxsHistoryGva {
-    /// Transactions sent
-    pub(crate) sent: Vec<TxGva>,
+pub(crate) struct TxsHistoryMempool {
     /// Transactions sending
     pub(crate) sending: Vec<TxGva>,
-    /// Transactions received
-    pub(crate) received: Vec<TxGva>,
     /// Transactions receiving
     pub(crate) receiving: Vec<TxGva>,
 }
 
+#[derive(async_graphql::SimpleObject)]
+pub(crate) struct TxsHistoryBlockchain {
+    /// Transactions sent
+    pub(crate) sent: Vec<TxGva>,
+    /// Transactions received
+    pub(crate) received: Vec<TxGva>,
+}
+
 #[derive(Clone, async_graphql::SimpleObject)]
 pub(crate) struct UtxoGva {
     /// Source amount
diff --git a/rust-libs/modules/gva/src/lib.rs b/rust-libs/modules/gva/src/lib.rs
index 9d868bf46450e81b0073b87ceb1e917ef578c1e1..6a6767261fc60ae1a8d9679308d12bb552ee683d 100644
--- a/rust-libs/modules/gva/src/lib.rs
+++ b/rust-libs/modules/gva/src/lib.rs
@@ -39,7 +39,8 @@ use crate::entities::{
     block_gva::Block,
     tx_gva::TxGva,
     ud_gva::{CurrentUdGva, RevalUdGva, UdGva},
-    AggregateSum, AmountWithBase, PeerCardGva, RawTxOrChanges, Sum, TxsHistoryGva, UtxoGva,
+    AggregateSum, AmountWithBase, PeerCardGva, RawTxOrChanges, Sum, TxsHistoryBlockchain,
+    TxsHistoryMempool, UtxoGva,
 };
 use crate::inputs::{TxIssuer, TxRecipient, UdsFilter};
 use crate::inputs_validators::TxCommentValidator;
@@ -454,11 +455,19 @@ mod tests {
                 &self,
                 bc_db: &BcDb,
             ) -> KvResult<Option<SourceAmount>>;
-            fn get_transactions_history<TxsMpDb: 'static + TxsMpV2DbReadable>(
+            fn get_txs_history_bc_received(
+                &self,
+                script_hash: Hash,
+            ) -> KvResult<Vec<TxDbV2>>;
+            fn get_txs_history_bc_sent(
+                &self,
+                script_hash: Hash,
+            ) -> KvResult<Vec<TxDbV2>>;
+            fn get_txs_history_mempool<TxsMpDb: 'static + TxsMpV2DbReadable>(
                 &self,
                 txs_mp_db_ro: &TxsMpDb,
                 pubkey: PublicKey,
-            ) -> KvResult<duniter_gva_dbs_reader::txs_history::TxsHistory>;
+            ) -> KvResult<(Vec<TransactionDocumentV10>, Vec<TransactionDocumentV10>)>;
             fn unspent_uds_of_pubkey<BcDb: 'static + BcV2DbReadable>(
                 &self,
                 bc_db: &BcDb,
diff --git a/rust-libs/modules/gva/src/queries.rs b/rust-libs/modules/gva/src/queries.rs
index 6427b25cad081d65a2f1bd487a6d0a685b2e6596..d6fd28551fda9cd6c7a3b14176326f3294e5cba5 100644
--- a/rust-libs/modules/gva/src/queries.rs
+++ b/rust-libs/modules/gva/src/queries.rs
@@ -29,7 +29,8 @@ pub struct QueryRoot(
     queries::account_balance::AccountBalanceQuery,
     queries::current_frame::CurrentFrameQuery,
     queries::gen_tx::GenTxsQuery,
-    queries::txs_history::TxsHistoryQuery,
+    queries::txs_history::TxsHistoryBlockchainQuery,
+    queries::txs_history::TxsHistoryMempoolQuery,
     queries::uds::UdsQuery,
     queries::utxos_of_script::UtxosQuery,
 );
diff --git a/rust-libs/modules/gva/src/queries/txs_history.rs b/rust-libs/modules/gva/src/queries/txs_history.rs
index ca1f714e8f8c35bb37102fab112ce4a24964f493..59ca16b5e2e946c5b923463c965e6f76df53e92c 100644
--- a/rust-libs/modules/gva/src/queries/txs_history.rs
+++ b/rust-libs/modules/gva/src/queries/txs_history.rs
@@ -14,48 +14,182 @@
 // along with this program.  If not, see <https://www.gnu.org/licenses/>.
 
 use crate::*;
+use dubp::documents_parser::wallet_script_from_str;
+use futures::future::join;
 
 #[derive(Default)]
-pub(crate) struct TxsHistoryQuery;
+pub(crate) struct TxsHistoryBlockchainQuery;
+
+#[async_graphql::Object]
+impl TxsHistoryBlockchainQuery {
+    /// Transactions history (written in blockchain)
+    async fn txs_history_bc(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+        #[graphql(desc = "Ed25519 public key on base 58 representation or DUBP script")]
+        pubkey_or_script: String,
+    ) -> async_graphql::Result<TxsHistoryBlockchain> {
+        let start_time = std::time::Instant::now();
+        let script = if let Ok(pubkey) = PublicKey::from_base58(&pubkey_or_script) {
+            WalletScriptV10::single_sig(pubkey)
+        } else {
+            wallet_script_from_str(&pubkey_or_script)?
+        };
+        let script_hash = Hash::compute(script.to_string().as_bytes());
+
+        let data = ctx.data::<SchemaData>()?;
+
+        let (sent, received) = if ctx.look_ahead().field("sent").exists() {
+            let db_reader = data.dbs_reader();
+            let sent_fut = data
+                .dbs_pool
+                .execute(move |_| db_reader.get_txs_history_bc_sent(script_hash));
+            if ctx.look_ahead().field("received").exists() {
+                let db_reader = data.dbs_reader();
+                let received_fut = data
+                    .dbs_pool
+                    .execute(move |_| db_reader.get_txs_history_bc_received(script_hash));
+                let (sent_res, received_res) = join(sent_fut, received_fut).await;
+                (sent_res??, received_res??)
+            } else {
+                let db_reader = data.dbs_reader();
+                (
+                    data.dbs_pool
+                        .execute(move |_| db_reader.get_txs_history_bc_sent(script_hash))
+                        .await??,
+                    vec![],
+                )
+            }
+        } else if ctx.look_ahead().field("received").exists() {
+            let db_reader = data.dbs_reader();
+            (
+                vec![],
+                data.dbs_pool
+                    .execute(move |_| db_reader.get_txs_history_bc_received(script_hash))
+                    .await??,
+            )
+        } else {
+            (vec![], vec![])
+        };
+
+        println!(
+            "txs_history_bc duration: {}ms",
+            start_time.elapsed().as_millis()
+        );
+
+        Ok(TxsHistoryBlockchain {
+            sent: sent.into_iter().map(|db_tx| db_tx.into()).collect(),
+            received: received.into_iter().map(|db_tx| db_tx.into()).collect(),
+        })
+    }
+}
+
+#[derive(Default)]
+pub(crate) struct TxsHistoryMempoolQuery;
+
 #[async_graphql::Object]
-impl TxsHistoryQuery {
-    /// Transactions history
-    async fn transactions_history(
+impl TxsHistoryMempoolQuery {
+    /// Transactions waiting on mempool
+    async fn txs_history_mp(
         &self,
         ctx: &async_graphql::Context<'_>,
         #[graphql(desc = "Ed25519 public key on base 58 representation")] pubkey: String,
-    ) -> async_graphql::Result<TxsHistoryGva> {
+    ) -> async_graphql::Result<TxsHistoryMempool> {
         let pubkey = PublicKey::from_base58(&pubkey)?;
 
         let data = ctx.data::<SchemaData>()?;
         let db_reader = data.dbs_reader();
 
-        let txs_history = data
+        let (sending, pending) = data
             .dbs_pool
-            .execute(move |dbs| db_reader.get_transactions_history(&dbs.txs_mp_db, pubkey))
+            .execute(move |dbs| db_reader.get_txs_history_mempool(&dbs.txs_mp_db, pubkey))
             .await??;
 
-        Ok(TxsHistoryGva {
-            sent: txs_history
-                .sent
-                .into_iter()
-                .map(|db_tx| db_tx.into())
-                .collect(),
-            sending: txs_history
-                .sending
+        Ok(TxsHistoryMempool {
+            sending: sending
                 .into_iter()
                 .map(|db_tx| TxGva::from(&db_tx))
                 .collect(),
-            received: txs_history
-                .received
-                .into_iter()
-                .map(|db_tx| db_tx.into())
-                .collect(),
-            receiving: txs_history
-                .pending
+            receiving: pending
                 .into_iter()
                 .map(|db_tx| TxGva::from(&db_tx))
                 .collect(),
         })
     }
 }
+
+#[cfg(test)]
+mod tests {
+    use dubp::documents::transaction::TransactionDocumentV10;
+    use dubp::documents::transaction::TransactionDocumentV10Stringified;
+    use dubp::documents_parser::prelude::FromStringObject;
+    use duniter_dbs::TxDbV2;
+
+    use crate::tests::*;
+
+    #[tokio::test]
+    async fn test_txs_history_blockchain() -> anyhow::Result<()> {
+        let mut dbs_reader = MockDbsReader::new();
+        dbs_reader
+            .expect_get_txs_history_bc_received()
+            .times(1)
+            .returning(|_| Ok(vec![]));
+        dbs_reader
+            .expect_get_txs_history_bc_sent()
+            .times(1)
+            .returning(|_| {
+                let tx = TransactionDocumentV10::from_string_object(
+                    &TransactionDocumentV10Stringified {
+                        currency: "test".to_owned(),
+                        blockstamp:
+                            "0-0000000000000000000000000000000000000000000000000000000000000000"
+                                .to_owned(),
+                        locktime: 0,
+                        issuers: vec![],
+                        inputs: vec![],
+                        unlocks: vec![],
+                        outputs: vec![],
+                        comment: "".to_owned(),
+                        signatures: vec![],
+                        hash: Some(
+                            "0000000000000000000000000000000000000000000000000000000000000000"
+                                .to_owned(),
+                        ),
+                    },
+                )
+                .expect("wrong tx");
+                Ok(vec![TxDbV2 {
+                    tx,
+                    ..Default::default()
+                }])
+            });
+        let schema = create_schema(dbs_reader)?;
+        assert_eq!(
+            exec_graphql_request(
+                &schema,
+                r#"{
+                txsHistoryBc(pubkeyOrScript: "D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx") {
+                    sent {
+                        blockstamp
+                    }
+                    received {
+                        blockstamp
+                    }
+                }
+              }"#
+            )
+            .await?,
+            serde_json::json!({
+                "data": {
+                    "txsHistoryBc": {
+                        "received": [],
+                        "sent": [{
+                            "blockstamp": "0-0000000000000000000000000000000000000000000000000000000000000000",
+                        }]
+                    }
+                  }
+            })
+        );
+        Ok(())
+    }
+}
diff --git a/rust-libs/modules/gva/src/schema.rs b/rust-libs/modules/gva/src/schema.rs
index 3ea5213d24ff72788b5f1fcbeecd5f2539ecbdf9..ca00003b487b4f9e3361e63c09dfd75eb4817bf7 100644
--- a/rust-libs/modules/gva/src/schema.rs
+++ b/rust-libs/modules/gva/src/schema.rs
@@ -29,6 +29,7 @@ pub(crate) struct SchemaData {
 
 #[cfg(not(test))]
 impl SchemaData {
+    #[inline(always)]
     pub fn dbs_reader(&self) -> DbsReader {
         self.dbs_reader
     }
diff --git a/rust-libs/tools/kv_typed/src/from_bytes.rs b/rust-libs/tools/kv_typed/src/from_bytes.rs
index 5bfe507254a7a440e37af53944ffda9b434884f3..c3c39541a52389f08244b8fdac2b3e7bb567abbf 100644
--- a/rust-libs/tools/kv_typed/src/from_bytes.rs
+++ b/rust-libs/tools/kv_typed/src/from_bytes.rs
@@ -99,7 +99,7 @@ where
 
     fn from_bytes(bytes: &[u8]) -> Result<Self, Self::Err> {
         let layout_verified = zerocopy::LayoutVerified::<_, [T]>::new_slice(bytes)
-            .ok_or_else(|| StringErr("".to_owned()))?;
+            .ok_or_else(|| StringErr("Bytes are invalid length or alignment.".to_owned()))?;
         let slice = layout_verified.into_slice();
         Ok(BTreeSet::from_iter(slice.iter().copied()))
     }