diff --git a/db/src/lib.rs b/db/src/lib.rs
index a8f2e073d3f87cdfd84bc170ecb877137c24cead..49a01194e6f43d75dd70d2e8e1cccf096d21e237 100644
--- a/db/src/lib.rs
+++ b/db/src/lib.rs
@@ -41,8 +41,8 @@ pub(crate) use duniter_core::dbs::kv_typed::db_schema;
 pub(crate) use duniter_core::dbs::kv_typed::prelude::*;
 pub(crate) use duniter_core::dbs::smallvec::SmallVec;
 pub(crate) use duniter_core::dbs::{
-    bincode_db, CorruptedBytes, HashKeyV2, PubKeyKeyV2, SourceAmountValV2, ToDumpString,
-    WalletConditionsV2,
+    bincode_db, CorruptedBytes, HashKeyV2, PubKeyKeyV2, PubKeyValV2, SourceAmountValV2,
+    ToDumpString, WalletConditionsV2,
 };
 pub(crate) use duniter_core::wallet::prelude::*;
 pub(crate) use duniter_core::wot::WotId;
@@ -67,6 +67,7 @@ db_schema!(
             GvaUtxoIdDbV1,
             SourceAmountValV2
         ],
+        ["idty_by_username", IdtyByUsername, String, PubKeyValV2],
         [
             "scripts_by_pubkey",
             ScriptsByPubkey,
diff --git a/dbs-reader/src/lib.rs b/dbs-reader/src/lib.rs
index 8372372ad4687c247d165b1d98dc9302569add7d..900dec5eb957883e4e35d155e566361b47100cc3 100644
--- a/dbs-reader/src/lib.rs
+++ b/dbs-reader/src/lib.rs
@@ -159,6 +159,11 @@ pub trait DbsReader {
         bc_db: &BcV2DbRo<FileBackend>,
         pubkey: PublicKey,
     ) -> KvResult<Option<duniter_core::dbs::IdtyDbV2>>;
+    fn idty_by_username(
+        &self,
+        bc_db: &BcV2DbRo<FileBackend>,
+        username: String,
+    ) -> KvResult<Option<(PublicKey, duniter_core::dbs::IdtyDbV2)>>;
     fn peers_and_heads<DB: 'static + NetworkV1DbReadable>(
         &self,
         dunp_db: &DB,
@@ -353,6 +358,21 @@ impl DbsReader for DbsReaderImpl {
         self.idty_(bc_db, pubkey)
     }
 
+    fn idty_by_username(
+        &self,
+        bc_db: &BcV2DbRo<FileBackend>,
+        username: String,
+    ) -> KvResult<Option<(PublicKey, duniter_core::dbs::IdtyDbV2)>> {
+        self.0
+            .idty_by_username()
+            .get(&username)?
+            .map_or(Ok(None), |pubkey| match self.idty_(bc_db, pubkey.0) {
+                Ok(Some(idty)) => Ok(Some((pubkey.0, idty))),
+                Ok(None) => Ok(None),
+                Err(e) => Err(e),
+            })
+    }
+
     fn peers_and_heads<DB: 'static + NetworkV1DbReadable>(
         &self,
         dunp_db: &DB,
diff --git a/gql/src/entities/idty_gva.rs b/gql/src/entities/idty_gva.rs
index 8729895ac809679fe4b34fa6966b1e0305fb4a27..4639a76a8241ca9131d303b7b1fec18dbaa2a7a6 100644
--- a/gql/src/entities/idty_gva.rs
+++ b/gql/src/entities/idty_gva.rs
@@ -13,8 +13,11 @@
 // 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::*;
+
 #[derive(Clone, Debug, async_graphql::SimpleObject)]
 pub(crate) struct Identity {
     pub is_member: bool,
+    pub pubkey: PubKeyGva,
     pub username: String,
 }
diff --git a/gql/src/queries/idty.rs b/gql/src/queries/idty.rs
index 65b43b1b4db4d730e80a34894339f7c6d4920127..cc1cc348b64f850cbbbaca7837a306ecf58b8f31 100644
--- a/gql/src/queries/idty.rs
+++ b/gql/src/queries/idty.rs
@@ -34,6 +34,27 @@ impl IdtyQuery {
             .await??
             .map(|idty| Identity {
                 is_member: idty.is_member,
+                pubkey,
+                username: idty.username,
+            }))
+    }
+
+    /// Get identity by username
+    async fn idty_by_username(
+        &self,
+        ctx: &async_graphql::Context<'_>,
+        #[graphql(desc = "username")] username: String,
+    ) -> async_graphql::Result<Option<Identity>> {
+        let data = ctx.data::<GvaSchemaData>()?;
+        let dbs_reader = data.dbs_reader();
+
+        Ok(data
+            .dbs_pool
+            .execute(move |dbs| dbs_reader.idty_by_username(&dbs.bc_db_ro, username))
+            .await??
+            .map(|(pubkey, idty)| Identity {
+                is_member: idty.is_member,
+                pubkey: PubKeyGva(pubkey),
                 username: idty.username,
             }))
     }
@@ -79,4 +100,41 @@ mod tests {
         );
         Ok(())
     }
+
+    #[tokio::test]
+    async fn test_idty_by_username() -> anyhow::Result<()> {
+        let mut dbs_reader = MockDbsReader::new();
+        dbs_reader
+            .expect_idty_by_username()
+            .withf(|_, s| s == &String::from("JohnDoe"))
+            .times(1)
+            .returning(|_, _| {
+                Ok(Some((
+                    PublicKey::from_base58("DnjL6hYA1k7FavGHbbir79PKQbmzw63d6bsamBBdUULP")
+                        .expect("wrong pubkey"),
+                    duniter_core::dbs::IdtyDbV2 {
+                        is_member: true,
+                        username: String::from("JohnDoe"),
+                        ..Default::default()
+                    },
+                )))
+            });
+        let schema = create_schema(MockAsyncAccessor::new(), dbs_reader)?;
+        assert_eq!(
+            exec_graphql_request(
+                &schema,
+                r#"{ idtyByUsername(username: "JohnDoe") {isMember, pubkey} }"#
+            )
+            .await?,
+            serde_json::json!({
+                "data": {
+                    "idtyByUsername": {
+                        "isMember": true,
+                        "pubkey": "DnjL6hYA1k7FavGHbbir79PKQbmzw63d6bsamBBdUULP"
+                    }
+                }
+            })
+        );
+        Ok(())
+    }
 }
diff --git a/gql/src/queries/wallets.rs b/gql/src/queries/wallets.rs
index aa3636b84419f977de92cff4f243caabd13fda33..2424b8426f9967c9ad4ab5c6acfff975d616d4c2 100644
--- a/gql/src/queries/wallets.rs
+++ b/gql/src/queries/wallets.rs
@@ -91,6 +91,7 @@ impl WalletsQuery {
                                     balance: AmountWithBase::from(pk_with_sa.1),
                                     idty: idty_opt.map(|idty_db| Identity {
                                         is_member: idty_db.is_member,
+                                        pubkey: PubKeyGva(pk_with_sa.0),
                                         username: idty_db.username,
                                     }),
                                 })
@@ -136,10 +137,17 @@ impl WalletsQuery {
                                 .map(|WalletWithIdtyOpt(script_with_sa, idty_opt)| Wallet {
                                     script: script_with_sa.0.to_string(),
                                     balance: AmountWithBase::from(script_with_sa.1),
-                                    idty: idty_opt.map(|idty_db| Identity {
-                                        is_member: idty_db.is_member,
-                                        username: idty_db.username,
-                                    }),
+                                    idty: if let (Some(idty_db), Some(pubkey)) =
+                                        (idty_opt, script_with_sa.0.as_single_sig())
+                                    {
+                                        Some(Identity {
+                                            is_member: idty_db.is_member,
+                                            pubkey: PubKeyGva(pubkey),
+                                            username: idty_db.username,
+                                        })
+                                    } else {
+                                        None
+                                    },
                                 })
                                 .collect()
                         })
diff --git a/indexer/src/identities.rs b/indexer/src/identities.rs
index 2360c7132ea9978c66b60d0ce669f60f7e232e23..e893e9eca56dc682f6fd00f77adae5af0e3c32c6 100644
--- a/indexer/src/identities.rs
+++ b/indexer/src/identities.rs
@@ -18,6 +18,7 @@ use crate::*;
 pub(crate) fn update_identities<B: Backend>(
     block: &DubpBlockV10,
     identities: &mut TxColRw<B::Col, GvaIdentitiesEvent>,
+    idty_by_username: &mut TxColRw<B::Col, IdtyByUsernameEvent>,
 ) -> KvResult<()> {
     let mut identities_pubkeys = HashSet::new();
     for idty in block.identities() {
@@ -35,6 +36,7 @@ pub(crate) fn update_identities<B: Backend>(
                 wot_id,
             },
         );
+        idty_by_username.upsert(idty.username().into(), PubKeyValV2(pubkey));
     }
     for mb in block.joiners() {
         let pubkey = mb.issuers()[0];
@@ -70,6 +72,7 @@ pub(crate) fn update_identities<B: Backend>(
 pub(crate) fn revert_identities<B: Backend>(
     block: &DubpBlockV10,
     identities: &mut TxColRw<B::Col, GvaIdentitiesEvent>,
+    idty_by_username: &mut TxColRw<B::Col, IdtyByUsernameEvent>,
 ) -> KvResult<()> {
     for mb in block.joiners() {
         let pubkey = mb.issuers()[0];
@@ -83,6 +86,7 @@ pub(crate) fn revert_identities<B: Backend>(
     }
     for idty in block.identities() {
         let pubkey = idty.issuers()[0];
+        idty_by_username.remove(idty.username().into());
         identities.remove(PubKeyKeyV2(pubkey));
         crate::revert_wot_id();
     }
diff --git a/indexer/src/lib.rs b/indexer/src/lib.rs
index b7fa23aba79fbc4dd9071e7bf5f9275fb439573b..b1451972ef93f679bee4c204dae5a9234ef091f5 100644
--- a/indexer/src/lib.rs
+++ b/indexer/src/lib.rs
@@ -41,7 +41,7 @@ use duniter_core::{
 use duniter_core::{
     dbs::{
         bincode_db, kv_typed::prelude::*, prelude::*, smallvec::smallvec, FileBackend, HashKeyV2,
-        PubKeyKeyV2, SourceAmountValV2, WalletConditionsV2,
+        PubKeyKeyV2, PubKeyValV2, SourceAmountValV2, WalletConditionsV2,
     },
     wot::{WebOfTrust, WotId, MAIN_WOT},
 };
@@ -126,7 +126,11 @@ pub fn apply_block<B: Backend>(
             .upsert(U64BE(block.common_time()), block.number().0);
         db.blockchain_time
             .upsert(U32BE(block.number().0), block.common_time());
-        identities::update_identities::<B>(&block, &mut db.gva_identities)?;
+        identities::update_identities::<B>(
+            &block,
+            &mut db.gva_identities,
+            &mut db.idty_by_username,
+        )?;
         if let Some(divident_amount) = block.dividend() {
             db.blocks_with_ud.upsert(U32BE(blockstamp.number.0), ());
             apply_ud::<B>(
@@ -153,7 +157,11 @@ pub fn revert_block<B: Backend>(
     gva_db.write(|mut db| {
         db.blocks_by_common_time.remove(U64BE(block.common_time()));
         db.blockchain_time.remove(U32BE(block.number().0));
-        identities::revert_identities::<B>(&block, &mut db.gva_identities)?;
+        identities::revert_identities::<B>(
+            &block,
+            &mut db.gva_identities,
+            &mut db.idty_by_username,
+        )?;
         if let Some(divident_amount) = block.dividend() {
             db.blocks_with_ud.remove(U32BE(block.number().0));
             revert_ud::<B>(
@@ -331,6 +339,12 @@ mod tests {
             Some(SourceAmountValV2(SourceAmount::with_base0(1000)))
         );
         assert_eq!(gva_db.txs_by_block().count()?, 0);
+        assert_eq!(
+            gva_db.idty_by_username().get(&String::from("elois"))?,
+            Some(PubKeyValV2(PublicKey::from_base58(
+                "D9D2zaJoWYWveii1JRYLVK3J4Z7ZH3QczoKrnQeiM6mx",
+            )?))
+        );
 
         let b1 = DubpBlockV10::from_string_object(&DubpBlockV10Stringified {
             number: 1,
@@ -644,6 +658,10 @@ mod tests {
         );
         assert_eq!(gva_db.balances().get(&WalletConditionsV2(s2))?, None);
 
+        revert_block(&b0, currency_params, &gva_db)?;
+
+        assert_eq!(gva_db.idty_by_username().count()?, 0);
+
         Ok(())
     }
 }