diff --git a/.gitignore b/.gitignore
index 535e5df50329583e8df5cbc48e38e0304813433f..9b56c22f28c83a694e542dd3fbad3d598c27dcae 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,10 +7,5 @@
 .classpath
 .settings
 .idea
-duniter4j-cesium/src/cesium/**/*.*
 target
-*/target
-duniter4j-elasticsearch/src/main/misc/.index.sh.kate-swp
-duniter4j-elasticsearch/src/main/misc/fr-cities.ods
-duniter4j-elasticsearch/src/main/misc/geoflar-communes-2015.geojson
-duniter4j.iml
\ No newline at end of file
+*/target
\ No newline at end of file
diff --git a/duniter4j-core-client/pom.xml b/duniter4j-core-client/pom.xml
index 868081fa1f3c5cbbd5e886eb002826dc51f12772..3721938557b5390f38788597e8a9338bb144f763 100644
--- a/duniter4j-core-client/pom.xml
+++ b/duniter4j-core-client/pom.xml
@@ -7,7 +7,6 @@
     <version>0.9.2-SNAPSHOT</version>
   </parent>
 
-  <groupId>org.duniter</groupId>
   <artifactId>duniter4j-core-client</artifactId>
   <packaging>jar</packaging>
   <name>Duniter4j :: Core Client API</name>
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/dao/CurrencyDao.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/dao/CurrencyDao.java
index 249975d38111f019f11863ae98102cc4643a5126..cef9f90a3a6138c68f20ac1be141374553940e83 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/dao/CurrencyDao.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/dao/CurrencyDao.java
@@ -32,7 +32,7 @@ import java.util.Set;
 /**
  * Created by eis on 07/02/15.
  */
-public interface CurrencyDao extends Bean, EntityDao<Currency> {
+public interface CurrencyDao extends Bean, EntityDao<String, Currency> {
 
     Currency create(final Currency currency);
 
@@ -42,44 +42,20 @@ public interface CurrencyDao extends Bean, EntityDao<Currency> {
 
     List<Currency> getCurrencies(long accountId);
 
-    /**
-     * Return a (cached) currency name, by id
-     * @param currencyId
-     * @return
-     */
-    String getCurrencyNameById(long currencyId);
-
-    /**
-     * Return a currency id, by name
-     * @param currencyName
-     * @return
-     */
-    Long getCurrencyIdByName(String currencyName);
-
-    /**
-     * Return a (cached) list of blockchain ids
-     * @return
-     */
-    Set<Long> getCurrencyIds();
-
-    /**
-     * Return a (cached) number of registered currencies
-     * @return
-     */
-    int getCurrencyCount();
-
     /**
      * Return the value of the last universal dividend
      * @param currencyId
      * @return
      */
-    long getLastUD(long currencyId);
+    long getLastUD(String currencyId);
 
     /**
      * Return a map of UD (key=blockNumber, value=amount)
      * @return
      */
-     Map<Integer, Long> getAllUD(long currencyId);
+     Map<Integer, Long> getAllUD(String currencyId);
+
+     void insertUDs(String currencyId,  Map<Integer, Long> newUDs);
 
-     void insertUDs(Long currencyId,  Map<Integer, Long> newUDs);
+    boolean isExists(String currencyId);
 }
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/dao/EntityDao.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/dao/EntityDao.java
index e66e8a779d3c04a7578e0a874ee379beb0c71867..49ca4c347ac1d03d9daf037811bc1acdaa7e4fb4 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/dao/EntityDao.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/dao/EntityDao.java
@@ -28,13 +28,13 @@ import org.duniter.core.client.model.local.LocalEntity;
 /**
  * Created by blavenie on 29/12/15.
  */
-public interface EntityDao<B extends LocalEntity> extends Bean{
+public interface EntityDao<I, B extends LocalEntity<I>> extends Bean{
 
         B create(B entity);
 
         B update(B entity);
 
-        B getById(long id);
+        B getById(I id);
 
         void remove(B entity);
 
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/dao/PeerDao.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/dao/PeerDao.java
index 143981ca1dc0419ef71da70f34cd2316a11f4161..3b3d598973342ef288714065297ae953c19b91ce 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/dao/PeerDao.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/dao/PeerDao.java
@@ -29,7 +29,9 @@ import java.util.List;
 /**
  * Created by blavenie on 29/12/15.
  */
-public interface PeerDao extends EntityDao<Peer> {
+public interface PeerDao extends EntityDao<String, Peer> {
 
-    List<Peer> getPeersByCurrencyId(long currencyId);
+    List<Peer> getPeersByCurrencyId(String currencyId);
+
+    boolean isExists(String currencyId, String peerId);
 }
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/dao/mem/MemoryCurrencyDaoImpl.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/dao/mem/MemoryCurrencyDaoImpl.java
index 5d9dffbfa7654f15b080de5b6e7b09ae3e9d7652..20699952287a2bd613304f024d95bccb1f82d39e 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/dao/mem/MemoryCurrencyDaoImpl.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/dao/mem/MemoryCurrencyDaoImpl.java
@@ -32,17 +32,18 @@ import java.util.*;
 public class MemoryCurrencyDaoImpl implements CurrencyDao {
 
 
-    private Map<Long, org.duniter.core.client.model.local.Currency> currencies = new HashMap<>();
+    private Map<String, org.duniter.core.client.model.local.Currency> currencies = new HashMap<>();
 
-    private Map<Long, Map<Integer, Long>> currencyUDsByBlock = new HashMap<>();
+    private Map<String, Map<Integer, Long>> currencyUDsByBlock = new HashMap<>();
+
+    public MemoryCurrencyDaoImpl() {
+        super();
+    }
 
     @Override
     public org.duniter.core.client.model.local.Currency create(final org.duniter.core.client.model.local.Currency entity) {
 
-        long id = getMaxId() + 1;
-        entity.setId(id);
-
-        currencies.put(id, entity);
+        currencies.put(entity.getId(), entity);
 
         return entity;
     }
@@ -66,42 +67,13 @@ public class MemoryCurrencyDaoImpl implements CurrencyDao {
     }
 
     @Override
-    public org.duniter.core.client.model.local.Currency getById(long currencyId) {
-        return currencies.get(currencyId);
-    }
-
-    @Override
-    public String getCurrencyNameById(long currencyId) {
-        org.duniter.core.client.model.local.Currency currency = getById(currencyId);
-        if (currency == null) {
-            return null;
-        }
-        return currency.getCurrencyName();
-    }
-
-    @Override
-    public Long getCurrencyIdByName(String currencyName) {
-        for(org.duniter.core.client.model.local.Currency currency: currencies.values()) {
-            if (currencyName.equalsIgnoreCase(currency.getCurrencyName())) {
-                return currency.getId();
-            }
-        }
-        return null;
-    }
-
-    @Override
-    public Set<Long> getCurrencyIds() {
-        return currencies.keySet();
+    public org.duniter.core.client.model.local.Currency getById(String id) {
+        return currencies.get(id);
     }
 
     @Override
-    public int getCurrencyCount() {
-        return currencies.size();
-    }
-
-    @Override
-    public long getLastUD(long currencyId) {
-        org.duniter.core.client.model.local.Currency currency = getById(currencyId);
+    public long getLastUD(String id) {
+        org.duniter.core.client.model.local.Currency currency = getById(id);
         if (currency == null) {
             return -1;
         }
@@ -109,31 +81,23 @@ public class MemoryCurrencyDaoImpl implements CurrencyDao {
     }
 
     @Override
-    public Map<Integer, Long> getAllUD(long currencyId) {
+    public Map<Integer, Long> getAllUD(String id) {
 
-        return currencyUDsByBlock.get(currencyId);
+        return currencyUDsByBlock.get(id);
     }
 
     @Override
-    public void insertUDs(Long currencyId,  Map<Integer, Long> newUDs) {
-        Map<Integer, Long> udsByBlock = currencyUDsByBlock.get(currencyId);
+    public void insertUDs(String id,  Map<Integer, Long> newUDs) {
+        Map<Integer, Long> udsByBlock = currencyUDsByBlock.get(id);
         if (udsByBlock == null) {
             udsByBlock = new HashMap<>();
-            currencyUDsByBlock.put(currencyId, udsByBlock);
+            currencyUDsByBlock.put(id, udsByBlock);
         }
         udsByBlock.putAll(newUDs);
     }
 
-    /* -- internal methods -- */
-
-    protected long getMaxId() {
-        long currencyId = -1;
-        for (Long anId : currencies.keySet()) {
-            if (anId > currencyId) {
-                currencyId = anId;
-            }
-        }
-
-        return currencyId;
+    @Override
+    public boolean isExists(String currencyId) {
+        return currencies.get(currencyId) != null;
     }
 }
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/dao/mem/MemoryPeerDaoImpl.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/dao/mem/MemoryPeerDaoImpl.java
index d48075bcc2664a95ec54d69d6cd0f34f6ee44e5a..24ce18f6f5c2da4eca552a2e34dbf52256b86c0e 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/dao/mem/MemoryPeerDaoImpl.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/dao/mem/MemoryPeerDaoImpl.java
@@ -29,20 +29,24 @@ import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 /**
  * Created by blavenie on 29/12/15.
  */
 public class MemoryPeerDaoImpl implements PeerDao {
 
-    private Map<Long, Peer> peersByCurrencyId = new HashMap<>();
+    private Map<String, Peer> peersByCurrencyId = new HashMap<>();
+
+    public MemoryPeerDaoImpl() {
+        super();
+    }
 
     @Override
     public Peer create(Peer entity) {
-        long id = getMaxId() + 1;
-        entity.setId(id);
+        entity.setId(entity.computeKey());
 
-        peersByCurrencyId.put(id, entity);
+        peersByCurrencyId.put(entity.getId(), entity);
 
         return entity;
     }
@@ -54,7 +58,7 @@ public class MemoryPeerDaoImpl implements PeerDao {
     }
 
     @Override
-    public Peer getById(long id) {
+    public Peer getById(String id) {
         return peersByCurrencyId.get(id);
     }
 
@@ -64,29 +68,15 @@ public class MemoryPeerDaoImpl implements PeerDao {
     }
 
     @Override
-    public List<Peer> getPeersByCurrencyId(long currencyId) {
-
-        List<Peer> result = new ArrayList<>();
-
-        for(Peer peer: peersByCurrencyId.values()) {
-            if (peer.getCurrencyId() == currencyId) {
-                result.add(peer);
-            }
-        }
-
-        return result;
+    public List<Peer> getPeersByCurrencyId(final String currencyId) {
+        return peersByCurrencyId.values().stream()
+            .filter(peer -> currencyId.equals(peer.getCurrency()))
+                .collect(Collectors.toList());
     }
 
-    /* -- internal methods -- */
-
-    protected long getMaxId() {
-        long currencyId = -1;
-        for (Long anId : peersByCurrencyId.keySet()) {
-            if (anId > currencyId) {
-                currencyId = anId;
-            }
-        }
-
-        return currencyId;
+    @Override
+    public boolean isExists(final String currencyId, final  String peerId) {
+        return peersByCurrencyId.values().stream()
+                .anyMatch(peer -> currencyId.equals(peer.getCurrency()) && peerId.equals(peer.getId()));
     }
 }
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/Account.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/Account.java
index b4291040fcabbca875af8b3cbdded1cc3eb064fe..0be89fb3e410c2f469badd6abb260c896771468e 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/Account.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/Account.java
@@ -28,7 +28,7 @@ import org.duniter.core.client.model.local.LocalEntity;
 /**
  * Created by eis on 07/02/15.
  */
-public class Account implements LocalEntity {
+public class Account implements LocalEntity<Long> {
 
     private Long id;
     private String uid;
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/Currency.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/Currency.java
index 665e91fef579d1eba144202d6ffd78b65e79b0c2..9a23e4f041706b57272f1259f76b4c4f4817aa60 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/Currency.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/Currency.java
@@ -30,59 +30,11 @@ import java.io.Serializable;
 /**
  * Created by eis on 05/02/15.
  */
-public class Currency implements Serializable {
-
-    public static final String PROPERTY_CURRENCY  = "currency";
-
-    private String currency;
-    private Integer membersCount;
-    private String firstBlockSignature;
-    private Long lastUD;
-    private BlockchainParameters parameters;
+public class Currency extends org.duniter.core.client.model.local.Currency implements Serializable {
 
     private String[] tags;
     private String issuer;
 
-    public String getCurrency() {
-        return currency;
-    }
-
-    public void setCurrency(String currency) {
-        this.currency = currency;
-    }
-
-    public Integer getMembersCount() {
-        return membersCount;
-    }
-
-    public void setMembersCount(Integer membersCount) {
-        this.membersCount = membersCount;
-    }
-
-    public String getFirstBlockSignature() {
-        return firstBlockSignature;
-    }
-
-    public void setFirstBlockSignature(String firstBlockSignature) {
-        this.firstBlockSignature = firstBlockSignature;
-    }
-
-    public Long getLastUD() {
-        return lastUD;
-    }
-
-    public void setLastUD(Long lastUD) {
-        this.lastUD = lastUD;
-    }
-
-    public BlockchainParameters getParameters() {
-        return parameters;
-    }
-
-    public void setParameters(BlockchainParameters parameters) {
-        this.parameters = parameters;
-    }
-
     public String[] getTags() {
         return tags;
     }
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Certification.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Certification.java
index c1ffc270785de68062d000a81a893a29dc673fe9..ec5ac6b055c256afa5890d59025c4ccac3845ca8 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Certification.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Certification.java
@@ -35,7 +35,7 @@ public class Certification {
 
     private static final long serialVersionUID = 2204517069552693026L;
 
-    private long currencyId;
+    private String currencyId;
 
     private String uid;
 
@@ -68,11 +68,11 @@ public class Certification {
         super();
     }
 
-    public long getCurrencyId() {
+    public String getCurrencyId() {
         return currencyId;
     }
 
-    public void setCurrencyId(long currencyId) {
+    public void setCurrencyId(String currencyId) {
         this.currencyId = currencyId;
     }
 
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Contact.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Contact.java
index b134f54f8420a15e31843892c235bae1e519d1d0..6d95919e9c812ae80c7939b0ef617b7d714ee834 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Contact.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Contact.java
@@ -32,7 +32,7 @@ import java.util.List;
  * A wallet is a user account
  * Created by eis on 13/01/15.
  */
-public class Contact implements LocalEntity, Serializable {
+public class Contact implements LocalEntity<Long>, Serializable {
 
     private long id;
     private long accountId;
@@ -111,12 +111,9 @@ public class Contact implements LocalEntity, Serializable {
         return false;
     }
 
-    public boolean hasIdentityForCurrency(long currencyId) {
-        for(Identity identity:identities) {
-            if (identity.getCurrencyId() != null && identity.getCurrencyId().longValue() == currencyId) {
-                return true;
-            }
-        }
-        return false;
+    public boolean hasIdentityForCurrency(String currencyId) {
+        return identities.stream()
+                .anyMatch(identity -> identity.getCurrencyId() != null
+                        && currencyId.equals(identity.getCurrencyId()));
     }
 }
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Currency.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Currency.java
index cb0fca9de29e3785504d400ecdfc026c504801c3..04770f58f5b0a07a1315706d2c0d430976bddf8b 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Currency.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Currency.java
@@ -24,22 +24,18 @@ package org.duniter.core.client.model.local;
 
 import java.io.Serializable;
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
 import org.duniter.core.client.model.Account;
 import org.duniter.core.client.model.bma.BlockchainParameters;
 
 /**
  * Created by eis on 05/02/15.
  */
-public class Currency implements LocalEntity, Serializable {
+public class Currency implements LocalEntity<String>, Serializable {
 
-    private Peer peers[];
-
-    private Long id;
     private String currencyName;
     private Integer membersCount;
     private String firstBlockSignature;
-    private Account account;
-    private Long accountId;
     private Long lastUD;
     private BlockchainParameters parameters;
 
@@ -49,17 +45,16 @@ public class Currency implements LocalEntity, Serializable {
     public Currency(String currencyName,
                     String firstBlockSignature,
                     int membersCount,
-                    Peer[] peers,
                     BlockchainParameters parameters) {
         this.currencyName = currencyName;
         this.firstBlockSignature = firstBlockSignature;
         this.membersCount = membersCount;
-        this.peers = peers;
         this.parameters = parameters;
     }
 
-    public Long getId() {
-        return id;
+    @JsonIgnore
+    public String getId() {
+        return currencyName;
     }
 
     public String getCurrencyName()
@@ -75,16 +70,8 @@ public class Currency implements LocalEntity, Serializable {
         return firstBlockSignature;
     }
 
-    public Peer[] getPeers() {
-        return peers;
-    }
-
-    public void setPeers(Peer[] peers) {
-        this.peers = peers;
-    }
-
-    public void setId(Long id) {
-        this.id = id;
+    public void setId(String id) {
+        this.currencyName = id;
     }
 
     public void setCurrencyName(String currencyName) {
@@ -99,22 +86,6 @@ public class Currency implements LocalEntity, Serializable {
         this.firstBlockSignature = firstBlockSignature;
     }
 
-    public Account getAccount() {
-        return account;
-    }
-
-    public void setAccount(Account account) {
-        this.account = account;
-    }
-
-    public Long getAccountId() {
-        return accountId;
-    }
-
-    public void setAccountId(Long accountId) {
-        this.accountId = accountId;
-    }
-
     public Long getLastUD() {
         return lastUD;
     }
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Identity.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Identity.java
index 8f082680af46b092504317d264cf721607e3cfc6..c860cc4ad469e24773c4e939b23675f5e93d59e5 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Identity.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Identity.java
@@ -33,7 +33,7 @@ public class Identity extends BasicIdentity {
 
     private Boolean isMember = null;
 
-    private Long currencyId;
+    private String currencyId;
 
     /**
      * The timestamp value of the signature date (a BLOCK_UID)
@@ -58,11 +58,11 @@ public class Identity extends BasicIdentity {
         this.isMember = isMember;
     }
 
-    public Long getCurrencyId() {
+    public String getCurrencyId() {
         return currencyId;
     }
 
-    public void setCurrencyId(Long currencyId) {
+    public void setCurrencyId(String currencyId) {
         this.currencyId = currencyId;
     }
 }
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/LocalEntity.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/LocalEntity.java
index 770672e2df3312de56703c82f045eb79438d08e3..6216420f0769230662729b420596b9bb71acee4c 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/LocalEntity.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/LocalEntity.java
@@ -26,8 +26,8 @@ package org.duniter.core.client.model.local;
 /**
  * Created by eis on 07/02/15.
  */
-public interface LocalEntity {
+public interface LocalEntity<I extends Object> {
 
-    Long getId();
-    void setId(Long id);
+    I getId();
+    void setId(I id);
 }
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Movement.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Movement.java
index 55822574af94fd113e144c2378fc3a45c996e496..8e2d134e9dd22c02a791d3571ce7a51585423b62 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Movement.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Movement.java
@@ -28,7 +28,7 @@ import java.io.Serializable;
  * A wallet's movement (DU or transfer)
  * @author
  */
-public class Movement implements LocalEntity, Serializable {
+public class Movement implements LocalEntity<Long>, Serializable {
 
     private Long id;
     private long walletId;
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Peer.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Peer.java
index d31a8496e6e44f0ed5b8f70725d58ac5d9cabb90..b6b410006427c698e317bb799f82275342738152 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Peer.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Peer.java
@@ -34,7 +34,7 @@ import org.duniter.core.util.http.InetAddressUtils;
 import java.io.Serializable;
 import java.util.StringJoiner;
 
-public class Peer implements LocalEntity, Serializable {
+public class Peer implements LocalEntity<String>, Serializable {
 
 
 
@@ -161,9 +161,7 @@ public class Peer implements LocalEntity, Serializable {
     }
 
 
-    // Local entity attribute (only used for local DB)
-    private Long id;
-    private Long currencyId;
+    private String id;
 
     private String api;
     private String dns;
@@ -232,23 +230,13 @@ public class Peer implements LocalEntity, Serializable {
     }
 
     @JsonIgnore
-    public Long getId() {
+    public String getId() {
         return id;
     }
 
     @JsonIgnore
-    public void setId(Long id) {
-        this.id = id;
-    }
-
-    @JsonIgnore
-    public Long getCurrencyId() {
-        return currencyId;
-    }
-
-    @JsonIgnore
-    public void setCurrencyId(Long currencyId) {
-        this.currencyId = currencyId;
+    public void setId(String id) {
+        this.id  = id;
     }
 
     public String getApi() {
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Wallet.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Wallet.java
index 5f31acad8b44b1d61ee2148486b32a1280bee457..675f5d9bbec4cf3c8e71d78754adc2da87a3cd8e 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Wallet.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/local/Wallet.java
@@ -34,12 +34,12 @@ import org.duniter.core.util.crypto.KeyPair;
  * A wallet is a user account
  * Created by eis on 13/01/15.
  */
-public class Wallet extends KeyPair implements LocalEntity, Serializable {
+public class Wallet extends KeyPair implements LocalEntity<Long>, Serializable {
 
 
     private Long id;
-    private Long currencyId;
     private Long accountId;
+    private String currency;
     private String name;
     private Long credit;
     private Identity identity;
@@ -53,10 +53,6 @@ public class Wallet extends KeyPair implements LocalEntity, Serializable {
      */
     private boolean isDirty = false;
 
-    // TODO : voir si besoin de les garder ou pas
-    private String salt;
-    private String currency;
-
     public Wallet() {
         super(null, null);
         this.identity = new Identity();
@@ -96,14 +92,6 @@ public class Wallet extends KeyPair implements LocalEntity, Serializable {
         return identity.getPubkey();
     }
 
-    public String getSalt(){
-        return salt;
-    }
-
-    public void setSalt(String salt){
-        this.salt = salt;
-    }
-
     public String getCurrency() {
         return currency;
     }
@@ -120,12 +108,12 @@ public class Wallet extends KeyPair implements LocalEntity, Serializable {
         return identity.getTimestamp() != null;
     }
 
-    public Long getCurrencyId() {
-        return currencyId;
+    public String getCurrencyId() {
+        return currency;
     }
 
-    public void setCurrencyId(Long currencyId) {
-        this.currencyId = currencyId;
+    public void setCurrencyId(String currencyId) {
+        this.currency = currencyId;
     }
 
     public Long getAccountId() {
@@ -235,7 +223,7 @@ public class Wallet extends KeyPair implements LocalEntity, Serializable {
         if (o instanceof Wallet) {
             return ObjectUtils.equals(id, ((Wallet)o).id)
                     && ObjectUtils.equals(getPubKeyHash(), ((Wallet)o).getPubKeyHash())
-                    && ObjectUtils.equals(currencyId, ((Wallet)o).currencyId);
+                    && ObjectUtils.equals(currency, ((Wallet)o).currency);
         }
         return super.equals(o);
     }
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/BaseRemoteServiceImpl.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/BaseRemoteServiceImpl.java
index b655d2ee0ebf17e4b29709bf22ceb80d8987427a..b1cfc667e3614188b9425aaca6985d758cf14000 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/BaseRemoteServiceImpl.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/BaseRemoteServiceImpl.java
@@ -22,14 +22,14 @@ package org.duniter.core.client.service.bma;
  * #L%
  */
 
+import org.apache.http.client.methods.HttpUriRequest;
 import org.apache.http.client.utils.URIBuilder;
 import org.duniter.core.beans.InitializingBean;
 import org.duniter.core.beans.Service;
 import org.duniter.core.client.model.local.Peer;
 import org.duniter.core.client.service.HttpService;
-import org.duniter.core.client.service.local.PeerService;
 import org.duniter.core.client.service.ServiceLocator;
-import org.apache.http.client.methods.HttpUriRequest;
+import org.duniter.core.client.service.local.PeerService;
 import org.duniter.core.exception.TechnicalException;
 import org.duniter.core.util.websocket.WebsocketClientEndpoint;
 
@@ -37,7 +37,6 @@ import java.io.IOException;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.URL;
-import java.util.ServiceConfigurationError;
 
 /**
  * Created by eis on 05/02/15.
@@ -63,7 +62,7 @@ public abstract class BaseRemoteServiceImpl implements Service, InitializingBean
         return httpService.executeRequest(peer, absolutePath, resultClass);
     }
 
-    public <T> T executeRequest(long currencyId, String absolutePath, Class<? extends T> resultClass)  {
+    public <T> T executeRequest(String currencyId, String absolutePath, Class<? extends T> resultClass)  {
         Peer peer = peerService.getActivePeerByCurrencyId(currencyId);
         return httpService.executeRequest(peer, absolutePath, resultClass);
     }
@@ -72,7 +71,7 @@ public abstract class BaseRemoteServiceImpl implements Service, InitializingBean
         return httpService.executeRequest(request, resultClass);
     }
 
-    public String getPath(long currencyId, String aPath) {
+    public String getPath(String currencyId, String aPath) {
         Peer peer = peerService.getActivePeerByCurrencyId(currencyId);
         return httpService.getPath(peer, aPath);
     }
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/BlockchainRemoteService.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/BlockchainRemoteService.java
index 2231f3f2561fedfda4a6248b23d21254e4a8b521..eaac3b408694040101a197afee66b7505285259a 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/BlockchainRemoteService.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/BlockchainRemoteService.java
@@ -47,7 +47,7 @@ public interface BlockchainRemoteService extends Service {
      * @param useCache
      * @return
      */
-    BlockchainParameters getParameters(long currencyId, boolean useCache);
+    BlockchainParameters getParameters(String currencyId, boolean useCache);
 
     /**
      * get the blockchain parameters (currency parameters)
@@ -55,7 +55,7 @@ public interface BlockchainRemoteService extends Service {
      * @param currencyId
      * @return
      */
-    BlockchainParameters getParameters(long currencyId);
+    BlockchainParameters getParameters(String currencyId);
 
     /**
      * get the blockchain parameters (currency parameters)
@@ -72,7 +72,7 @@ public interface BlockchainRemoteService extends Service {
      * @param number
      * @return
      */
-    BlockchainBlock getBlock(long currencyId, long number) throws BlockNotFoundException;
+    BlockchainBlock getBlock(String currencyId, long number) throws BlockNotFoundException;
 
     /**
      * Retrieve the dividend of a block, by id (from 0 to current).
@@ -82,7 +82,7 @@ public interface BlockchainRemoteService extends Service {
      * @param number
      * @return
      */
-    Long getBlockDividend(long currencyId, long number) throws BlockNotFoundException;
+    Long getBlockDividend(String currencyId, long number) throws BlockNotFoundException;
 
     /**
      * Retrieve a block, by id (from 0 to current)
@@ -124,14 +124,14 @@ public interface BlockchainRemoteService extends Service {
      *
      * @return
      */
-    BlockchainBlock getCurrentBlock(long currencyId, boolean useCache);
+    BlockchainBlock getCurrentBlock(String currencyId, boolean useCache);
 
     /**
      * Retrieve the current block
      *
      * @return
      */
-    BlockchainBlock getCurrentBlock(long currencyId);
+    BlockchainBlock getCurrentBlock(String currencyId);
 
     /**
      * Retrieve the current block
@@ -157,7 +157,7 @@ public interface BlockchainRemoteService extends Service {
      * @param currencyId id of currency
      * @return
      */
-    long getLastUD(long currencyId);
+    long getLastUD(String currencyId);
 
     /**
      * Retrieve the last emitted UD, from a peer (or ud0 if not UD emitted yet)
@@ -191,12 +191,12 @@ public interface BlockchainRemoteService extends Service {
      *
      * @param identity
      */
-    void loadMembership(long currencyId, Identity identity, boolean checkLookupForNonMember);
+    void loadMembership(String currencyId, Identity identity, boolean checkLookupForNonMember);
 
 
-    BlockchainMemberships getMembershipByUid(long currencyId, String uid);
+    BlockchainMemberships getMembershipByUid(String currencyId, String uid);
 
-    BlockchainMemberships getMembershipByPublicKey(long currencyId, String pubkey);
+    BlockchainMemberships getMembershipByPublicKey(String currencyId, String pubkey);
 
     /**
      * Request to integrate the wot
@@ -205,7 +205,7 @@ public interface BlockchainRemoteService extends Service {
 
     void requestMembership(Peer peer, String currency, byte[] pubKey, byte[] secKey, String uid, String membershipBlockUid, String selfBlockUid);
 
-    BlockchainMemberships getMembershipByPubkeyOrUid(long currencyId, String uidOrPubkey);
+    BlockchainMemberships getMembershipByPubkeyOrUid(String currencyId, String uidOrPubkey);
 
     BlockchainMemberships getMembershipByPubkeyOrUid(Peer peer, String uidOrPubkey);
 
@@ -220,7 +220,7 @@ public interface BlockchainRemoteService extends Service {
      * @param startOffset
      * @return
      */
-    Map<Integer, Long> getUDs(long currencyId, long startOffset);
+    Map<Integer, Long> getUDs(String currencyId, long startOffset);
 
     /**
      * Listening new block event
@@ -229,7 +229,7 @@ public interface BlockchainRemoteService extends Service {
      * @param autoReconnect
      * @return
      */
-    WebsocketClientEndpoint addBlockListener(long currencyId, WebsocketClientEndpoint.MessageListener listener, boolean autoReconnect);
+    WebsocketClientEndpoint addBlockListener(String currencyId, WebsocketClientEndpoint.MessageListener listener, boolean autoReconnect);
 
     WebsocketClientEndpoint addBlockListener(Peer peer, WebsocketClientEndpoint.MessageListener listener, boolean autoReconnect);
 
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/BlockchainRemoteServiceImpl.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/BlockchainRemoteServiceImpl.java
index 22c7f92e4ac677b1a4fbd763a4480cf44bd87600..c577dbc8b5bc982b9c135f1ccdb8fddcbd6079d3 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/BlockchainRemoteServiceImpl.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/BlockchainRemoteServiceImpl.java
@@ -22,15 +22,12 @@ package org.duniter.core.client.service.bma;
  * #L%
  */
 
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.duniter.core.util.Preconditions;
 import org.apache.http.NameValuePair;
 import org.apache.http.client.entity.UrlEncodedFormEntity;
 import org.apache.http.client.methods.HttpPost;
 import org.apache.http.message.BasicNameValuePair;
 import org.duniter.core.client.config.Configuration;
 import org.duniter.core.client.model.bma.*;
-import org.duniter.core.util.json.JsonArrayParser;
 import org.duniter.core.client.model.local.Identity;
 import org.duniter.core.client.model.local.Peer;
 import org.duniter.core.client.model.local.Wallet;
@@ -38,10 +35,12 @@ import org.duniter.core.client.service.ServiceLocator;
 import org.duniter.core.client.service.exception.*;
 import org.duniter.core.exception.TechnicalException;
 import org.duniter.core.service.CryptoService;
+import org.duniter.core.util.Preconditions;
 import org.duniter.core.util.StringUtils;
 import org.duniter.core.util.cache.Cache;
 import org.duniter.core.util.cache.SimpleCache;
 import org.duniter.core.util.crypto.CryptoUtils;
+import org.duniter.core.util.json.JsonArrayParser;
 import org.duniter.core.util.websocket.WebsocketClientEndpoint;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -49,7 +48,6 @@ import org.slf4j.LoggerFactory;
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.URI;
-import java.net.URISyntaxException;
 import java.util.*;
 
 public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implements BlockchainRemoteService {
@@ -79,20 +77,14 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement
 
     public static final String URL_WS_BLOCK = "/ws/block";
 
-    public static final String URL_WS_PEER = "/ws/peer";
-
-    private ObjectMapper objectMapper;
-
-    private NetworkRemoteService networkRemoteService;
-
     private Configuration config;
 
     // Cache need for wallet refresh : iteration on wallet should not
     // execute a download of the current block
-    private Cache<Long, BlockchainBlock> mCurrentBlockCache;
+    private Cache<String, BlockchainBlock> mCurrentBlockCache;
 
     // Cache on blockchain parameters
-    private Cache<Long, BlockchainParameters> mParametersCache;
+    private Cache<String, BlockchainParameters> mParametersCache;
 
     private Map<URI, WebsocketClientEndpoint> wsEndPoints = new HashMap<>();
 
@@ -103,7 +95,6 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement
     @Override
     public void afterPropertiesSet() {
         super.afterPropertiesSet();
-        networkRemoteService = ServiceLocator.instance().getNetworkRemoteService();
         config = Configuration.instance();
 
         // Initialize caches
@@ -123,7 +114,7 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement
     }
 
     @Override
-    public BlockchainParameters getParameters(long currencyId, boolean useCache) {
+    public BlockchainParameters getParameters(String currencyId, boolean useCache) {
         if (!useCache) {
             return getParameters(currencyId);
         } else {
@@ -132,7 +123,7 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement
     }
 
     @Override
-    public BlockchainParameters getParameters(long currencyId) {
+    public BlockchainParameters getParameters(String currencyId) {
         // get blockchain parameter
         BlockchainParameters result = executeRequest(currencyId, URL_PARAMETERS, BlockchainParameters.class);
         return result;
@@ -146,7 +137,7 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement
     }
 
     @Override
-    public BlockchainBlock getBlock(long currencyId, long number) throws BlockNotFoundException  {
+    public BlockchainBlock getBlock(String currencyId, long number) throws BlockNotFoundException  {
         String path = String.format(URL_BLOCK, number);
         try {
             return executeRequest(currencyId, path, BlockchainBlock.class);
@@ -157,7 +148,7 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement
     }
 
     @Override
-    public Long getBlockDividend(long currencyId, long number) throws BlockNotFoundException {
+    public Long getBlockDividend(String currencyId, long number) throws BlockNotFoundException {
         String path = String.format(URL_BLOCK, number);
         try {
             String json = executeRequest(currencyId, path, String.class);
@@ -221,7 +212,7 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement
      *
      * @return
      */
-    public BlockchainBlock getCurrentBlock(long currencyId, boolean useCache) {
+    public BlockchainBlock getCurrentBlock(String currencyId, boolean useCache) {
         if (!useCache) {
             return getCurrentBlock(currencyId);
         } else {
@@ -230,7 +221,7 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement
     }
 
     @Override
-    public BlockchainBlock getCurrentBlock(long currencyId) {
+    public BlockchainBlock getCurrentBlock(String currencyId) {
         // get blockchain parameter
         BlockchainBlock result = executeRequest(currencyId, URL_BLOCK_CURRENT, BlockchainBlock.class);
         return result;
@@ -264,7 +255,7 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement
     }
 
     @Override
-    public long getLastUD(long currencyId) {
+    public long getLastUD(String currencyId) {
         // get block number with UD
         String blocksWithUdResponse = executeRequest(currencyId, URL_BLOCK_WITH_UD, String.class);
         Integer blockNumber = getLastBlockNumberFromJson(blocksWithUdResponse);
@@ -374,12 +365,12 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement
      *
      * @param identity
      */
-    public void loadMembership(long currencyId, Identity identity, boolean checkLookupForNonMember) {
+    public void loadMembership(String currencyId, Identity identity, boolean checkLookupForNonMember) {
         loadMembership(currencyId, null, identity, checkLookupForNonMember);
     }
 
 
-    public BlockchainMemberships getMembershipByUid(long currencyId, String uid) {
+    public BlockchainMemberships getMembershipByUid(String currencyId, String uid) {
         Preconditions.checkArgument(StringUtils.isNotBlank(uid));
 
         BlockchainMemberships result = getMembershipByPubkeyOrUid(currencyId, uid);
@@ -389,7 +380,7 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement
         return result;
     }
 
-    public BlockchainMemberships getMembershipByPublicKey(long currencyId, String pubkey) {
+    public BlockchainMemberships getMembershipByPublicKey(String currencyId, String pubkey) {
         Preconditions.checkArgument(StringUtils.isNotBlank(pubkey));
 
         BlockchainMemberships result = getMembershipByPubkeyOrUid(currencyId, pubkey);
@@ -460,7 +451,7 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement
         executeRequest(httpPost, String.class);
     }
 
-    public BlockchainMemberships getMembershipByPubkeyOrUid(long currencyId, String uidOrPubkey) {
+    public BlockchainMemberships getMembershipByPubkeyOrUid(String currencyId, String uidOrPubkey) {
         String path = String.format(URL_MEMBERSHIP_SEARCH, uidOrPubkey);
 
         // search blockchain membership
@@ -514,7 +505,7 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement
      * @param startOffset
      * @return
      */
-    public Map<Integer, Long> getUDs(long currencyId, long startOffset) {
+    public Map<Integer, Long> getUDs(String currencyId, long startOffset) {
         log.debug(String.format("Getting block's UD from block [%s]", startOffset));
 
         int[] blockNumbersWithUD = getBlocksWithUD(currencyId);
@@ -566,7 +557,7 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement
     }
 
     @Override
-    public WebsocketClientEndpoint addBlockListener(long currencyId, WebsocketClientEndpoint.MessageListener listener, boolean autoReconnect) {
+    public WebsocketClientEndpoint addBlockListener(String currencyId, WebsocketClientEndpoint.MessageListener listener, boolean autoReconnect) {
         return addBlockListener(peerService.getActivePeerByCurrencyId(currencyId), listener, autoReconnect);
     }
 
@@ -593,23 +584,23 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement
     protected void initCaches() {
         int cacheTimeInMillis = config.getNetworkCacheTimeInMillis();
 
-        mCurrentBlockCache = new SimpleCache<Long, BlockchainBlock>(cacheTimeInMillis) {
+        mCurrentBlockCache = new SimpleCache<String, BlockchainBlock>(cacheTimeInMillis) {
             @Override
-            public BlockchainBlock load(Long currencyId) {
+            public BlockchainBlock load(String currencyId) {
                 return getCurrentBlock(currencyId);
             }
         };
 
-        mParametersCache = new SimpleCache<Long, BlockchainParameters>(/*eternal cache*/) {
+        mParametersCache = new SimpleCache<String, BlockchainParameters>(/*eternal cache*/) {
             @Override
-            public BlockchainParameters load(Long currencyId) {
+            public BlockchainParameters load(String currencyId) {
                 return getParameters(currencyId);
             }
         };
     }
 
 
-    protected void loadMembership(Long currencyId, Peer peer, Identity identity, boolean checkLookupForNonMember) {
+    protected void loadMembership(String currencyId, Peer peer, Identity identity, boolean checkLookupForNonMember) {
         Preconditions.checkNotNull(identity);
         Preconditions.checkArgument(StringUtils.isNotBlank(identity.getUid()));
         Preconditions.checkArgument(StringUtils.isNotBlank(identity.getPubkey()));
@@ -655,7 +646,7 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement
 
     }
 
-    private int[] getBlocksWithUD(long currencyId) {
+    private int[] getBlocksWithUD(String currencyId) {
         log.debug("Getting blocks with UD");
 
         String json = executeRequest(currencyId, URL_BLOCK_WITH_UD, String.class);
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/NetworkRemoteService.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/NetworkRemoteService.java
index b8e1a0fc784258a9ee22d061019043fc4723cce2..dc14cc9cc2750d2ecf1da97af4bd433db1714f30 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/NetworkRemoteService.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/NetworkRemoteService.java
@@ -46,7 +46,7 @@ public interface NetworkRemoteService extends Service {
 
     List<Peer> findPeers(Peer peer, String status, EndpointApi endpointApi, Integer currentBlockNumber, String currentBlockHash);
 
-    WebsocketClientEndpoint addPeerListener(long currencyId, WebsocketClientEndpoint.MessageListener listener, boolean autoReconnect);
+    WebsocketClientEndpoint addPeerListener(String currencyId, WebsocketClientEndpoint.MessageListener listener, boolean autoReconnect);
 
     WebsocketClientEndpoint addPeerListener(Peer peer, WebsocketClientEndpoint.MessageListener listener, boolean autoReconnect);
 }
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/NetworkRemoteServiceImpl.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/NetworkRemoteServiceImpl.java
index 5d8b1aef38e1b490ee3209656c8353f6e78f1a3b..7cfafe68ee2c9e99390094c550fec9b06e579366 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/NetworkRemoteServiceImpl.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/NetworkRemoteServiceImpl.java
@@ -153,7 +153,7 @@ public class NetworkRemoteServiceImpl extends BaseRemoteServiceImpl implements N
 
 
     @Override
-    public WebsocketClientEndpoint addPeerListener(long currencyId, WebsocketClientEndpoint.MessageListener listener, boolean autoReconnect) {
+    public WebsocketClientEndpoint addPeerListener(String currencyId, WebsocketClientEndpoint.MessageListener listener, boolean autoReconnect) {
         Peer peer = peerService.getActivePeerByCurrencyId(currencyId);
         return addPeerListener(peer, listener, autoReconnect);
     }
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/TransactionRemoteService.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/TransactionRemoteService.java
index 882f4c542310df2c93a8ebf1f863ccd413d6d97f..0e5ab18a7569748dcce3f1229db76b93685d905f 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/TransactionRemoteService.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/TransactionRemoteService.java
@@ -58,17 +58,17 @@ public interface TransactionRemoteService extends Service {
 	String transfer(Wallet wallet, String destPubKey, long amount,
                     String comment) throws InsufficientCreditException;
 
-	TxSource getSources(long currencyId, String pubKey);
+	TxSource getSources(String currencyId, String pubKey);
 
     TxSource getSources(Peer peer, String pubKey);
 
-    long getCreditOrZero(long currencyId, String pubKey);
+    long getCreditOrZero(String currencyId, String pubKey);
 
-    Long getCredit(long currencyId, String pubKey);
+    Long getCredit(String currencyId, String pubKey);
 
     Long getCredit(Peer peer, String pubKey);
 
     long computeCredit(TxSource.Source[] sources);
 
-    TxHistory getTxHistory(long currencyId, String pubKey, long fromBlockNumber, long toBlockNumber);
+    TxHistory getTxHistory(String currencyId, String pubKey, long fromBlockNumber, long toBlockNumber);
 }
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/TransactionRemoteServiceImpl.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/TransactionRemoteServiceImpl.java
index 4c8e869fec2bf9770e9f83cbd042bbe55e9de857..47c91a51dab46017ec0d4aa9f6e00bc25e35437e 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/TransactionRemoteServiceImpl.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/TransactionRemoteServiceImpl.java
@@ -136,7 +136,7 @@ public class TransactionRemoteServiceImpl extends BaseRemoteServiceImpl implemen
 
 	}
 
-	public TxSource getSources(long currencyId, String pubKey) {
+	public TxSource getSources(String currencyId, String pubKey) {
 		if (log.isDebugEnabled()) {
 			log.debug(String.format("Get sources by pubKey: %s", pubKey));
 		}
@@ -160,7 +160,7 @@ public class TransactionRemoteServiceImpl extends BaseRemoteServiceImpl implemen
 		return result;
 	}
 
-    public long getCreditOrZero(long currencyId, String pubKey) {
+    public long getCreditOrZero(String currencyId, String pubKey) {
         Long credit = getCredit(currencyId, pubKey);
 
         if (credit == null) {
@@ -169,7 +169,7 @@ public class TransactionRemoteServiceImpl extends BaseRemoteServiceImpl implemen
         return credit.longValue();
     }
 
-    public Long getCredit(long currencyId, String pubKey) {
+    public Long getCredit(String currencyId, String pubKey) {
 		if (log.isDebugEnabled()) {
 			log.debug(String.format("Get credit by pubKey [%s] for currency [id=%s]", pubKey, currencyId));
 		}
@@ -216,7 +216,7 @@ public class TransactionRemoteServiceImpl extends BaseRemoteServiceImpl implemen
         return credit;
     }
 
-    public TxHistory getTxHistory(long currencyId, String pubKey, long fromBlockNumber, long toBlockNumber) {
+    public TxHistory getTxHistory(String currencyId, String pubKey, long fromBlockNumber, long toBlockNumber) {
 		Preconditions.checkNotNull(pubKey);
         Preconditions.checkArgument(fromBlockNumber >= 0);
         Preconditions.checkArgument(fromBlockNumber <= toBlockNumber);
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/WotRemoteService.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/WotRemoteService.java
index 0b8aca4c0fb407f1f8cca179b9503fab95618ff2..f89c399f433c277eed28ec2a95cac30bb5587f06 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/WotRemoteService.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/WotRemoteService.java
@@ -37,39 +37,39 @@ import java.util.Set;
 
 public interface WotRemoteService extends Service {
 
-    List<Identity> findIdentities(Set<Long> currenciesIds, String uidOrPubKey);
+    List<Identity> findIdentities(Set<String> currenciesIds, String uidOrPubKey);
 
-    WotLookup.Uid find(long currencyId, String uidOrPubKey);
+    WotLookup.Uid find(String currencyId, String uidOrPubKey);
 
-    void getRequirments(long currencyId, String pubKey);
+    void getRequirments(String currencyId, String pubKey);
 
-    WotLookup.Uid findByUid(long currencyId, String uid);
+    WotLookup.Uid findByUid(String currencyId, String uid);
 
-    WotLookup.Uid findByUidAndPublicKey(long currencyId, String uid, String pubKey);
+    WotLookup.Uid findByUidAndPublicKey(String currencyId, String uid, String pubKey);
 
     WotLookup.Uid findByUidAndPublicKey(Peer peer, String uid, String pubKey);
 
-    Identity getIdentity(long currencyId, String uid, String pubKey);
+    Identity getIdentity(String currencyId, String uid, String pubKey);
 
-    Identity getIdentity(long currencyId, String pubKey);
+    Identity getIdentity(String currencyId, String pubKey);
 
     Identity getIdentity(Peer peer, String uid, String pubKey);
 
-    Collection<Certification> getCertifications(long currencyId, String uid, String pubkey, boolean isMember);
+    Collection<Certification> getCertifications(String currencyId, String uid, String pubkey, boolean isMember);
 
-    WotCertification getCertifiedBy(long currencyId, String uid);
+    WotCertification getCertifiedBy(String currencyId, String uid);
 
-    int countValidCertifiers(long currencyId, String pubkey);
+    int countValidCertifiers(String currencyId, String pubkey);
     
-    WotCertification getCertifiersOf(long currencyId, String uid);
+    WotCertification getCertifiersOf(String currencyId, String uid);
 
     String getSignedIdentity(String currency, byte[] pubKey, byte[] secKey, String uid, String blockUid);
 
-    Map<String, String> getMembersUids(long currencyId);
+    Map<String, String> getMembersUids(String currencyId);
 
     Map<String, String> getMembersUids(Peer peer);
 
-    void sendIdentity(long currencyId, byte[] pubKey, byte[] secKey, String uid, String blockUid);
+    void sendIdentity(String currencyId, byte[] pubKey, byte[] secKey, String uid, String blockUid);
 
     void sendIdentity(Peer peer, String currency, byte[] pubKey, byte[] secKey, String uid, String blockUid);
 
@@ -79,7 +79,7 @@ public interface WotRemoteService extends Service {
 
     String sendCertification(Wallet wallet, Identity identity);
 
-    String sendCertification(long currencyId,
+    String sendCertification(String currencyId,
                                     byte[] pubKey, byte[] secKey,
                                   String uid, String timestamp,
                                   String userUid, String userPubKeyHash,
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/WotRemoteServiceImpl.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/WotRemoteServiceImpl.java
index 863e6e07aa8de33b579d75095e158a52cee4c1b7..f2869f899cf87c58ddee69ee2d8746fc8f6bfbda 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/WotRemoteServiceImpl.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/bma/WotRemoteServiceImpl.java
@@ -87,12 +87,12 @@ public class WotRemoteServiceImpl extends BaseRemoteServiceImpl implements WotRe
         currencyService = ServiceLocator.instance().getCurrencyService();
     }
 
-    public List<Identity> findIdentities(Set<Long> currenciesIds, String uidOrPubKey) {
+    public List<Identity> findIdentities(Set<String> currenciesIds, String uidOrPubKey) {
         List<Identity> result = new ArrayList<Identity>();
 
         String path = String.format(URL_LOOKUP, uidOrPubKey);
 
-        for (Long currencyId: currenciesIds) {
+        for (String currencyId: currenciesIds) {
 
             WotLookup lookupResult = executeRequest(currencyId, path, WotLookup.class);
 
@@ -102,7 +102,7 @@ public class WotRemoteServiceImpl extends BaseRemoteServiceImpl implements WotRe
         return result;
     }
 
-    public WotLookup.Uid find(long currencyId, String uidOrPubKey) {
+    public WotLookup.Uid find(String currencyId, String uidOrPubKey) {
         if (log.isDebugEnabled()) {
             log.debug(String.format("Try to find user by looking up on [%s]", uidOrPubKey));
         }
@@ -122,7 +122,7 @@ public class WotRemoteServiceImpl extends BaseRemoteServiceImpl implements WotRe
 
     }
 
-    public Map<String, String> getMembersUids(long currencyId) {
+    public Map<String, String> getMembersUids(String currencyId) {
         // get /wot/members
         JsonNode json = executeRequest(currencyId, URL_MEMBERS, JsonNode.class);
 
@@ -151,7 +151,7 @@ public class WotRemoteServiceImpl extends BaseRemoteServiceImpl implements WotRe
     }
 
 
-    public void getRequirments(long currencyId, String pubKey) {
+    public void getRequirments(String currencyId, String pubKey) {
         if (log.isDebugEnabled()) {
             log.debug(String.format("Try to find user requirements on [%s]", pubKey));
         }
@@ -162,7 +162,7 @@ public class WotRemoteServiceImpl extends BaseRemoteServiceImpl implements WotRe
 
     }
 
-    public WotLookup.Uid findByUid(long currencyId, String uid) {
+    public WotLookup.Uid findByUid(String currencyId, String uid) {
         if (log.isDebugEnabled()) {
             log.debug(String.format("Try to find user info by uid: %s", uid));
         }
@@ -180,7 +180,7 @@ public class WotRemoteServiceImpl extends BaseRemoteServiceImpl implements WotRe
         return uniqueResult;
     }
 
-    public WotLookup.Uid findByUidAndPublicKey(long currencyId, String uid, String pubKey) {
+    public WotLookup.Uid findByUidAndPublicKey(String currencyId, String uid, String pubKey) {
         if (log.isDebugEnabled()) {
             log.debug(String.format("Try to find user info by uid [%s] and pubKey [%s]", uid, pubKey));
         }
@@ -216,7 +216,7 @@ public class WotRemoteServiceImpl extends BaseRemoteServiceImpl implements WotRe
         return uniqueResult;
     }
 
-    public Identity getIdentity(long currencyId, String uid, String pubKey) {
+    public Identity getIdentity(String currencyId, String uid, String pubKey) {
         if (log.isDebugEnabled()) {
             log.debug(String.format("Get identity by uid [%s] and pubKey [%s]", uid, pubKey));
         }
@@ -228,7 +228,7 @@ public class WotRemoteServiceImpl extends BaseRemoteServiceImpl implements WotRe
         return toIdentity(lookupUid);
     }
 
-    public Identity getIdentity(long currencyId, String pubKey) {
+    public Identity getIdentity(String currencyId, String pubKey) {
 //        Log.d(TAG, String.format("Get identity by uid [%s] and pubKey [%s]", uid, pubKey));
 
         WotLookup.Uid lookupUid = find(currencyId, pubKey);
@@ -253,7 +253,7 @@ public class WotRemoteServiceImpl extends BaseRemoteServiceImpl implements WotRe
         return toIdentity(lookupUid);
     }
 
-    public Collection<Certification> getCertifications(long currencyId, String uid, String pubkey, boolean isMember) {
+    public Collection<Certification> getCertifications(String currencyId, String uid, String pubkey, boolean isMember) {
         Preconditions.checkNotNull(uid);
         Preconditions.checkNotNull(pubkey);
 
@@ -266,7 +266,7 @@ public class WotRemoteServiceImpl extends BaseRemoteServiceImpl implements WotRe
     }
 
 
-    public WotCertification getCertifiedBy(long currencyId, String uid) {
+    public WotCertification getCertifiedBy(String currencyId, String uid) {
         if (log.isDebugEnabled()) {
             log.debug(String.format("Try to get certifications done by uid: %s", uid));
         }
@@ -279,7 +279,7 @@ public class WotRemoteServiceImpl extends BaseRemoteServiceImpl implements WotRe
 
     }
 
-    public int countValidCertifiers(long currencyId, String pubkey) {
+    public int countValidCertifiers(String currencyId, String pubkey) {
         if (log.isDebugEnabled()) {
             log.debug(String.format("Try to count valid certifications done by pubkey: %s", pubkey));
         }
@@ -302,7 +302,7 @@ public class WotRemoteServiceImpl extends BaseRemoteServiceImpl implements WotRe
 
     }
     
-    public WotCertification getCertifiersOf(long currencyId, String uid) {
+    public WotCertification getCertifiersOf(String currencyId, String uid) {
         if (log.isDebugEnabled()) {
             log.debug(String.format("Try to get certifications done to uid: %s", uid));
         }
@@ -315,7 +315,7 @@ public class WotRemoteServiceImpl extends BaseRemoteServiceImpl implements WotRe
     }
 
 
-    public void sendIdentity(long currencyId, byte[] pubKey, byte[] secKey, String userId, String blockUid) {
+    public void sendIdentity(String currencyId, byte[] pubKey, byte[] secKey, String userId, String blockUid) {
         // http post /wot/add
         HttpPost httpPost = new HttpPost(getPath(currencyId, URL_ADD));
 
@@ -373,7 +373,7 @@ public class WotRemoteServiceImpl extends BaseRemoteServiceImpl implements WotRe
                     identity.getSignature());
     }
 
-    public String sendCertification(long currencyId,
+    public String sendCertification(String currencyId,
                                     byte[] pubKey, byte[] secKey,
                                   String uid, String timestamp,
                                   String userUid, String userPubKeyHash,
@@ -419,11 +419,7 @@ public class WotRemoteServiceImpl extends BaseRemoteServiceImpl implements WotRe
         return selfResult;
     }
 
-    public void addAllIdentities(List<Identity> result, WotLookup lookupResults, Long currencyId) {
-        String currencyName = null;
-        if (currencyId != null) {
-            currencyName = ServiceLocator.instance().getCurrencyService().getCurrencyNameById(currencyId);
-        }
+    public void addAllIdentities(List<Identity> result, WotLookup lookupResults, String currencyName) {
 
         for (WotLookup.Result lookupResult: lookupResults.getResults()) {
             String pubKey = lookupResult.getPubkey();
@@ -437,8 +433,7 @@ public class WotRemoteServiceImpl extends BaseRemoteServiceImpl implements WotRe
 
                 // Fill currency id and name
                 // TODO
-                //target.setCurrencyId(currencyId);
-                //target.setCurrency(currencyName);
+                target.setCurrencyId(currencyName);
 
                 result.add(target);
             }
@@ -476,7 +471,7 @@ public class WotRemoteServiceImpl extends BaseRemoteServiceImpl implements WotRe
 
     /* -- Internal methods -- */
 
-    protected Collection<Certification> getCertificationsByPubkeyForMember(long currencyId, String pubkey, boolean onlyCertifiersOf) {
+    protected Collection<Certification> getCertificationsByPubkeyForMember(String currencyId, String pubkey, boolean onlyCertifiersOf) {
 
         BlockchainParameters bcParameter = bcService.getParameters(currencyId, true);
         BlockchainBlock currentBlock = bcService.getCurrentBlock(currencyId, true);
@@ -557,10 +552,10 @@ public class WotRemoteServiceImpl extends BaseRemoteServiceImpl implements WotRe
         return result;
     }
 
-    protected Collection<Certification> getCertificationsByPubkeyForNonMember(long currencyId, final String uid, final String pubkey) {
+    protected Collection<Certification> getCertificationsByPubkeyForNonMember(String currencyId, final String uid, final String pubkey) {
         // Ordered list, by uid/pubkey/cert time
 
-        Collection<Certification> result = new TreeSet<Certification>(ModelUtils.newWotCertificationComparatorByUid());
+        Collection<Certification> result = new TreeSet<>(ModelUtils.newWotCertificationComparatorByUid());
 
         if (log.isDebugEnabled()) {
             log.debug(String.format("Get non member WOT, by uid [%s] and pubKey [%s]", uid, pubkey));
@@ -574,7 +569,7 @@ public class WotRemoteServiceImpl extends BaseRemoteServiceImpl implements WotRe
         WotLookup.Uid lookupUId = getUidByUidAndPublicKey(lookupResults, uid, pubkey);
 
         // Read certifiers, if any
-        Map<String, Certification> certifierByPubkeys = new HashMap<String, Certification>();
+        Map<String, Certification> certifierByPubkeys = new HashMap<>();
         if (lookupUId != null && lookupUId.getOthers() != null) {
             for(WotLookup.OtherSignature lookupSignature: lookupUId.getOthers()) {
                 Collection<Certification> certifiers = toCertifierCertifications(lookupSignature, currencyId);
@@ -749,7 +744,7 @@ public class WotRemoteServiceImpl extends BaseRemoteServiceImpl implements WotRe
         return null;
     }
 
-    private Certification toCertification(final WotCertification.Certification source, final long currencyId) {
+    private Certification toCertification(final WotCertification.Certification source, final String currencyId) {
         Certification target = new Certification();
         target.setPubkey(source.getPubkey());
         target.setUid(source.getUid());
@@ -759,7 +754,7 @@ public class WotRemoteServiceImpl extends BaseRemoteServiceImpl implements WotRe
         return target;
     }
 
-    private Collection<Certification> toCertifierCertifications(final WotLookup.OtherSignature source, final long currencyId) {
+    private Collection<Certification> toCertifierCertifications(final WotLookup.OtherSignature source, final String currencyId) {
         List<Certification> result = new ArrayList<Certification>();
         // If only one uid
         if (source.getUids().length == 1) {
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/CurrencyService.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/CurrencyService.java
index f9cbe365dfb7a0cd06de41f09eae007717013fc9..45b2e7916a66befe2b2640d4ec964ec19db1b2f8 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/CurrencyService.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/CurrencyService.java
@@ -38,27 +38,27 @@ public interface CurrencyService extends Service {
 
     List<Currency> getCurrencies(long accountId);
 
-    Currency getCurrencyById(long currencyId);
+    Currency getCurrencyById(String currencyId);
 
     /**
      * Return a (cached) currency name, by id
      * @param currencyId
      * @return
      */
-    String getCurrencyNameById(long currencyId);
+    String getCurrencyNameById(String currencyId);
 
     /**
      * Return a currency id, by name
      * @param currencyName
      * @return
      */
-    Long getCurrencyIdByName(String currencyName);
+    String getCurrencyIdByName(String currencyName);
 
     /**
      * Return a (cached) list of currency ids
      * @return
      */
-    Set<Long> getCurrencyIds();
+    Set<String> getCurrencyIds();
 
     /**
      * Return a (cached) number of registered currencies
@@ -77,17 +77,17 @@ public interface CurrencyService extends Service {
      * @param currencyId
      * @return
      */
-    long getLastUD(long currencyId);
+    long getLastUD(String currencyId);
 
     /**
      * Return a map of UD (key=blockNumber, value=amount)
      * @return
      */
-    Map<Integer, Long> refreshAndGetUD(long currencyId, long lastSyncBlockNumber);
+    Map<Integer, Long> refreshAndGetUD(String currencyId, long lastSyncBlockNumber);
 
     /**
      * Return a map of UD (key=blockNumber, value=amount)
      * @return
      */
-     Map<Integer, Long> getAllUD(long currencyId);
+     Map<Integer, Long> getAllUD(String currencyId);
 }
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/CurrencyServiceImpl.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/CurrencyServiceImpl.java
index 91b9be1f13544a194b66bc144936cdfee35c57e6..50fcb9b14daf3c10cd75f02516a400a06fc54f44 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/CurrencyServiceImpl.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/CurrencyServiceImpl.java
@@ -47,8 +47,8 @@ public class CurrencyServiceImpl implements CurrencyService, InitializingBean {
 
     private static final long UD_CACHE_TIME_MILLIS = 5 * 60 * 1000; // = 5 min
 
-    private Cache<Long, Currency> mCurrencyCache;
-    private Cache<Long, Long> mUDCache;
+    private Cache<String, Currency> mCurrencyCache;
+    private Cache<String, Long> mUDCache;
 
     private BlockchainRemoteService blockchainRemoteService;
     private CurrencyDao currencyDao;
@@ -84,9 +84,6 @@ public class CurrencyServiceImpl implements CurrencyService, InitializingBean {
         ObjectUtils.checkNotNull(currency.getLastUD());
         ObjectUtils.checkArgument(currency.getLastUD().longValue() > 0);
 
-        ObjectUtils.checkArgument((currency.getAccount() != null && currency.getAccount().getId() != null)
-            || currency.getAccountId() != null, "One of 'currency.account.id' or 'currency.accountId' is mandatory.");
-
         Currency result;
 
         // Create
@@ -114,7 +111,7 @@ public class CurrencyServiceImpl implements CurrencyService, InitializingBean {
     }
 
 
-    public Currency getCurrencyById(long currencyId) {
+    public Currency getCurrencyById(String currencyId) {
         return mCurrencyCache.get(currencyId);
     }
 
@@ -123,7 +120,7 @@ public class CurrencyServiceImpl implements CurrencyService, InitializingBean {
      * @param currencyId
      * @return
      */
-    public String getCurrencyNameById(long currencyId) {
+    public String getCurrencyNameById(String currencyId) {
         Currency currency = mCurrencyCache.getIfPresent(currencyId);
         if (currency == null) {
             return null;
@@ -136,11 +133,11 @@ public class CurrencyServiceImpl implements CurrencyService, InitializingBean {
      * @param currencyName
      * @return
      */
-    public Long getCurrencyIdByName(String currencyName) {
+    public String getCurrencyIdByName(String currencyName) {
         Preconditions.checkArgument(StringUtils.isNotBlank(currencyName));
 
         // Search from currencies
-        for (Map.Entry<Long, Currency> entry : mCurrencyCache.entrySet()) {
+        for (Map.Entry<String, Currency> entry : mCurrencyCache.entrySet()) {
             Currency currency = entry.getValue();
             if (ObjectUtils.equals(currencyName, currency.getCurrencyName())) {
                 return entry.getKey();
@@ -153,7 +150,7 @@ public class CurrencyServiceImpl implements CurrencyService, InitializingBean {
      * Return a (cached) list of currency ids
      * @return
      */
-    public Set<Long> getCurrencyIds() {
+    public Set<String> getCurrencyIds() {
         return mCurrencyCache.keySet();
     }
 
@@ -176,9 +173,9 @@ public class CurrencyServiceImpl implements CurrencyService, InitializingBean {
             List<Currency> currencies = getCurrencies(accountId);
             if (mCurrencyCache == null) {
 
-                mCurrencyCache = new SimpleCache<Long, Currency>() {
+                mCurrencyCache = new SimpleCache<String, Currency>() {
                     @Override
-                    public Currency load(Long currencyId) {
+                    public Currency load(String currencyId) {
                         return currencyDao.getById(currencyId);
                     }
                 };
@@ -192,9 +189,9 @@ public class CurrencyServiceImpl implements CurrencyService, InitializingBean {
             // Create the UD cache
             if (mUDCache == null) {
 
-                mUDCache = new SimpleCache<Long, Long>(UD_CACHE_TIME_MILLIS) {
+                mUDCache = new SimpleCache<String, Long>(UD_CACHE_TIME_MILLIS) {
                     @Override
-                    public Long load(final Long currencyId) {
+                    public Long load(final String currencyId) {
                         // Retrieve the last UD from the blockchain
                         final Long lastUD = blockchainRemoteService.getLastUD(currencyId);
 
@@ -217,7 +214,7 @@ public class CurrencyServiceImpl implements CurrencyService, InitializingBean {
      * @param currencyId
      * @return
      */
-    public long getLastUD(long currencyId) {
+    public long getLastUD(String currencyId) {
         return mUDCache.get(currencyId);
     }
 
@@ -225,7 +222,7 @@ public class CurrencyServiceImpl implements CurrencyService, InitializingBean {
      * Return a map of UD (key=blockNumber, value=amount)
      * @return
      */
-    public Map<Integer, Long> refreshAndGetUD(long currencyId, long lastSyncBlockNumber) {
+    public Map<Integer, Long> refreshAndGetUD(String currencyId, long lastSyncBlockNumber) {
 
         // Retrieve new UDs from blockchain
         Map<Integer, Long> newUDs = blockchainRemoteService.getUDs(currencyId, lastSyncBlockNumber + 1);
@@ -242,7 +239,7 @@ public class CurrencyServiceImpl implements CurrencyService, InitializingBean {
      * Return a map of UD (key=blockNumber, value=amount)
      * @return
      */
-    public Map<Integer, Long> getAllUD(long currencyId) {
+    public Map<Integer, Long> getAllUD(String currencyId) {
         return currencyDao.getAllUD(currencyId);
     }
 
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/NetworkServiceImpl.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/NetworkServiceImpl.java
index 7aacd7313a7f0c7ec208c0aea3b1ef5af040c231..648f83b87d55d10e40b5aa5bec18a114acea6154 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/NetworkServiceImpl.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/NetworkServiceImpl.java
@@ -48,8 +48,10 @@ import org.slf4j.LoggerFactory;
 import java.io.IOException;
 import java.util.*;
 import java.util.concurrent.*;
+import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
+import java.util.function.Supplier;
 import java.util.stream.Collectors;
 
 /**
@@ -277,101 +279,140 @@ public class NetworkServiceImpl extends BaseRemoteServiceImpl implements Network
         final Comparator<Peer> peerComparator = peerComparator(sort);
         final ExecutorService pool = (executor != null) ? executor : ForkJoinPool.commonPool();
 
-        // Manage new block event
-        blockchainRemoteService.addBlockListener(mainPeer, json -> {
+        /*Runnable initCacheRunnable = () -> {
             if (threadLock.isLocked()) return;
             synchronized (threadLock) {
                 threadLock.lock();
             }
             try {
-                BlockchainBlock block = readValue(json, BlockchainBlock.class);
-                String blockBuid = buid(block);
-                boolean isNewBlock = (blockBuid != null && !knownBlocks.contains(blockBuid));
+                // TODO : load all peers from DAO, then fill list ?
+            }
+            catch(Exception e) {
+                log.error("Error while loading all peers: " + e.getMessage(), e);
+            }
+            finally {
+                synchronized (threadLock) {
+                    threadLock.unlock();
+                }
+            }
+        };*/
 
-                // If new block + wait 5s for network propagation
-                if (isNewBlock && waitSafe(5000)) {
-                    List<Peer> updatedPeers = getPeers(mainPeer, filter, sort);
-
-                    knownPeers.clear();
-                    updatedPeers.stream().forEach(peer -> {
-                        String buid = buid(peer.getStats());
-                        if (!knownBlocks.contains(buid)) {
-                            knownBlocks.add(buid);
-                        }
-                        knownPeers.put(peer.toString(), peer);
-                    });
+        Runnable getPeersRunnable = () -> {
+            if (threadLock.isLocked()) return;
+            synchronized (threadLock) {
+                threadLock.lock();
+            }
+            try {
+                List<Peer> updatedPeers = getPeers(mainPeer, filter, sort);
 
-                    result.clear();
-                    result.addAll(updatedPeers);
-                    listener.onChanged(result);
-                }
-            } catch(IOException e) {
-                log.error("Could not parse peer received by WS: " + e.getMessage(), e);
+                knownPeers.clear();
+                updatedPeers.stream().forEach(peer -> {
+                    String buid = buid(peer.getStats());
+                    if (!knownBlocks.contains(buid)) {
+                        knownBlocks.add(buid);
+                    }
+                    knownPeers.put(peer.toString(), peer);
+                });
+
+                result.clear();
+                result.addAll(updatedPeers);
+                listener.onChanged(result);
+            }
+            catch(Exception e) {
+                log.error("Error while loading all peers: " + e.getMessage(), e);
             }
             finally {
                 synchronized (threadLock) {
                     threadLock.unlock();
                 }
             }
-        }, autoreconnect);
+        };
 
-        // Manage new peer event
-        networkRemoteService.addPeerListener(mainPeer, json -> {
+        Consumer<NetworkPeers.Peer> refreshPeerConsumer = (bmaPeer) -> {
             if (threadLock.isLocked()) return;
             synchronized (threadLock) {
                 threadLock.lock();
             }
-
             try {
-                NetworkPeers.Peer bmaPeer = readValue(json, NetworkPeers.Peer.class);
                 final List<Peer> newPeers = new ArrayList<>();
                 addEndpointsAsPeers(bmaPeer, newPeers, null);
 
                 CompletableFuture<List<CompletableFuture<Peer>>> jobs =
                         CompletableFuture.supplyAsync(() -> wotRemoteService.getMembersUids(mainPeer), pool)
-                    .thenApply(memberUids ->
-                        newPeers.stream().map(peer ->
-                            asyncRefreshPeer(peer, memberUids, pool))
-                            .collect(Collectors.toList())
-                    );
+                                .thenApply(memberUids ->
+                                        newPeers.stream().map(peer ->
+                                                asyncRefreshPeer(peer, memberUids, pool))
+                                                .collect(Collectors.toList())
+                                );
                 jobs.thenCompose(refreshedPeersFuture -> CompletableFutures.allOfToList(refreshedPeersFuture, refreshedPeer -> {
-                        boolean exists = knownPeers.containsKey(refreshedPeer.toString());
-                        boolean include = peerFilter.test(refreshedPeer);
-                        if (!include && exists) {
-                            Peer removedPeer = knownPeers.remove(refreshedPeer.toString());
-                            result.remove(removedPeer);
-                        }
-                        else if (include && exists) {
-                            result.remove(knownPeers.get(refreshedPeer.toString()));
-                        }
-                        return include;
-                    }))
-                    .thenApply(addedPeers -> {
-                        result.addAll(addedPeers);
-                        fillPeerStatsConsensus(result);
-                        result.sort(peerComparator);
-
-                        result.stream().forEach(peer -> {
-                            String buid = buid(peer.getStats());
-                            if (!knownBlocks.contains(buid)) {
-                                knownBlocks.add(buid);
-                            }
-                            knownPeers.put(peer.toString(), peer);
+                    boolean exists = knownPeers.containsKey(refreshedPeer.toString());
+                    boolean include = peerFilter.test(refreshedPeer);
+                    if (!include && exists) {
+                        Peer removedPeer = knownPeers.remove(refreshedPeer.toString());
+                        result.remove(removedPeer);
+                    }
+                    else if (include && exists) {
+                        result.remove(knownPeers.get(refreshedPeer.toString()));
+                    }
+                    return include;
+                }))
+                        .thenApply(addedPeers -> {
+                            result.addAll(addedPeers);
+                            fillPeerStatsConsensus(result);
+                            result.sort(peerComparator);
+
+                            result.stream().forEach(peer -> {
+                                String buid = buid(peer.getStats());
+                                if (!knownBlocks.contains(buid)) {
+                                    knownBlocks.add(buid);
+                                }
+                                knownPeers.put(peer.toString(), peer);
+                            });
+
+                            listener.onChanged(result);
+                            return result;
                         });
-
-                        listener.onChanged(result);
-                        return result;
-                    });
-
-            } catch(IOException e) {
-                log.error("Could not parse peer received by WS: " + e.getMessage(), e);
+            }
+            catch(Exception e) {
+                log.error("Error while refreshing a peer: " + e.getMessage(), e);
             }
             finally {
                 synchronized (threadLock) {
                     threadLock.unlock();
                 }
             }
+        };
+
+        // Load all peers
+        pool.submit(getPeersRunnable);
+
+        // Manage new block event
+        blockchainRemoteService.addBlockListener(mainPeer, json -> {
+
+            log.debug("Received new block event");
+            try {
+                BlockchainBlock block = readValue(json, BlockchainBlock.class);
+                String blockBuid = buid(block);
+                boolean isNewBlock = (blockBuid != null && !knownBlocks.contains(blockBuid));
+                // If new block + wait 5s for network propagation
+                if (!isNewBlock) return;
+            } catch(IOException e) {
+                log.error("Could not parse peer received by WS: " + e.getMessage(), e);
+            }
 
+            schedule(getPeersRunnable, pool, 5000);
+        }, autoreconnect);
+
+        // Manage new peer event
+        networkRemoteService.addPeerListener(mainPeer, json -> {
+
+            log.debug("Received new peer event");
+            try {
+                final NetworkPeers.Peer bmaPeer = readValue(json, NetworkPeers.Peer.class);
+                pool.submit(() -> refreshPeerConsumer.accept(bmaPeer));
+            } catch(IOException e) {
+                log.error("Could not parse peer received by WS: " + e.getMessage(), e);
+            }
         }, autoreconnect);
     }
 
@@ -624,12 +665,18 @@ public class NetworkServiceImpl extends BaseRemoteServiceImpl implements Network
         return block.getNumber() + "-" + block.getHash();
     }
 
-    protected boolean waitSafe(long duration) {
-        try {
-            Thread.sleep(duration);
-            return true;
-        } catch (InterruptedException e) {
-            return false;
+    protected void schedule(Runnable command, ExecutorService pool, long delayInMs) {
+        if (pool instanceof ScheduledExecutorService) {
+            ((ScheduledExecutorService)pool).schedule(command, delayInMs, TimeUnit.MILLISECONDS);
+        }
+        else {
+            pool.submit(() -> {
+                try {
+                    Thread.sleep(delayInMs);
+                    command.run();
+                } catch (InterruptedException e) {
+                }
+            });
         }
     }
 }
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/PeerService.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/PeerService.java
index 103e8a2b0d4f61fa3f2279b8d776430f7633b30e..f800a82916d2ecf4fa69f0391eda6b6f6560ca39 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/PeerService.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/PeerService.java
@@ -34,21 +34,19 @@ public interface PeerService extends Service {
 
     Peer save(final Peer peer);
 
-    Peer getPeerById(long peerId);
-
     /**
      * Return a (cached) active peer, by currency id
      * @param currencyId
      * @return
      */
-    Peer getActivePeerByCurrencyId(long currencyId);
+    Peer getActivePeerByCurrencyId(String currencyId);
 
     /**
      * Return a (cached) peer list, by currency id
      * @param currencyId
      * @return
      */
-    List<Peer> getPeersByCurrencyId(long currencyId);
+    List<Peer> getPeersByCurrencyId(String currencyId);
 
     /**
      * Fill all cache need for currencies
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/PeerServiceImpl.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/PeerServiceImpl.java
index f724f1576d1f9f65ac86da21037fbabeb5b45027..ad9f7b736c4b816fe20058b7a3bf8f8082f28512 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/PeerServiceImpl.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/PeerServiceImpl.java
@@ -44,8 +44,8 @@ import java.util.List;
  */
 public class PeerServiceImpl implements PeerService, InitializingBean {
 
-    private Cache<Long, List<Peer>> peersByCurrencyIdCache;
-    private Cache<Long, Peer> activePeerByCurrencyIdCache;
+    private Cache<String, List<Peer>> peersByCurrencyIdCache;
+    private Cache<String, Peer> activePeerByCurrencyIdCache;
 
     private CurrencyService currencyService;
     private PeerDao peerDao;
@@ -70,7 +70,7 @@ public class PeerServiceImpl implements PeerService, InitializingBean {
 
     public Peer save(final Peer peer) {
         Preconditions.checkNotNull(peer);
-        Preconditions.checkNotNull(peer.getCurrencyId());
+        Preconditions.checkNotNull(peer.getCurrency());
         Preconditions.checkArgument(StringUtils.isNotBlank(peer.getHost()));
         Preconditions.checkArgument(peer.getPort() >= 0);
 
@@ -88,10 +88,10 @@ public class PeerServiceImpl implements PeerService, InitializingBean {
 
         // update cache (if already loaded)
         if (peersByCurrencyIdCache != null) {
-            List<Peer> peers = peersByCurrencyIdCache.get(peer.getCurrencyId());
+            List<Peer> peers = peersByCurrencyIdCache.get(peer.getCurrency());
             if (peers == null) {
-                peers = new ArrayList<Peer>();
-                peersByCurrencyIdCache.put(peer.getCurrencyId(), peers);
+                peers = new ArrayList<>();
+                peersByCurrencyIdCache.put(peer.getCurrency(), peers);
                 peers.add(peer);
             }
             else if (!peers.contains(peer)) {
@@ -102,23 +102,18 @@ public class PeerServiceImpl implements PeerService, InitializingBean {
         return result;
     }
 
-
-    public Peer getPeerById(long peerId) {
-        return peerDao.getById(peerId);
-    }
-
-    /**
+   /**
      * Return a (cached) active peer, by currency id
      * @param currencyId
      * @return
      */
-    public Peer getActivePeerByCurrencyId(long currencyId) {
+    public Peer getActivePeerByCurrencyId(String currency) {
         // Check if cache as been loaded
         if (activePeerByCurrencyIdCache == null) {
 
-            activePeerByCurrencyIdCache = new SimpleCache<Long, Peer>() {
+            activePeerByCurrencyIdCache = new SimpleCache<String, Peer>() {
                 @Override
-                public Peer load(Long currencyId) {
+                public Peer load(String currencyId) {
                     List<Peer> peers = peerDao.getPeersByCurrencyId(currencyId);
                     if (CollectionUtils.isEmpty(peers)) {
                         String currencyName = currencyService.getCurrencyNameById(currencyId);
@@ -132,7 +127,7 @@ public class PeerServiceImpl implements PeerService, InitializingBean {
             };
         }
 
-        return activePeerByCurrencyIdCache.get(currencyId);
+        return activePeerByCurrencyIdCache.get(currency);
     }
 
     /**
@@ -140,7 +135,7 @@ public class PeerServiceImpl implements PeerService, InitializingBean {
      * @param currencyId
      * @return
      */
-    public List<Peer> getPeersByCurrencyId(long currencyId) {
+    public List<Peer> getPeersByCurrencyId(String currencyId) {
         // Check if cache as been loaded
         if (peersByCurrencyIdCache == null) {
             throw new TechnicalException("Cache not initialize. Please call loadCache() before getPeersByCurrencyId().");
@@ -158,9 +153,9 @@ public class PeerServiceImpl implements PeerService, InitializingBean {
             return;
         }
 
-        peersByCurrencyIdCache = new SimpleCache<Long, List<Peer>>() {
+        peersByCurrencyIdCache = new SimpleCache<String, List<Peer>>() {
             @Override
-            public List<Peer> load(Long currencyId) {
+            public List<Peer> load(String currencyId) {
                 return peerDao.getPeersByCurrencyId(currencyId);
             }
         };
diff --git a/duniter4j-core-client/src/test/java/org/duniter/core/client/TestFixtures.java b/duniter4j-core-client/src/test/java/org/duniter/core/client/TestFixtures.java
index a08ab9ca7130c8d85bcd5e5c508c86739a9e08b8..f57e8a39845def9fe0a3e6b7783c3b597f507e87 100644
--- a/duniter4j-core-client/src/test/java/org/duniter/core/client/TestFixtures.java
+++ b/duniter4j-core-client/src/test/java/org/duniter/core/client/TestFixtures.java
@@ -25,8 +25,7 @@ package org.duniter.core.client;
 
 public class TestFixtures extends org.duniter.core.test.TestFixtures {
 
-
-    public long getDefaultCurrencyId() {
-        return -1;
+    public String getDefaultCurrency() {
+        return "g1";
     }
 }
diff --git a/duniter4j-core-client/src/test/java/org/duniter/core/client/TestResource.java b/duniter4j-core-client/src/test/java/org/duniter/core/client/TestResource.java
index 1029dadb1f1e56926ef3f159918356cf17304957..7c8301261e62e827713a6f3eb1d2bbcef19ac9cf 100644
--- a/duniter4j-core-client/src/test/java/org/duniter/core/client/TestResource.java
+++ b/duniter4j-core-client/src/test/java/org/duniter/core/client/TestResource.java
@@ -105,7 +105,6 @@ public class TestResource extends org.duniter.core.test.TestResource {
      * Convenience methods that could be override to initialize other configuration
      *
      * @param configFilename
-     * @param configArgs
      */
     protected void initConfiguration(String configFilename) {
         String[] configArgs = getConfigArgs();
@@ -159,7 +158,7 @@ public class TestResource extends org.duniter.core.test.TestResource {
                 .setHost(config.getNodeHost())
                 .setPort(config.getNodePort())
                 .build();
-        peer.setCurrencyId(fixtures.getDefaultCurrencyId());
+        peer.setCurrency(fixtures.getDefaultCurrency());
 
         ServiceLocator.instance().getPeerService().save(peer);
 
diff --git a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/TransactionRemoteServiceTest.java b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/TransactionRemoteServiceTest.java
index 3118e7f373661f1c9060abbd5182999c69510331..42f6bb05cbd76bf60a573ae44e296b100529611c 100644
--- a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/TransactionRemoteServiceTest.java
+++ b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/TransactionRemoteServiceTest.java
@@ -84,7 +84,7 @@ public class TransactionRemoteServiceTest {
 				CryptoUtils.decodeBase58(resource.getFixtures().getUserPublicKey()),
 				CryptoUtils.decodeBase58(resource.getFixtures().getUserSecretKey()));
 
-        wallet.setCurrencyId(resource.getFixtures().getDefaultCurrencyId());
+        wallet.setCurrencyId(resource.getFixtures().getDefaultCurrency());
 
 		return wallet;
 	}
diff --git a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/WotRemoteServiceTest.java b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/WotRemoteServiceTest.java
index 3415c584aa530991466f2f7a1580f82fa7eb9362..bb6ea3a72b4593968c9d5c8f9ff8ba490e50b629 100644
--- a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/WotRemoteServiceTest.java
+++ b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/WotRemoteServiceTest.java
@@ -60,8 +60,8 @@ public class WotRemoteServiceTest {
 
 	@Test
 	public void getIdentity() throws Exception {
-        Set<Long> currencyIds = new HashSet<>();
-        currencyIds.add(resource.getFixtures().getDefaultCurrencyId());
+        Set<String> currencyIds = new HashSet<>();
+        currencyIds.add(resource.getFixtures().getDefaultCurrency());
 		List<Identity> result = service
 				.findIdentities(currencyIds, resource.getFixtures().getUid());
 		Assert.assertNotNull(result);
@@ -71,7 +71,7 @@ public class WotRemoteServiceTest {
 	@Test
 	public void findByUid() throws Exception {
 		WotLookup.Uid result = service
-				.findByUid(resource.getFixtures().getDefaultCurrencyId(),
+				.findByUid(resource.getFixtures().getDefaultCurrency(),
 						resource.getFixtures().getUid());
 		Assert.assertNotNull(result);
 	}
@@ -82,7 +82,7 @@ public class WotRemoteServiceTest {
 	public void getCertifiedBy() throws Exception {
 		WotRemoteService service = ServiceLocator.instance().getWotRemoteService();
 		WotCertification result = service.getCertifiedBy(
-				resource.getFixtures().getDefaultCurrencyId(),
+				resource.getFixtures().getDefaultCurrency(),
 				resource.getFixtures().getUid());
 
 		Assert.assertNotNull(result);
@@ -111,7 +111,7 @@ public class WotRemoteServiceTest {
 	// FIXME: user 'gab' has no certification
 	public void getCertifiersOf() throws Exception {
 		WotCertification result = service.getCertifiersOf(
-				resource.getFixtures().getDefaultCurrencyId(),
+				resource.getFixtures().getDefaultCurrency(),
 				resource.getFixtures().getUid());
 
 		Assert.assertNotNull(result);
diff --git a/duniter4j-core-shared/pom.xml b/duniter4j-core-shared/pom.xml
index f826f638bebf7a4043a5a65f45775f4d288a7037..ebad633fad0b2fd6886fe70e1614c9e227228faf 100644
--- a/duniter4j-core-shared/pom.xml
+++ b/duniter4j-core-shared/pom.xml
@@ -7,7 +7,6 @@
     <version>0.9.2-SNAPSHOT</version>
   </parent>
 
-  <groupId>org.duniter</groupId>
   <artifactId>duniter4j-core-shared</artifactId>
   <packaging>jar</packaging>
   <name>Duniter4j :: Core Shared</name>
diff --git a/duniter4j-core-shared/src/main/java/org/duniter/core/beans/BeanCreationException.java b/duniter4j-core-shared/src/main/java/org/duniter/core/beans/BeanCreationException.java
new file mode 100644
index 0000000000000000000000000000000000000000..624ce7e40e9c96c4af9c8cfe1ac5bffbc5382291
--- /dev/null
+++ b/duniter4j-core-shared/src/main/java/org/duniter/core/beans/BeanCreationException.java
@@ -0,0 +1,28 @@
+package org.duniter.core.beans;
+
+import org.duniter.core.exception.TechnicalException;
+
+/**
+ * Created by blavenie on 31/03/17.
+ */
+public class BeanCreationException extends TechnicalException {
+
+    public BeanCreationException() {
+    }
+
+    public BeanCreationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+
+    public BeanCreationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public BeanCreationException(String message) {
+        super(message);
+    }
+
+    public BeanCreationException(Throwable cause) {
+        super(cause);
+    }
+}
diff --git a/duniter4j-core-shared/src/main/java/org/duniter/core/beans/BeanFactory.java b/duniter4j-core-shared/src/main/java/org/duniter/core/beans/BeanFactory.java
index 4567cea4a8f677485eb0249ba5a7ed16f1293b80..7284e9f15b721f033614f3db4afdfe9abe8b358e 100644
--- a/duniter4j-core-shared/src/main/java/org/duniter/core/beans/BeanFactory.java
+++ b/duniter4j-core-shared/src/main/java/org/duniter/core/beans/BeanFactory.java
@@ -61,23 +61,34 @@ public class BeanFactory implements Closeable{
         beansCache.put(clazz, bean);
 
         // Call initialization
+        initBean(bean);
+
+        return bean;
+    }
+
+    public <S extends Bean, B extends S> void setBean(B bean, Class<S> clazz) {
+        if (!beansCache.containsKey(clazz)) {
+            beansCache.put(clazz, bean);
+        }
+    }
+
+    /* -- protected methods -- */
+
+    protected <S extends Bean> void initBean(S bean) {
         if (bean instanceof InitializingBean){
             if (log.isDebugEnabled()) {
-                log.debug(String.format("Initializing bean of type [%s]", clazz.getName()));
+                log.debug(String.format("Initializing bean of type [%s]", bean.getClass().getName()));
             }
             try {
-                ((InitializingBean) bean).afterPropertiesSet();
+                ((InitializingBean)bean).afterPropertiesSet();
             }
             catch(Exception e) {
-                throw new TechnicalException(String.format("Unable to initialize bean of type [%s]", clazz.getName()), e);
+                throw new TechnicalException(String.format("Unable to initialize bean of type [%s]", bean.getClass().getName()), e);
             }
         }
-
-        return bean;
     }
 
-
-    public <S extends Bean> S newBean(Class<S> clazz) {
+    protected <S extends Bean> S newBean(Class<S> clazz) {
         if (log.isTraceEnabled()) {
             log.trace(String.format("Asking bean on type [%s]...", clazz.getName()));
         }
@@ -106,18 +117,21 @@ public class BeanFactory implements Closeable{
 
                     Class<? extends Bean> beanDefClass = beanDef.getValue();
                     try {
-                        Bean bean = beanDefClass.newInstance();
-                        if (clazz.isInstance(bean)) {
+                        if (clazz.isAssignableFrom(beanDefClass)) {
                             return (S) beanDefClass.newInstance();
                         }
                     } catch (Exception e) {
                         // skip
+                        if (log.isDebugEnabled()) {
+                            log.debug(String.format("Unable to create the bean of type [%s] with class [%s]", clazz.getName(), beanDef.getValue().getName()), e);
+                        }
+
                     }
                 }
             }
         }
 
-        throw new TechnicalException(String.format("Unable to create bean with type [%s]: not configured for the service loader [%s]", clazz.getName(), Bean.class.getCanonicalName()));
+        throw new BeanCreationException(String.format("Unable to create bean with type [%s]: not configured for the service loader [%s]", clazz.getName(), Bean.class.getCanonicalName()));
     }
 
     @Override
diff --git a/duniter4j-core-shared/src/main/java/org/duniter/core/util/websocket/WebsocketClientEndpoint.java b/duniter4j-core-shared/src/main/java/org/duniter/core/util/websocket/WebsocketClientEndpoint.java
index 2f127d8dae2b8f279b75fabcad23c8bec343163a..71f3c54674151d5a9b4b749a00240fa29628ae0f 100644
--- a/duniter4j-core-shared/src/main/java/org/duniter/core/util/websocket/WebsocketClientEndpoint.java
+++ b/duniter4j-core-shared/src/main/java/org/duniter/core/util/websocket/WebsocketClientEndpoint.java
@@ -110,18 +110,20 @@ public class WebsocketClientEndpoint implements Closeable {
      */
     @OnMessage
     public void onMessage(final String message) {
+
         if (CollectionUtils.isNotEmpty(messageListeners)) {
             if (log.isDebugEnabled()) {
                 log.debug("[%s] Received message: " + message);
             }
-
-            messageListeners.stream().forEach(messageListener -> {
-                try {
-                    messageListener.onMessage(message);
-                } catch (Exception e) {
-                    log.error(String.format("[%s] Error during message handling: %s", endpointURI, e.getMessage()), e);
-                }
-            });
+            synchronized (messageListeners) {
+                messageListeners.stream().forEach(messageListener -> {
+                    try {
+                        messageListener.onMessage(message);
+                    } catch (Exception e) {
+                        log.error(String.format("[%s] Error during message handling: %s", endpointURI, e.getMessage()), e);
+                    }
+                });
+            }
         }
     }
 
diff --git a/duniter4j-es-assembly/pom.xml b/duniter4j-es-assembly/pom.xml
index e12752f15b9e94853f17dc2922c26961fd90ea71..e89d4dd9270c82e4154463cd1f35bf96af369e4a 100644
--- a/duniter4j-es-assembly/pom.xml
+++ b/duniter4j-es-assembly/pom.xml
@@ -7,10 +7,9 @@
     <version>0.9.2-SNAPSHOT</version>
   </parent>
 
-  <groupId>org.duniter</groupId>
   <artifactId>duniter4j-es-assembly</artifactId>
-  <packaging>pom</packaging>
   <name>Duniter4j :: ElasticSearch Assembly</name>
+  <packaging>pom</packaging>
 
   <properties>
     <!-- bundle configuration -->
diff --git a/duniter4j-es-assembly/src/main/assembly/config/logging.yml b/duniter4j-es-assembly/src/main/assembly/config/logging.yml
index 82d2ed08061ae5b56335db6048aeb3906777b52b..1218c97d7b571db79ac3d0510811680e7020c1de 100644
--- a/duniter4j-es-assembly/src/main/assembly/config/logging.yml
+++ b/duniter4j-es-assembly/src/main/assembly/config/logging.yml
@@ -15,6 +15,7 @@ logger:
   com.amazonaws.metrics.AwsSdkMetrics: ERROR
 
   org.apache.http: INFO
+  org.apache.http.client: ERROR
 
   org.duniter: INFO
 
@@ -23,6 +24,8 @@ logger:
   org.nuiton.i18n: WARN
   org.nuiton.config: ERROR
   org.nuiton.converter: WARN
+  org.glassfish.tyrus: WARN
+  org.apache.http.client: ERROR
 
   # gateway
   #gateway: DEBUG
diff --git a/duniter4j-es-assembly/src/test/es-home/config/logging.yml b/duniter4j-es-assembly/src/test/es-home/config/logging.yml
index 6b4d5cab048a436cf77afde842d50e52b2e5ab35..c9b46939f307aa5433a2217e0e80075467c8e6b2 100644
--- a/duniter4j-es-assembly/src/test/es-home/config/logging.yml
+++ b/duniter4j-es-assembly/src/test/es-home/config/logging.yml
@@ -15,10 +15,13 @@ logger:
   com.amazonaws.metrics.AwsSdkMetrics: ERROR
 
   org.apache.http: INFO
+  org.apache.http.client: ERROR
 
   org.duniter: INFO
 
+  org.duniter.core.beans: DEBUG
   org.duniter.elasticsearch: DEBUG
+  org.duniter.elasticsearch.service: DEBUG
   org.duniter.core.client.service: DEBUG
 
   duniter : DEBUG
diff --git a/duniter4j-es-core/pom.xml b/duniter4j-es-core/pom.xml
index e6c1a758bffdb70edc996c3ff2ccbc8858405971..5b1e16e44415b9a471109439965dc588019c3c15 100644
--- a/duniter4j-es-core/pom.xml
+++ b/duniter4j-es-core/pom.xml
@@ -7,7 +7,6 @@
     <version>0.9.2-SNAPSHOT</version>
   </parent>
 
-  <groupId>org.duniter</groupId>
   <artifactId>duniter4j-es-core</artifactId>
   <packaging>jar</packaging>
   <name>Duniter4j :: ElasticSearch Core plugin</name>
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/Plugin.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/Plugin.java
index ae86d980b2a59fb3d8f5d3a46f73fdcb434057c3..4c8b851d4b7da36e83f02eb1e3c946c40694fa1a 100644
--- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/Plugin.java
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/Plugin.java
@@ -23,6 +23,7 @@ package org.duniter.elasticsearch;
  */
 
 import com.google.common.collect.Lists;
+import org.duniter.elasticsearch.dao.DaoModule;
 import org.duniter.elasticsearch.rest.RestModule;
 import org.duniter.elasticsearch.security.SecurityModule;
 import org.duniter.elasticsearch.service.ServiceModule;
@@ -69,6 +70,7 @@ public class Plugin extends org.elasticsearch.plugins.Plugin {
         modules.add(new WebSocketModule());
         modules.add(new RestModule());
 
+        modules.add(new DaoModule());
         modules.add(new ServiceModule());
 
         return modules;
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginInit.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginInit.java
index 0468f58696aacd214c7358abade834b43e5bb700..d91277ecc49c437e4978e99ee960ec19720659b8 100644
--- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginInit.java
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginInit.java
@@ -27,7 +27,7 @@ import org.duniter.core.client.model.local.Peer;
 import org.duniter.elasticsearch.rest.security.RestSecurityController;
 import org.duniter.elasticsearch.service.BlockchainService;
 import org.duniter.elasticsearch.service.CurrencyService;
-import org.duniter.elasticsearch.service.EndpointService;
+import org.duniter.elasticsearch.service.PeerService;
 import org.duniter.elasticsearch.threadpool.ThreadPool;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.cluster.health.ClusterHealthStatus;
@@ -101,7 +101,8 @@ public class PluginInit extends AbstractLifecycleComponent<PluginInit> {
                 logger.info("Checking Duniter core indices...");
             }
 
-            injector.getInstance(CurrencyService.class).createIndexIfNotExists();
+            injector.getInstance(CurrencyService.class)
+                    .createIndexIfNotExists();
 
             if (logger.isInfoEnabled()) {
                 logger.info("Checking Duniter core indices... [OK]");
@@ -115,21 +116,22 @@ public class PluginInit extends AbstractLifecycleComponent<PluginInit> {
             Peer peer = pluginSettings.checkAndGetPeer();
 
             // Index (or refresh) node's currency
-            Currency currency = injector.getInstance(CurrencyService.class).indexCurrencyFromPeer(peer, true);
+            Currency currency = injector.getInstance(CurrencyService.class)
+                    .indexCurrencyFromPeer(peer, true);
 
             // Add access to currency/block index
             injector.getInstance(RestSecurityController.class).allowIndexType(RestRequest.Method.GET,
-                    currency.getCurrency(),
+                    currency.getCurrencyName(),
                     BlockchainService.BLOCK_TYPE);
             injector.getInstance(RestSecurityController.class).allowPostSearchIndexType(
-                    currency.getCurrency(),
+                    currency.getCurrencyName(),
                     BlockchainService.BLOCK_TYPE);
             // Add access to currency/peer index
             injector.getInstance(RestSecurityController.class).allowIndexType(RestRequest.Method.GET,
-                    currency.getCurrency(),
+                    currency.getCurrencyName(),
                     BlockchainService.PEER_TYPE);
             injector.getInstance(RestSecurityController.class).allowPostSearchIndexType(
-                    currency.getCurrency(),
+                    currency.getCurrencyName(),
                     BlockchainService.PEER_TYPE);
 
             // Index blocks (and listen if new block appear)
@@ -138,9 +140,9 @@ public class PluginInit extends AbstractLifecycleComponent<PluginInit> {
                     .listenAndIndexNewBlock(peer);
 
             // Index peers (and listen if new peer appear)
-            injector.getInstance(EndpointService.class)
-                    .indexAllEndpoints(peer)/*
-                    .listenAndIndexNewPeer(peer)*/;
+            injector.getInstance(PeerService.class)
+                    //.indexAllPeers(peer)
+                    .listenAndIndexPeers(peer);
         }
     }
 }
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/beans/ESBeanFactory.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/beans/ESBeanFactory.java
new file mode 100644
index 0000000000000000000000000000000000000000..fa8ed45ff059c0c8326e62c545c987cd9115ecd9
--- /dev/null
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/beans/ESBeanFactory.java
@@ -0,0 +1,40 @@
+package org.duniter.elasticsearch.beans;
+
+import org.duniter.core.beans.Bean;
+import org.duniter.core.beans.BeanCreationException;
+import org.duniter.core.beans.BeanFactory;
+import org.elasticsearch.common.inject.Injector;
+
+/**
+ * Created by blavenie on 31/03/17.
+ */
+public class ESBeanFactory extends BeanFactory {
+
+    private Injector injector = null;
+
+    public void setInjector(Injector injector) {
+        this.injector = injector;
+    }
+
+    @Override
+    protected <S extends Bean> void initBean(S bean) {
+        super.initBean(bean);
+        if (injector != null) {
+            injector.injectMembers(bean);
+        }
+    }
+
+    @Override
+    protected <S extends Bean> S newBean(Class<S> clazz) {
+        try {
+            return super.newBean(clazz);
+        }
+        catch(BeanCreationException e) {
+            // try using injector, if exists
+            if (injector != null) {
+                return injector.getBinding(clazz).getProvider().get();
+            }
+            throw e;
+        }
+    }
+}
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/client/Duniter4jClient.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/client/Duniter4jClient.java
new file mode 100644
index 0000000000000000000000000000000000000000..a00760357cb0f36a2d023db2d616d0208a5dd20b
--- /dev/null
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/client/Duniter4jClient.java
@@ -0,0 +1,72 @@
+package org.duniter.elasticsearch.client;
+
+import org.duniter.core.beans.Bean;
+import org.duniter.elasticsearch.dao.handler.StringReaderHandler;
+import org.elasticsearch.ElasticsearchException;
+import org.elasticsearch.action.bulk.BulkRequestBuilder;
+import org.elasticsearch.client.Client;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Created by blavenie on 03/04/17.
+ */
+public interface Duniter4jClient extends Bean, Client {
+
+    boolean existsIndex(String index);
+
+    void deleteIndexIfExists(String indexName);
+
+    Object getFieldById(String index, String type, String docId, String fieldName);
+
+    Map<String, Object> getFieldByIds(String index, String type, Set<String> ids, String fieldName);
+
+    Map<String, Object> getFieldsById(String index, String type, String docId, String... fieldNames);
+
+    <T> T getTypedFieldById(String index, String type, String docId, String fieldName);
+
+    Map<String, Object> getMandatoryFieldsById(String index, String type, String docId, String... fieldNames);
+
+    String indexDocumentFromJson(String index, String type, String json);
+
+    void updateDocumentFromJson(String index, String type, String id, String json);
+
+    void checkSameDocumentField(String index, String type, String id, String fieldName, String expectedvalue) throws ElasticsearchException;
+
+    void checkSameDocumentIssuer(String index, String type, String id, String expectedIssuer);
+
+    boolean isDocumentExists(String index, String type, String id) throws ElasticsearchException;
+
+    void checkDocumentExists(String index, String type, String id) throws ElasticsearchException;
+
+    /**
+     * Retrieve a document by id (safe mode)
+     * @param docId
+     * @return
+     */
+    <T extends Object> T getSourceByIdOrNull(String index, String type, String docId, Class<T> classOfT, String... fieldNames);
+
+    /**
+     * Retrieve a document by id
+     * @param docId
+     * @return
+     */
+    <T extends Object> T getSourceById(String index, String type, String docId, Class<T> classOfT, String... fieldNames);
+
+    void bulkFromClasspathFile(String classpathFile, String indexName, String indexType);
+
+    void bulkFromClasspathFile(String classpathFile, String indexName, String indexType, StringReaderHandler handler);
+
+    void bulkFromFile(File file, String indexName, String indexType);
+
+    void bulkFromFile(File file, String indexName, String indexType, StringReaderHandler handler);
+
+    void bulkFromStream(InputStream is, String indexName, String indexType);
+
+    void bulkFromStream(InputStream is, String indexName, String indexType, StringReaderHandler handler);
+
+    void flushDeleteBulk(final String index, final String type, BulkRequestBuilder bulkRequest);
+}
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/client/Duniter4jClientImpl.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/client/Duniter4jClientImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..158b86b4b3cecbfedca3fcebf5da1ceaeb5210fb
--- /dev/null
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/client/Duniter4jClientImpl.java
@@ -0,0 +1,952 @@
+package org.duniter.elasticsearch.client;
+
+/*
+ * #%L
+ * UCoin Java Client :: Core API
+ * %%
+ * Copyright (C) 2014 - 2015 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.base.Joiner;
+import org.apache.commons.collections4.MapUtils;
+import org.duniter.core.client.model.bma.jackson.JacksonUtils;
+import org.duniter.core.client.model.elasticsearch.Record;
+import org.duniter.core.exception.TechnicalException;
+import org.duniter.core.util.CollectionUtils;
+import org.duniter.core.util.ObjectUtils;
+import org.duniter.core.util.Preconditions;
+import org.duniter.core.util.StringUtils;
+import org.duniter.elasticsearch.dao.AbstractDao;
+import org.duniter.elasticsearch.dao.handler.StringReaderHandler;
+import org.duniter.elasticsearch.exception.AccessDeniedException;
+import org.duniter.elasticsearch.exception.NotFoundException;
+import org.elasticsearch.ElasticsearchException;
+import org.elasticsearch.action.*;
+import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequestBuilder;
+import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequestBuilder;
+import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsResponse;
+import org.elasticsearch.action.bulk.BulkItemResponse;
+import org.elasticsearch.action.bulk.BulkRequest;
+import org.elasticsearch.action.bulk.BulkRequestBuilder;
+import org.elasticsearch.action.bulk.BulkResponse;
+import org.elasticsearch.action.count.CountRequest;
+import org.elasticsearch.action.count.CountRequestBuilder;
+import org.elasticsearch.action.count.CountResponse;
+import org.elasticsearch.action.delete.DeleteRequest;
+import org.elasticsearch.action.delete.DeleteRequestBuilder;
+import org.elasticsearch.action.delete.DeleteResponse;
+import org.elasticsearch.action.exists.ExistsRequest;
+import org.elasticsearch.action.exists.ExistsRequestBuilder;
+import org.elasticsearch.action.exists.ExistsResponse;
+import org.elasticsearch.action.explain.ExplainRequest;
+import org.elasticsearch.action.explain.ExplainRequestBuilder;
+import org.elasticsearch.action.explain.ExplainResponse;
+import org.elasticsearch.action.fieldstats.FieldStatsRequest;
+import org.elasticsearch.action.fieldstats.FieldStatsRequestBuilder;
+import org.elasticsearch.action.fieldstats.FieldStatsResponse;
+import org.elasticsearch.action.get.*;
+import org.elasticsearch.action.index.IndexRequest;
+import org.elasticsearch.action.index.IndexRequestBuilder;
+import org.elasticsearch.action.index.IndexResponse;
+import org.elasticsearch.action.indexedscripts.delete.DeleteIndexedScriptRequest;
+import org.elasticsearch.action.indexedscripts.delete.DeleteIndexedScriptRequestBuilder;
+import org.elasticsearch.action.indexedscripts.delete.DeleteIndexedScriptResponse;
+import org.elasticsearch.action.indexedscripts.get.GetIndexedScriptRequest;
+import org.elasticsearch.action.indexedscripts.get.GetIndexedScriptRequestBuilder;
+import org.elasticsearch.action.indexedscripts.get.GetIndexedScriptResponse;
+import org.elasticsearch.action.indexedscripts.put.PutIndexedScriptRequest;
+import org.elasticsearch.action.indexedscripts.put.PutIndexedScriptRequestBuilder;
+import org.elasticsearch.action.indexedscripts.put.PutIndexedScriptResponse;
+import org.elasticsearch.action.percolate.*;
+import org.elasticsearch.action.search.*;
+import org.elasticsearch.action.suggest.SuggestRequest;
+import org.elasticsearch.action.suggest.SuggestRequestBuilder;
+import org.elasticsearch.action.suggest.SuggestResponse;
+import org.elasticsearch.action.termvectors.*;
+import org.elasticsearch.action.update.UpdateRequest;
+import org.elasticsearch.action.update.UpdateRequestBuilder;
+import org.elasticsearch.action.update.UpdateResponse;
+import org.elasticsearch.client.AdminClient;
+import org.elasticsearch.client.Client;
+import org.elasticsearch.client.Requests;
+import org.elasticsearch.client.support.Headers;
+import org.elasticsearch.common.Nullable;
+import org.elasticsearch.common.bytes.BytesArray;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.logging.ESLogger;
+import org.elasticsearch.common.logging.Loggers;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.search.SearchHit;
+import org.elasticsearch.search.SearchHitField;
+import org.elasticsearch.threadpool.ThreadPool;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * Created by Benoit on 08/04/2015.
+ */
+public class Duniter4jClientImpl implements Duniter4jClient {
+
+    private final static ESLogger logger = Loggers.getLogger("duniter.client");
+
+    private final Client client;
+
+    private final ObjectMapper objectMapper;
+
+    @Inject
+    public Duniter4jClientImpl(Client client) {
+        super();
+        this.client = client;
+        this.objectMapper = JacksonUtils.newObjectMapper();
+    }
+
+    @Override
+    public boolean existsIndex(String indexes) {
+        IndicesExistsRequestBuilder requestBuilder = client.admin().indices().prepareExists(indexes);
+        IndicesExistsResponse response = requestBuilder.execute().actionGet();
+        return response.isExists();
+    }
+
+    @Override
+    public void deleteIndexIfExists(String indexName){
+        if (!existsIndex(indexName)) {
+            return;
+        }
+        if (logger.isInfoEnabled()) {
+            logger.info(String.format("Deleting index [%s]", indexName));
+        }
+
+        DeleteIndexRequestBuilder deleteIndexRequestBuilder = client.admin().indices().prepareDelete(indexName);
+        deleteIndexRequestBuilder.execute().actionGet();
+    }
+
+    @Override
+    public String indexDocumentFromJson(String index, String type, String json) {
+        IndexResponse response = client.prepareIndex(index, type)
+                .setSource(json)
+                .setRefresh(true)
+                .execute().actionGet();
+        return response.getId();
+    }
+
+    @Override
+    public void updateDocumentFromJson(String index, String type, String id, String json) {
+        // Execute indexBlocksFromNode
+        client.prepareUpdate(index, type, id)
+                .setRefresh(true)
+                .setDoc(json)
+                .execute().actionGet();
+    }
+
+    @Override
+    public void checkSameDocumentField(String index, String type, String id, String fieldName, String expectedvalue) throws ElasticsearchException {
+
+        GetResponse response = client.prepareGet(index, type, id)
+                .setFields(fieldName)
+                .execute().actionGet();
+        boolean failed = !response.isExists();
+        if (failed) {
+            throw new NotFoundException(String.format("Document [%s/%s/%s] not exists.", index, type, id));
+        } else {
+            String docValue = (String)response.getFields().get(fieldName).getValue();
+            if (!Objects.equals(expectedvalue, docValue)) {
+                throw new AccessDeniedException(String.format("Could not delete this document: not same [%s].", fieldName));
+            }
+        }
+    }
+
+    @Override
+    public boolean isDocumentExists(String index, String type, String id) throws ElasticsearchException {
+        GetResponse response = client.prepareGet(index, type, id)
+                .setFetchSource(false)
+                .execute().actionGet();
+        return response.isExists();
+    }
+
+    @Override
+    public void checkDocumentExists(String index, String type, String id) throws ElasticsearchException {
+        if (!isDocumentExists(index, type, id)) {
+            throw new NotFoundException(String.format("Document [%s/%s/%s] not exists.", index, type, id));
+        }
+    }
+
+
+    @Override
+    public void checkSameDocumentIssuer(String index, String type, String id, String expectedIssuer) {
+        String issuer = getMandatoryFieldsById(index, type, id, Record.PROPERTY_ISSUER).get(Record.PROPERTY_ISSUER).toString();
+        if (!ObjectUtils.equals(expectedIssuer, issuer)) {
+            throw new TechnicalException("Not same issuer");
+        }
+    }
+
+    /**
+     * Retrieve some field from a document id, and check if all field not null
+     * @param docId
+     * @return
+     */
+    @Override
+    public Map<String, Object> getMandatoryFieldsById(String index, String type, String docId, String... fieldNames) {
+        Map<String, Object> fields = getFieldsById(index, type, docId, fieldNames);
+        if (MapUtils.isEmpty(fields)) throw new NotFoundException(String.format("Document [%s/%s/%s] not exists.", index, type, docId));
+        Arrays.stream(fieldNames).forEach((fieldName) -> {
+            if (!fields.containsKey(fieldName)) throw new NotFoundException(String.format("Document [%s/%s/%s] should have the mandatory field [%s].", index, type, docId, fieldName));
+        });
+        return fields;
+    }
+
+    /**
+     * Retrieve some field from a document id
+     * @param docId
+     * @return
+     */
+    @Override
+    public Map<String, Object> getFieldsById(String index, String type, String docId, String... fieldNames) {
+        // Prepare request
+        SearchRequestBuilder searchRequest = client
+                .prepareSearch(index)
+                .setTypes(type)
+                .setSearchType(SearchType.DFS_QUERY_THEN_FETCH);
+
+        searchRequest.setQuery(QueryBuilders.idsQuery().ids(docId));
+        searchRequest.addFields(fieldNames);
+
+        // Execute query
+        try {
+            SearchResponse response = searchRequest.execute().actionGet();
+
+            Map<String, Object> result = new HashMap<>();
+            // Read query result
+            SearchHit[] searchHits = response.getHits().getHits();
+            for (SearchHit searchHit : searchHits) {
+                Map<String, SearchHitField> hitFields = searchHit.getFields();
+                for(String fieldName: hitFields.keySet()) {
+                    result.put(fieldName, hitFields.get(fieldName).getValue());
+                }
+                break;
+            }
+            return result;
+        }
+        catch(SearchPhaseExecutionException e) {
+            // Failed or no item on index
+            throw new TechnicalException(String.format("[%s/%s] Unable to retrieve fields [%s] for id [%s]",
+                    index, type,
+                    Joiner.on(',').join(fieldNames).toString(),
+                    docId), e);
+        }
+    }
+
+    /**
+     * Retrieve some field from a document id
+     * @param index
+     * @param type
+     * @param ids
+     * @param fieldName
+     * @return
+     */
+    @Override
+    public Map<String, Object> getFieldByIds(String index, String type, Set<String> ids, String fieldName) {
+        // Prepare request
+        SearchRequestBuilder searchRequest = client
+                .prepareSearch(index)
+                .setTypes(type)
+                .setSearchType(SearchType.DFS_QUERY_THEN_FETCH);
+
+        searchRequest.setQuery(QueryBuilders.idsQuery().ids(ids));
+        searchRequest.addFields(fieldName);
+
+        // Execute query
+        try {
+            SearchResponse response = searchRequest.execute().actionGet();
+
+            Map<String, Object> result = new HashMap<>();
+            // Read query result
+            SearchHit[] searchHits = response.getHits().getHits();
+            for (SearchHit searchHit : searchHits) {
+                Map<String, SearchHitField> hitFields = searchHit.getFields();
+                if (hitFields.get(fieldName) != null) {
+                    result.put(searchHit.getId(), hitFields.get(fieldName).getValue());
+                }
+            }
+            return result;
+        }
+        catch(SearchPhaseExecutionException e) {
+            // Failed or no item on index
+            throw new TechnicalException(String.format("[%s/%s] Unable to retrieve field [%s] for ids [%s]",
+                    index, type, fieldName,
+                    Joiner.on(',').join(ids).toString()), e);
+        }
+    }
+
+    /**
+     * Retrieve a field from a document id
+     * @param docId
+     * @return
+     */
+    @Override
+    public Object getFieldById(String index, String type, String docId, String fieldName) {
+
+        Map<String, Object> result = getFieldsById(index, type, docId, fieldName);
+        if (MapUtils.isEmpty(result)) {
+            return null;
+        }
+        return result.get(fieldName);
+    }
+
+    @Override
+    public <T> T getTypedFieldById(String index, String type, String docId, String fieldName) {
+        return (T)getFieldById(index, type, docId, fieldName);
+    }
+
+    /**
+     * Retrieve a document by id (safe mode)
+     * @param docId
+     * @return
+     */
+    @Override
+    public <T extends Object> T getSourceByIdOrNull(String index, String type, String docId, Class<T> classOfT, String... fieldNames) {
+        try {
+            return getSourceById(index, type, docId, classOfT, fieldNames);
+        }
+        catch(TechnicalException e) {
+            return null; // not found
+        }
+    }
+
+    /**
+     * Retrieve a document by id
+     * @param docId
+     * @return
+     */
+    @Override
+    public <T extends Object> T getSourceById(String index, String type, String docId, Class<T> classOfT, String... fieldNames) {
+
+        // Prepare request
+        SearchRequestBuilder searchRequest = client
+                .prepareSearch(index)
+                .setSearchType(SearchType.QUERY_AND_FETCH);
+
+        searchRequest.setQuery(QueryBuilders.idsQuery(type).ids(docId));
+        if (CollectionUtils.isNotEmpty(fieldNames)) {
+            searchRequest.setFetchSource(fieldNames, null);
+        }
+        else {
+            searchRequest.setFetchSource(true); // full source
+        }
+
+        // Execute query
+        try {
+            SearchResponse response = searchRequest.execute().actionGet();
+
+            if (response.getHits().getTotalHits() == 0) return null;
+
+            // Read query result
+            SearchHit[] searchHits = response.getHits().getHits();
+
+            for (SearchHit searchHit : searchHits) {
+                if (searchHit.source() != null) {
+                    return objectMapper.readValue(searchHit.source(), classOfT);
+                }
+                break;
+            }
+            return null;
+        }
+        catch(SearchPhaseExecutionException | IOException e) {
+            // Failed to get source
+            throw new TechnicalException(String.format("[%s/%s] Error while getting [%s]",
+                    index, type,
+                    docId), e);
+        }
+    }
+
+    @Override
+    public void bulkFromClasspathFile(String classpathFile, String indexName, String indexType) {
+        bulkFromClasspathFile(classpathFile, indexName, indexType, null);
+    }
+
+    @Override
+    public void bulkFromClasspathFile(String classpathFile, String indexName, String indexType, StringReaderHandler handler) {
+        InputStream is = null;
+        try {
+            is = getClass().getClassLoader().getResourceAsStream(classpathFile);
+            if (is == null) {
+                throw new TechnicalException(String.format("Could not retrieve data file [%s] need to fill index [%s]: ", classpathFile, indexName));
+            }
+
+            bulkFromStream(is, indexName, indexType, handler);
+        }
+        finally {
+            if (is != null) {
+                try  {
+                    is.close();
+                }
+                catch(IOException e) {
+                    // Silent is gold
+                }
+            }
+        }
+    }
+
+    @Override
+    public void bulkFromFile(File file, String indexName, String indexType) {
+        bulkFromFile(file, indexName, indexType, null);
+    }
+
+    @Override
+    public void bulkFromFile(File file, String indexName, String indexType, StringReaderHandler handler) {
+        Preconditions.checkNotNull(file);
+        Preconditions.checkArgument(file.exists());
+
+        InputStream is = null;
+        try {
+            is = new BufferedInputStream(new FileInputStream(file));
+            bulkFromStream(is, indexName, indexType, handler);
+        }
+        catch(FileNotFoundException e) {
+            throw new TechnicalException(String.format("[%s] Could not find file %s", indexName, file.getPath()), e);
+        }
+        finally {
+            if (is != null) {
+                try  {
+                    is.close();
+                }
+                catch(IOException e) {
+                    // Silent is gold
+                }
+            }
+        }
+    }
+
+    @Override
+    public void bulkFromStream(InputStream is, String indexName, String indexType) {
+        bulkFromStream(is, indexName, indexType, null);
+    }
+
+    @Override
+    public void bulkFromStream(InputStream is, String indexName, String indexType, StringReaderHandler handler) {
+        Preconditions.checkNotNull(is);
+        BulkRequest bulkRequest = Requests.bulkRequest();
+
+        BufferedReader br = null;
+
+        try {
+            br = new BufferedReader(new InputStreamReader(is));
+
+            String line = br.readLine();
+            StringBuilder builder = new StringBuilder();
+            while(line != null) {
+                line = line.trim();
+                if (StringUtils.isNotBlank(line)) {
+                    if (logger.isTraceEnabled()) {
+                        logger.trace(String.format("[%s] Add to bulk: %s", indexName, line));
+                    }
+                    if (handler != null) {
+                        line = handler.onReadLine(line.trim());
+                    }
+                    builder.append(line).append('\n');
+                }
+                line = br.readLine();
+            }
+
+            byte[] data = builder.toString().getBytes();
+            bulkRequest.add(new BytesArray(data), indexName, indexType, false);
+
+        } catch(Exception e) {
+            throw new TechnicalException(String.format("[%s] Error while inserting rows into %s", indexName, indexType), e);
+        }
+        finally {
+            if (br != null) {
+                try  {
+                    br.close();
+                }
+                catch(IOException e) {
+                    // Silent is gold
+                }
+            }
+        }
+
+        try {
+            client.bulk(bulkRequest).actionGet();
+        } catch(Exception e) {
+            throw new TechnicalException(String.format("[%s] Error while inserting rows into %s", indexName, indexType), e);
+        }
+    }
+
+    @Override
+    public void flushDeleteBulk(final String index, final String type, BulkRequestBuilder bulkRequest) {
+        if (bulkRequest.numberOfActions() > 0) {
+
+            BulkResponse bulkResponse = bulkRequest.execute().actionGet();
+            // If failures, continue but save missing blocks
+            if (bulkResponse.hasFailures()) {
+                // process failures by iterating through each bulk response item
+                for (BulkItemResponse itemResponse : bulkResponse) {
+                    boolean skip = !itemResponse.isFailed();
+                    if (!skip) {
+                        logger.debug(String.format("[%s/%s] Error while deleting doc [%s]: %s. Skipping this deletion.", index, type, itemResponse.getId(), itemResponse.getFailureMessage()));
+                    }
+                }
+            }
+        }
+    }
+
+    /* delegate methods */
+
+    @Override
+    public AdminClient admin() {
+        return client.admin();
+    }
+
+    @Override
+    public ActionFuture<IndexResponse> index(IndexRequest request) {
+        return client.index(request);
+    }
+
+    @Override
+    public void index(IndexRequest request, ActionListener<IndexResponse> listener) {
+        client.index(request, listener);
+    }
+
+    @Override
+    public IndexRequestBuilder prepareIndex() {
+        return client.prepareIndex();
+    }
+
+    @Override
+    public ActionFuture<UpdateResponse> update(UpdateRequest request) {
+        return client.update(request);
+    }
+
+    @Override
+    public void update(UpdateRequest request, ActionListener<UpdateResponse> listener) {
+        client.update(request, listener);
+    }
+
+    @Override
+    public UpdateRequestBuilder prepareUpdate() {
+        return client.prepareUpdate();
+    }
+
+    @Override
+    public UpdateRequestBuilder prepareUpdate(String index, String type, String id) {
+        return client.prepareUpdate(index, type, id);
+    }
+
+    @Override
+    public IndexRequestBuilder prepareIndex(String index, String type) {
+        return client.prepareIndex(index, type);
+    }
+
+    @Override
+    public IndexRequestBuilder prepareIndex(String index, String type, @Nullable String id) {
+        return client.prepareIndex(index, type, id);
+    }
+
+    @Override
+    public ActionFuture<DeleteResponse> delete(DeleteRequest request) {
+        return client.delete(request);
+    }
+
+    @Override
+    public void delete(DeleteRequest request, ActionListener<DeleteResponse> listener) {
+        client.delete(request, listener);
+    }
+
+    @Override
+    public DeleteRequestBuilder prepareDelete() {
+        return client.prepareDelete();
+    }
+
+    @Override
+    public DeleteRequestBuilder prepareDelete(String index, String type, String id) {
+        return client.prepareDelete(index, type, id);
+    }
+
+    @Override
+    public ActionFuture<BulkResponse> bulk(BulkRequest request) {
+        return client.bulk(request);
+    }
+
+    @Override
+    public void bulk(BulkRequest request, ActionListener<BulkResponse> listener) {
+        client.bulk(request, listener);
+    }
+
+    @Override
+    public BulkRequestBuilder prepareBulk() {
+        return client.prepareBulk();
+    }
+
+    @Override
+    public ActionFuture<GetResponse> get(GetRequest request) {
+        return client.get(request);
+    }
+
+    @Override
+    public void get(GetRequest request, ActionListener<GetResponse> listener) {
+        client.get(request, listener);
+    }
+
+    @Override
+    public GetRequestBuilder prepareGet() {
+        return client.prepareGet();
+    }
+
+    @Override
+    public GetRequestBuilder prepareGet(String index, @Nullable String type, String id) {
+        return client.prepareGet(index, type, id);
+    }
+
+    @Override
+    public PutIndexedScriptRequestBuilder preparePutIndexedScript() {
+        return client.preparePutIndexedScript();
+    }
+
+    @Override
+    public PutIndexedScriptRequestBuilder preparePutIndexedScript(@Nullable String scriptLang, String id, String source) {
+        return client.preparePutIndexedScript(scriptLang, id, source);
+    }
+
+    @Override
+    public void deleteIndexedScript(DeleteIndexedScriptRequest request, ActionListener<DeleteIndexedScriptResponse> listener) {
+        client.deleteIndexedScript(request, listener);
+    }
+
+    @Override
+    public ActionFuture<DeleteIndexedScriptResponse> deleteIndexedScript(DeleteIndexedScriptRequest request) {
+        return client.deleteIndexedScript(request);
+    }
+
+    @Override
+    public DeleteIndexedScriptRequestBuilder prepareDeleteIndexedScript() {
+        return client.prepareDeleteIndexedScript();
+    }
+
+    @Override
+    public DeleteIndexedScriptRequestBuilder prepareDeleteIndexedScript(@Nullable String scriptLang, String id) {
+        return client.prepareDeleteIndexedScript(scriptLang, id);
+    }
+
+    @Override
+    public void putIndexedScript(PutIndexedScriptRequest request, ActionListener<PutIndexedScriptResponse> listener) {
+        client.putIndexedScript(request, listener);
+    }
+
+    @Override
+    public ActionFuture<PutIndexedScriptResponse> putIndexedScript(PutIndexedScriptRequest request) {
+        return client.putIndexedScript(request);
+    }
+
+    @Override
+    public GetIndexedScriptRequestBuilder prepareGetIndexedScript() {
+        return client.prepareGetIndexedScript();
+    }
+
+    @Override
+    public GetIndexedScriptRequestBuilder prepareGetIndexedScript(@Nullable String scriptLang, String id) {
+        return client.prepareGetIndexedScript(scriptLang, id);
+    }
+
+    @Override
+    public void getIndexedScript(GetIndexedScriptRequest request, ActionListener<GetIndexedScriptResponse> listener) {
+        client.getIndexedScript(request, listener);
+    }
+
+    @Override
+    public ActionFuture<GetIndexedScriptResponse> getIndexedScript(GetIndexedScriptRequest request) {
+        return client.getIndexedScript(request);
+    }
+
+    @Override
+    public ActionFuture<MultiGetResponse> multiGet(MultiGetRequest request) {
+        return client.multiGet(request);
+    }
+
+    @Override
+    public void multiGet(MultiGetRequest request, ActionListener<MultiGetResponse> listener) {
+        client.multiGet(request, listener);
+    }
+
+    @Override
+    public MultiGetRequestBuilder prepareMultiGet() {
+        return client.prepareMultiGet();
+    }
+
+    @Override
+    @Deprecated
+    public ActionFuture<CountResponse> count(CountRequest request) {
+        return client.count(request);
+    }
+
+    @Override
+    @Deprecated
+    public void count(CountRequest request, ActionListener<CountResponse> listener) {
+        client.count(request, listener);
+    }
+
+    @Override
+    @Deprecated
+    public CountRequestBuilder prepareCount(String... indices) {
+        return client.prepareCount(indices);
+    }
+
+    @Override
+    @Deprecated
+    public ActionFuture<ExistsResponse> exists(ExistsRequest request) {
+        return client.exists(request);
+    }
+
+    @Override
+    @Deprecated
+    public void exists(ExistsRequest request, ActionListener<ExistsResponse> listener) {
+        client.exists(request, listener);
+    }
+
+    @Override
+    @Deprecated
+    public ExistsRequestBuilder prepareExists(String... indices) {
+        return client.prepareExists(indices);
+    }
+
+    @Override
+    public ActionFuture<SuggestResponse> suggest(SuggestRequest request) {
+        return client.suggest(request);
+    }
+
+    @Override
+    public void suggest(SuggestRequest request, ActionListener<SuggestResponse> listener) {
+        client.suggest(request, listener);
+    }
+
+    @Override
+    public SuggestRequestBuilder prepareSuggest(String... indices) {
+        return client.prepareSuggest(indices);
+    }
+
+    @Override
+    public ActionFuture<SearchResponse> search(SearchRequest request) {
+        return client.search(request);
+    }
+
+    @Override
+    public void search(SearchRequest request, ActionListener<SearchResponse> listener) {
+        client.search(request, listener);
+    }
+
+    @Override
+    public SearchRequestBuilder prepareSearch(String... indices) {
+        return client.prepareSearch(indices);
+    }
+
+    @Override
+    public ActionFuture<SearchResponse> searchScroll(SearchScrollRequest request) {
+        return client.searchScroll(request);
+    }
+
+    @Override
+    public void searchScroll(SearchScrollRequest request, ActionListener<SearchResponse> listener) {
+        client.searchScroll(request, listener);
+    }
+
+    @Override
+    public SearchScrollRequestBuilder prepareSearchScroll(String scrollId) {
+        return client.prepareSearchScroll(scrollId);
+    }
+
+    @Override
+    public ActionFuture<MultiSearchResponse> multiSearch(MultiSearchRequest request) {
+        return client.multiSearch(request);
+    }
+
+    @Override
+    public void multiSearch(MultiSearchRequest request, ActionListener<MultiSearchResponse> listener) {
+        client.multiSearch(request, listener);
+    }
+
+    @Override
+    public MultiSearchRequestBuilder prepareMultiSearch() {
+        return client.prepareMultiSearch();
+    }
+
+    @Override
+    public ActionFuture<TermVectorsResponse> termVectors(TermVectorsRequest request) {
+        return client.termVectors(request);
+    }
+
+    @Override
+    public void termVectors(TermVectorsRequest request, ActionListener<TermVectorsResponse> listener) {
+        client.termVectors(request, listener);
+    }
+
+    @Override
+    public TermVectorsRequestBuilder prepareTermVectors() {
+        return client.prepareTermVectors();
+    }
+
+    @Override
+    public TermVectorsRequestBuilder prepareTermVectors(String index, String type, String id) {
+        return client.prepareTermVectors(index, type, id);
+    }
+
+    @Override
+    @Deprecated
+    public ActionFuture<TermVectorsResponse> termVector(TermVectorsRequest request) {
+        return client.termVector(request);
+    }
+
+    @Override
+    @Deprecated
+    public void termVector(TermVectorsRequest request, ActionListener<TermVectorsResponse> listener) {
+        client.termVector(request, listener);
+    }
+
+    @Override
+    @Deprecated
+    public TermVectorsRequestBuilder prepareTermVector() {
+        return client.prepareTermVector();
+    }
+
+    @Override
+    @Deprecated
+    public TermVectorsRequestBuilder prepareTermVector(String index, String type, String id) {
+        return client.prepareTermVector(index, type, id);
+    }
+
+    @Override
+    public ActionFuture<MultiTermVectorsResponse> multiTermVectors(MultiTermVectorsRequest request) {
+        return client.multiTermVectors(request);
+    }
+
+    @Override
+    public void multiTermVectors(MultiTermVectorsRequest request, ActionListener<MultiTermVectorsResponse> listener) {
+        client.multiTermVectors(request, listener);
+    }
+
+    @Override
+    public MultiTermVectorsRequestBuilder prepareMultiTermVectors() {
+        return client.prepareMultiTermVectors();
+    }
+
+    @Override
+    public ActionFuture<PercolateResponse> percolate(PercolateRequest request) {
+        return client.percolate(request);
+    }
+
+    @Override
+    public void percolate(PercolateRequest request, ActionListener<PercolateResponse> listener) {
+        client.percolate(request, listener);
+    }
+
+    @Override
+    public PercolateRequestBuilder preparePercolate() {
+        return client.preparePercolate();
+    }
+
+    @Override
+    public ActionFuture<MultiPercolateResponse> multiPercolate(MultiPercolateRequest request) {
+        return client.multiPercolate(request);
+    }
+
+    @Override
+    public void multiPercolate(MultiPercolateRequest request, ActionListener<MultiPercolateResponse> listener) {
+        client.multiPercolate(request, listener);
+    }
+
+    @Override
+    public MultiPercolateRequestBuilder prepareMultiPercolate() {
+        return client.prepareMultiPercolate();
+    }
+
+    @Override
+    public ExplainRequestBuilder prepareExplain(String index, String type, String id) {
+        return client.prepareExplain(index, type, id);
+    }
+
+    @Override
+    public ActionFuture<ExplainResponse> explain(ExplainRequest request) {
+        return client.explain(request);
+    }
+
+    @Override
+    public void explain(ExplainRequest request, ActionListener<ExplainResponse> listener) {
+        client.explain(request, listener);
+    }
+
+    @Override
+    public ClearScrollRequestBuilder prepareClearScroll() {
+        return client.prepareClearScroll();
+    }
+
+    @Override
+    public ActionFuture<ClearScrollResponse> clearScroll(ClearScrollRequest request) {
+        return client.clearScroll(request);
+    }
+
+    @Override
+    public void clearScroll(ClearScrollRequest request, ActionListener<ClearScrollResponse> listener) {
+        client.clearScroll(request, listener);
+    }
+
+    @Override
+    public FieldStatsRequestBuilder prepareFieldStats() {
+        return client.prepareFieldStats();
+    }
+
+    @Override
+    public ActionFuture<FieldStatsResponse> fieldStats(FieldStatsRequest request) {
+        return client.fieldStats(request);
+    }
+
+    @Override
+    public void fieldStats(FieldStatsRequest request, ActionListener<FieldStatsResponse> listener) {
+        client.fieldStats(request, listener);
+    }
+
+    @Override
+    public Settings settings() {
+        return client.settings();
+    }
+
+    @Override
+    public Headers headers() {
+        return client.headers();
+    }
+
+    public <Request extends ActionRequest, Response extends ActionResponse, RequestBuilder extends ActionRequestBuilder<Request, Response, RequestBuilder>> ActionFuture<Response> execute(Action<Request, Response, RequestBuilder> action, Request request) {
+        return client.execute(action, request);
+    }
+
+    public <Request extends ActionRequest, Response extends ActionResponse, RequestBuilder extends ActionRequestBuilder<Request, Response, RequestBuilder>> void execute(Action<Request, Response, RequestBuilder> action, Request request, ActionListener<Response> listener) {
+        client.execute(action, request, listener);
+    }
+
+    public <Request extends ActionRequest, Response extends ActionResponse, RequestBuilder extends ActionRequestBuilder<Request, Response, RequestBuilder>> RequestBuilder prepareExecute(Action<Request, Response, RequestBuilder> action) {
+        return client.prepareExecute(action);
+    }
+
+    public ThreadPool threadPool() {
+        return client.threadPool();
+    }
+
+    public void close() {
+        client.close();
+    }
+}
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/AbstractDao.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/AbstractDao.java
new file mode 100644
index 0000000000000000000000000000000000000000..0fe773e93eacefc8f0aea1c2b839ad92d0387c76
--- /dev/null
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/AbstractDao.java
@@ -0,0 +1,105 @@
+package org.duniter.elasticsearch.dao;
+
+/*
+ * #%L
+ * UCoin Java Client :: Core API
+ * %%
+ * Copyright (C) 2014 - 2015 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.base.Joiner;
+import org.apache.commons.collections4.MapUtils;
+import org.duniter.core.beans.Bean;
+import org.duniter.core.client.model.bma.jackson.JacksonUtils;
+import org.duniter.core.exception.TechnicalException;
+import org.duniter.core.service.CryptoService;
+import org.duniter.core.util.CollectionUtils;
+import org.duniter.core.util.Preconditions;
+import org.duniter.core.util.StringUtils;
+import org.duniter.elasticsearch.PluginSettings;
+import org.duniter.elasticsearch.client.Duniter4jClient;
+import org.duniter.elasticsearch.dao.handler.StringReaderHandler;
+import org.duniter.elasticsearch.exception.AccessDeniedException;
+import org.duniter.elasticsearch.exception.NotFoundException;
+import org.elasticsearch.ElasticsearchException;
+import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequestBuilder;
+import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequestBuilder;
+import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsResponse;
+import org.elasticsearch.action.bulk.BulkItemResponse;
+import org.elasticsearch.action.bulk.BulkRequest;
+import org.elasticsearch.action.bulk.BulkRequestBuilder;
+import org.elasticsearch.action.bulk.BulkResponse;
+import org.elasticsearch.action.get.GetResponse;
+import org.elasticsearch.action.index.IndexResponse;
+import org.elasticsearch.action.search.SearchPhaseExecutionException;
+import org.elasticsearch.action.search.SearchRequestBuilder;
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.action.search.SearchType;
+import org.elasticsearch.client.Client;
+import org.elasticsearch.client.Requests;
+import org.elasticsearch.common.bytes.BytesArray;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.logging.ESLogger;
+import org.elasticsearch.common.logging.Loggers;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.search.SearchHit;
+import org.elasticsearch.search.SearchHitField;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * Created by Benoit on 08/04/2015.
+ */
+public abstract class AbstractDao implements Bean {
+
+
+    protected final ESLogger logger;
+    protected final ObjectMapper objectMapper;
+
+    protected Duniter4jClient client;
+    protected CryptoService cryptoService;
+    protected PluginSettings pluginSettings;
+
+    public AbstractDao(String loggerName) {
+        super();
+        this.logger = Loggers.getLogger(loggerName);
+        this.objectMapper = JacksonUtils.newObjectMapper();
+    }
+
+    @Inject
+    public void setClient(Duniter4jClient client) {
+        this.client = client;
+    }
+
+    @Inject
+    public void setCryptoService(CryptoService cryptoService) {
+        this.cryptoService = cryptoService;
+    }
+
+    @Inject
+    public void setPluginSettings(PluginSettings pluginSettings) {
+        this.pluginSettings = pluginSettings;
+    }
+
+    /* -- protected methods  -- */
+
+
+}
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/AbstractIndexDao.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/AbstractIndexDao.java
new file mode 100644
index 0000000000000000000000000000000000000000..7979af65309455e0e2ec3b032c87589a41dd300d
--- /dev/null
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/AbstractIndexDao.java
@@ -0,0 +1,91 @@
+package org.duniter.elasticsearch.dao;
+
+/*
+ * #%L
+ * UCoin Java Client :: Core API
+ * %%
+ * Copyright (C) 2014 - 2015 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.duniter.core.exception.TechnicalException;
+import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequestBuilder;
+
+/**
+ * Created by Benoit on 08/04/2015.
+ */
+public abstract class AbstractIndexDao<T extends IndexDao> extends AbstractDao implements IndexDao<T> {
+
+    private final String index;
+
+    public AbstractIndexDao(String index) {
+        super("duniter.dao."+index);
+        this.index = index;
+    }
+
+    /**
+     * Create index
+     * @throws JsonProcessingException
+     */
+    protected abstract void createIndex() throws JsonProcessingException;
+
+    @Override
+    public String getIndex() {
+        return index;
+    }
+
+    @Override
+    public T createIndexIfNotExists() {
+        try {
+            if (!client.existsIndex(index)) {
+                createIndex();
+            }
+        }
+        catch(JsonProcessingException e) {
+            throw new TechnicalException(String.format("Error while creating index [%s]", index));
+        }
+        return (T)this;
+    }
+
+    @Override
+    public T deleteIndex() {
+        client.deleteIndexIfExists(index);
+        return (T)this;
+    }
+
+    @Override
+    public boolean existsIndex() {
+        return client.existsIndex(index);
+    }
+
+
+    /* -- protected methods -- */
+
+    protected void deleteIndexIfExists(){
+        if (!client.existsIndex(index)) {
+            return;
+        }
+        if (logger.isInfoEnabled()) {
+            logger.info(String.format("Deleting index [%s]", index));
+        }
+
+        DeleteIndexRequestBuilder deleteIndexRequestBuilder = client.admin().indices().prepareDelete(index);
+        deleteIndexRequestBuilder.execute().actionGet();
+    }
+}
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/AbstractIndexTypeDao.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/AbstractIndexTypeDao.java
new file mode 100644
index 0000000000000000000000000000000000000000..4f1bf5f64014b0c7fc6551c810bbc6508f5daf3c
--- /dev/null
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/AbstractIndexTypeDao.java
@@ -0,0 +1,178 @@
+package org.duniter.elasticsearch.dao;
+
+/*
+ * #%L
+ * UCoin Java Client :: Core API
+ * %%
+ * Copyright (C) 2014 - 2015 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.duniter.core.exception.TechnicalException;
+import org.duniter.elasticsearch.dao.handler.StringReaderHandler;
+import org.elasticsearch.action.bulk.BulkRequestBuilder;
+
+import java.io.File;
+import java.io.InputStream;
+import java.util.Map;
+
+/**
+ * Created by Benoit on 08/04/2015.
+ */
+public abstract class AbstractIndexTypeDao<T extends IndexTypeDao> extends AbstractDao implements IndexTypeDao<T> {
+
+    private final String index;
+    private final String type;
+
+    public AbstractIndexTypeDao(String index, String type) {
+        super("duniter.dao."+index);
+        this.index = index;
+        this.type = type;
+    }
+
+    /**
+     * Create index
+     * @throws JsonProcessingException
+     */
+    protected abstract void createIndex() throws JsonProcessingException;
+
+    @Override
+    public String getIndex() {
+        return index;
+    }
+
+    @Override
+    public String getType() {
+        return type;
+    }
+
+    @Override
+    public T createIndexIfNotExists() {
+        try {
+            if (!client.existsIndex(index)) {
+                createIndex();
+            }
+        }
+        catch(JsonProcessingException e) {
+            throw new TechnicalException(String.format("Error while creating index [%s]", index));
+        }
+        return (T)this;
+    }
+
+    @Override
+    public T deleteIndex() {
+        client.deleteIndexIfExists(index);
+        return (T)this;
+    }
+
+    @Override
+    public boolean isExists(String docId) {
+        return client.isDocumentExists(index, type, docId);
+    }
+
+    public String create(final String json) {
+        return client.indexDocumentFromJson(index, type, json);
+    }
+
+    public void update(final String id, final String json) {
+        client.updateDocumentFromJson(index, type, id, json);
+    }
+
+    public String indexDocumentFromJson(String json) {
+        return client.indexDocumentFromJson(index, type, json);
+    }
+
+    public void updateDocumentFromJson(String id, String json) {
+        client.updateDocumentFromJson(index, type, id, json);
+    }
+
+    /**
+     * Retrieve a field from a document id
+     * @param docId
+     * @return
+     */
+    public Object getFieldById(String docId, String fieldName) {
+        return client.getFieldById(index, type, docId, fieldName);
+    }
+
+    public <T> T getTypedFieldById(String docId, String fieldName) {
+        return client.getTypedFieldById(index, type, docId, fieldName);
+    }
+
+    @Override
+    public Map<String, Object> getMandatoryFieldsById(String docId, String... fieldNames) {
+        return client.getMandatoryFieldsById(index, type, docId, fieldNames);
+    }
+
+    @Override
+    public Map<String, Object> getFieldsById(String docId, String... fieldNames) {
+        return client.getFieldsById(index, type, docId, fieldNames);
+    }
+
+    /**
+     * Retrieve a document by id (safe mode)
+     * @param docId
+     * @return
+     */
+    public <T extends Object> T getSourceByIdOrNull(String docId, Class<T> classOfT, String... fieldNames) {
+        return client.getSourceByIdOrNull(index, type, docId, classOfT, fieldNames);
+    }
+
+    /**
+     * Retrieve a document by id
+     * @param docId
+     * @return
+     */
+    public <T extends Object> T getSourceById(String docId, Class<T> classOfT, String... fieldNames) {
+        return client.getSourceById(index, type, docId, classOfT, fieldNames);
+    }
+
+    public void bulkFromClasspathFile(String classpathFile) {
+        client.bulkFromClasspathFile(classpathFile, index, type, null);
+    }
+
+    public void bulkFromClasspathFile(String classpathFile, StringReaderHandler handler) {
+        client.bulkFromClasspathFile(classpathFile, index, type, handler);
+    }
+
+    public void bulkFromFile(File file) {
+        client.bulkFromFile(file, index, type, null);
+    }
+
+    public void bulkFromFile(File file, StringReaderHandler handler) {
+        client.bulkFromFile(file, index, type, handler);
+    }
+
+    public void bulkFromStream(InputStream is) {
+        client.bulkFromStream(is, index, type, null);
+    }
+
+    public void bulkFromStream(InputStream is, StringReaderHandler handler) {
+        client.bulkFromStream(is, index, type, handler);
+    }
+
+    public void flushDeleteBulk(BulkRequestBuilder bulkRequest) {
+        client.flushDeleteBulk(index, type, bulkRequest);
+    }
+
+    @Override
+    public boolean existsIndex() {
+        return client.existsIndex(index);
+    }
+}
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/BlockDao.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/BlockDao.java
new file mode 100644
index 0000000000000000000000000000000000000000..34d72245f4c047066e16f982a53ba23598d2ddb5
--- /dev/null
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/BlockDao.java
@@ -0,0 +1,42 @@
+package org.duniter.elasticsearch.dao;
+
+import org.duniter.core.beans.Bean;
+import org.duniter.core.client.model.bma.BlockchainBlock;
+
+import java.util.List;
+
+/**
+ * Created by blavenie on 03/04/17.
+ */
+public interface BlockDao extends Bean, TypeDao<BlockDao> {
+
+    void create(BlockchainBlock block, boolean wait);
+
+    /**
+     *
+     * @param currencyName
+     * @param number the block number
+     * @param json block as JSON
+     */
+    void create(String currencyName, String id, byte[] json, boolean wait);
+
+    boolean isExists(String currencyName, String id);
+
+    void update(BlockchainBlock block, boolean wait);
+
+    /**
+     *
+     * @param currencyName
+     * @param number the block number, or -1 for current
+     * @param json block as JSON
+     */
+    void update(String currencyName, String id, byte[] json, boolean wait);
+
+    List<BlockchainBlock> findBlocksByHash(String currencyName, String query);
+
+    int getMaxBlockNumber(String currencyName);
+
+    BlockchainBlock getBlockById(String currencyName, String id);
+
+    void deleteRange(final String currencyName, final int fromNumber, final int toNumber);
+}
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/CurrencyExtendDao.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/CurrencyExtendDao.java
new file mode 100644
index 0000000000000000000000000000000000000000..d501336180a1e1797149109c6ded023a9c204f1c
--- /dev/null
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/CurrencyExtendDao.java
@@ -0,0 +1,9 @@
+package org.duniter.elasticsearch.dao;
+
+import org.duniter.core.client.dao.CurrencyDao;
+
+/**
+ * Created by blavenie on 03/04/17.
+ */
+public interface CurrencyExtendDao extends CurrencyDao, IndexTypeDao<CurrencyExtendDao> {
+}
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/DaoModule.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/DaoModule.java
new file mode 100644
index 0000000000000000000000000000000000000000..f1b870dd4fbb9f2b95aa80b6b08fc98347936f91
--- /dev/null
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/DaoModule.java
@@ -0,0 +1,51 @@
+package org.duniter.elasticsearch.dao;
+
+/*
+ * #%L
+ * duniter4j-elasticsearch-plugin
+ * %%
+ * Copyright (C) 2014 - 2016 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+import org.duniter.core.beans.Bean;
+import org.duniter.core.client.dao.CurrencyDao;
+import org.duniter.core.client.dao.PeerDao;
+import org.duniter.elasticsearch.client.Duniter4jClient;
+import org.duniter.elasticsearch.client.Duniter4jClientImpl;
+import org.duniter.elasticsearch.service.ServiceLocator;
+import org.elasticsearch.common.inject.AbstractModule;
+import org.elasticsearch.common.inject.Module;
+
+public class DaoModule extends AbstractModule implements Module {
+
+    @Override protected void configure() {
+
+        bind(Duniter4jClient.class).to(Duniter4jClientImpl.class).asEagerSingleton();
+
+        bindWithLocator(BlockDao.class);
+        bindWithLocator(PeerDao.class);
+        bindWithLocator(CurrencyDao.class);
+    }
+
+    /* protected methods */
+
+    protected <T extends Bean> void bindWithLocator(Class<T> clazz) {
+        bind(clazz).toProvider(new ServiceLocator.Provider<>(clazz));
+    }
+
+}
\ No newline at end of file
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/IndexDao.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/IndexDao.java
new file mode 100644
index 0000000000000000000000000000000000000000..5d5bb1c809b5578746aa80344db1609516c4dd1e
--- /dev/null
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/IndexDao.java
@@ -0,0 +1,18 @@
+package org.duniter.elasticsearch.dao;
+
+import org.duniter.elasticsearch.dao.handler.StringReaderHandler;
+
+/**
+ * Created by blavenie on 30/03/17.
+ */
+
+public interface IndexDao<T extends IndexDao> {
+
+    T createIndexIfNotExists();
+
+    T deleteIndex();
+
+    String getIndex();
+
+    boolean existsIndex();
+}
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/IndexTypeDao.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/IndexTypeDao.java
new file mode 100644
index 0000000000000000000000000000000000000000..047280fbf0b7c93504df0eb68e8aea7b8403ef3a
--- /dev/null
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/IndexTypeDao.java
@@ -0,0 +1,38 @@
+package org.duniter.elasticsearch.dao;
+
+import org.duniter.elasticsearch.dao.handler.StringReaderHandler;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import java.util.Map;
+
+/**
+ * Created by blavenie on 03/04/17.
+ */
+public interface IndexTypeDao<T extends IndexTypeDao> {
+
+    T createIndexIfNotExists();
+
+    T deleteIndex();
+
+    String getIndex();
+
+    boolean existsIndex();
+
+    XContentBuilder createTypeMapping();
+
+    String getType();
+
+    boolean isExists(String docId);
+
+    Object getFieldById(String docId, String fieldName);
+
+    Map<String, Object> getFieldsById(String docId, String... fieldNames);
+
+    <B> B getTypedFieldById(String docId, String fieldName);
+
+    Map<String, Object> getMandatoryFieldsById(String docId, String... fieldNames);
+
+    void bulkFromClasspathFile(String classpathFile);
+
+    void bulkFromClasspathFile(String classpathFile, StringReaderHandler handler);
+}
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/TypeDao.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/TypeDao.java
new file mode 100644
index 0000000000000000000000000000000000000000..d03df4cfa753b73e5228dd2fe641e2cd110cbc22
--- /dev/null
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/TypeDao.java
@@ -0,0 +1,16 @@
+package org.duniter.elasticsearch.dao;
+
+import org.elasticsearch.common.xcontent.XContentBuilder;
+
+import java.util.Map;
+
+/**
+ * Created by blavenie on 30/03/17.
+ */
+
+public interface TypeDao<T extends TypeDao> {
+
+    XContentBuilder createTypeMapping();
+
+    String getType();
+}
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/handler/AddSequenceAttributeHandler.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/handler/AddSequenceAttributeHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..517ce2b45737d0a65ff06f1250eaa81d2b46881e
--- /dev/null
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/handler/AddSequenceAttributeHandler.java
@@ -0,0 +1,26 @@
+package org.duniter.elasticsearch.dao.handler;
+
+import java.util.regex.Pattern;
+
+public class AddSequenceAttributeHandler implements StringReaderHandler {
+        private int order;
+        private final String attributeName;
+        private final Pattern filterPattern;
+        public AddSequenceAttributeHandler(String attributeName, String filterRegex, int startValue) {
+            this.order = startValue;
+            this.attributeName = attributeName;
+            this.filterPattern = Pattern.compile(filterRegex);
+        }
+
+        @Override
+        public String onReadLine(String line) {
+            // add 'order' field into
+            if (filterPattern.matcher(line).matches()) {
+                return String.format("%s, \"%s\": %d}",
+                        line.substring(0, line.length()-1),
+                        attributeName,
+                        order++);
+            }
+            return line;
+        }
+    }
\ No newline at end of file
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/handler/StringReaderHandler.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/handler/StringReaderHandler.java
new file mode 100644
index 0000000000000000000000000000000000000000..46082040768a6ea51685baac903559a39a0a4432
--- /dev/null
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/handler/StringReaderHandler.java
@@ -0,0 +1,6 @@
+package org.duniter.elasticsearch.dao.handler;
+
+public interface StringReaderHandler {
+
+        String onReadLine(String line);
+    }
\ No newline at end of file
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/impl/BlockDaoImpl.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/impl/BlockDaoImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..2964b8dab7c19a0d9d6931d1cf727466393d00d8
--- /dev/null
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/impl/BlockDaoImpl.java
@@ -0,0 +1,388 @@
+package org.duniter.elasticsearch.dao.impl;
+
+/*
+ * #%L
+ * UCoin Java Client :: Core API
+ * %%
+ * Copyright (C) 2014 - 2015 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.common.collect.Lists;
+import org.duniter.core.client.model.bma.BlockchainBlock;
+import org.duniter.core.exception.TechnicalException;
+import org.duniter.core.util.Preconditions;
+import org.duniter.core.util.StringUtils;
+import org.duniter.core.util.json.JsonSyntaxException;
+import org.duniter.elasticsearch.PluginSettings;
+import org.duniter.elasticsearch.dao.AbstractDao;
+import org.duniter.elasticsearch.dao.BlockDao;
+import org.elasticsearch.action.ActionRequestBuilder;
+import org.elasticsearch.action.bulk.BulkRequestBuilder;
+import org.elasticsearch.action.index.IndexRequestBuilder;
+import org.elasticsearch.action.search.SearchRequestBuilder;
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.action.search.SearchType;
+import org.elasticsearch.action.update.UpdateRequestBuilder;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.search.SearchHitField;
+import org.elasticsearch.search.aggregations.AggregationBuilders;
+import org.elasticsearch.search.aggregations.metrics.max.Max;
+import org.elasticsearch.search.highlight.HighlightField;
+import org.elasticsearch.search.sort.SortOrder;
+
+import java.io.IOException;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Created by Benoit on 30/03/2015.
+ */
+public class BlockDaoImpl extends AbstractDao implements BlockDao {
+
+    public static final String BLOCK_TYPE = "block";
+
+    public BlockDaoImpl(){
+        super("duniter.dao.block");
+    }
+
+    @Override
+    public String getType() {
+        return BLOCK_TYPE;
+    }
+
+    public void create(BlockchainBlock block, boolean wait) {
+        Preconditions.checkNotNull(block);
+        Preconditions.checkArgument(StringUtils.isNotBlank(block.getCurrency()));
+        Preconditions.checkNotNull(block.getHash());
+        Preconditions.checkNotNull(block.getNumber());
+
+        // Serialize into JSON
+        // WARN: must use GSON, to have same JSON result (e.g identities and joiners field must be converted into String)
+        try {
+            String json = objectMapper.writeValueAsString(block);
+
+            // Preparing
+            IndexRequestBuilder request = client.prepareIndex(block.getCurrency(), BLOCK_TYPE)
+                    .setId(block.getNumber().toString())
+                    .setSource(json);
+
+            // Execute
+            safeExecute(request, wait);
+        }
+        catch(JsonProcessingException e) {
+            throw new TechnicalException(e);
+        }
+    }
+
+    /**
+     *
+     * @param currencyName
+     * @param id the block id
+     * @param json block as JSON
+     */
+    public void create(String currencyName, String id, byte[] json, boolean wait) {
+        Preconditions.checkNotNull(currencyName);
+        Preconditions.checkNotNull(id);
+        Preconditions.checkNotNull(json);
+        Preconditions.checkArgument(json.length > 0);
+
+        // Preparing indexBlocksFromNode
+        IndexRequestBuilder request = client.prepareIndex(currencyName, BLOCK_TYPE)
+                .setId(id)
+                .setRefresh(true)
+                .setSource(json);
+
+        // Execute
+        safeExecute(request, wait);
+    }
+
+    public boolean isExists(String currencyName, String id) {
+        return client.isDocumentExists(currencyName, BLOCK_TYPE, id);
+    }
+
+    public void update(BlockchainBlock block, boolean wait) {
+        Preconditions.checkNotNull(block);
+        Preconditions.checkArgument(StringUtils.isNotBlank(block.getCurrency()));
+        Preconditions.checkNotNull(block.getHash());
+        Preconditions.checkNotNull(block.getNumber());
+
+        // Serialize into JSON
+        // WARN: must use GSON, to have same JSON result (e.g identities and joiners field must be converted into String)
+        try {
+            String json = objectMapper.writeValueAsString(block);
+
+            // Preparing
+            UpdateRequestBuilder request = client.prepareUpdate(block.getCurrency(), BLOCK_TYPE, block.getNumber().toString())
+                    .setRefresh(true)
+                    .setDoc(json);
+
+            // Execute
+            safeExecute(request, wait);
+        }
+        catch(JsonProcessingException e) {
+            throw new TechnicalException(e);
+        }
+    }
+
+    /**
+     *
+     * @param currencyName
+     * @param id the block id
+     * @param json block as JSON
+     */
+    public void update(String currencyName, String id, byte[] json, boolean wait) {
+        Preconditions.checkNotNull(currencyName);
+        Preconditions.checkNotNull(json);
+        Preconditions.checkArgument(json.length > 0);
+
+        // Preparing indexBlocksFromNode
+        UpdateRequestBuilder request = client.prepareUpdate(currencyName, BLOCK_TYPE, id)
+                .setRefresh(true)
+                .setDoc(json);
+
+        // Execute
+        safeExecute(request, wait);
+    }
+
+    public List<BlockchainBlock> findBlocksByHash(String currencyName, String query) {
+        String[] queryParts = query.split("[\\t ]+");
+
+        // Prepare request
+        SearchRequestBuilder searchRequest = client
+                .prepareSearch(currencyName)
+                .setTypes(BLOCK_TYPE)
+                .setSearchType(SearchType.DFS_QUERY_THEN_FETCH);
+
+        // If only one term, search as prefix
+        if (queryParts.length == 1) {
+            searchRequest.setQuery(QueryBuilders.prefixQuery("hash", query));
+        }
+
+        // If more than a word, search on terms match
+        else {
+            searchRequest.setQuery(QueryBuilders.matchQuery("hash", query));
+        }
+
+        // Sort as score/memberCount
+        searchRequest.addSort("_score", SortOrder.DESC)
+                .addSort("number", SortOrder.DESC);
+
+        // Highlight matched words
+        searchRequest.setHighlighterTagsSchema("styled")
+                .addHighlightedField("hash")
+                .addFields("hash")
+                .addFields("*", "_source");
+
+        // Execute query
+        SearchResponse searchResponse = searchRequest.execute().actionGet();
+
+        // Read query result
+        return toBlocks(searchResponse, true);
+    }
+
+    public int getMaxBlockNumber(String currencyName) {
+        // Prepare request
+        SearchRequestBuilder searchRequest = client
+                .prepareSearch(currencyName)
+                .setTypes(BLOCK_TYPE)
+                .setSearchType(SearchType.DFS_QUERY_THEN_FETCH);
+
+        // Get max(number)
+        searchRequest.addAggregation(AggregationBuilders.max("max_number").field("number"));
+
+        // Execute query
+        SearchResponse searchResponse = searchRequest.execute().actionGet();
+
+        // Read query result
+        Max result = searchResponse.getAggregations().get("max_number");
+        if (result == null) {
+            return -1;
+        }
+
+        return (result.getValue() == Double.NEGATIVE_INFINITY)
+                ? -1
+                : (int)result.getValue();
+    }
+
+    @Override
+    public XContentBuilder createTypeMapping() {
+        try {
+            XContentBuilder mapping = XContentFactory.jsonBuilder()
+                    .startObject()
+                    .startObject(BLOCK_TYPE)
+                    .startObject("properties")
+
+                    // block number
+                    .startObject("number")
+                    .field("type", "integer")
+                    .endObject()
+
+                    // hash
+                    .startObject("hash")
+                    .field("type", "string")
+                    .endObject()
+
+                    // issuer
+                    .startObject("issuer")
+                    .field("type", "string")
+                    .field("index", "not_analyzed")
+                    .endObject()
+
+                    // previous hash
+                    .startObject("previousHash")
+                    .field("type", "string")
+                    .endObject()
+
+                    // membercount
+                    .startObject("memberCount")
+                    .field("type", "integer")
+                    .endObject()
+
+                    // membersChanges
+                    .startObject("membersChanges")
+                    .field("type", "string")
+                    .endObject()
+
+                    // unitbase
+                    .startObject("unitbase")
+                    .field("type", "integer")
+                    .endObject()
+
+                    // membersChanges
+                    .startObject("monetaryMass")
+                    .field("type", "long")
+                    .endObject()
+
+                    // dividend
+                    .startObject("dividend")
+                    .field("type", "integer")
+                    .endObject()
+
+                    // identities:
+                    //.startObject("identities")
+                    //.endObject()
+
+                    .endObject()
+                    .endObject().endObject();
+
+            return mapping;
+        }
+        catch(IOException ioe) {
+            throw new TechnicalException("Error while getting mapping for block index: " + ioe.getMessage(), ioe);
+        }
+    }
+
+    public BlockchainBlock getBlockById(String currencyName, String id) {
+        return client.getSourceById(currencyName, BLOCK_TYPE, id, BlockchainBlock.class);
+    }
+
+    /**
+     * Delete blocks from a start number (using bulk)
+     * @param currencyName
+     * @param fromNumber
+     */
+    public void deleteRange(final String currencyName, final int fromNumber, final int toNumber) {
+
+        int bulkSize = pluginSettings.getIndexBulkSize();
+
+        BulkRequestBuilder bulkRequest = client.prepareBulk();
+        for (int number=fromNumber; number<=toNumber; number++) {
+
+            bulkRequest.add(
+                    client.prepareDelete(currencyName, BLOCK_TYPE, String.valueOf(number))
+            );
+
+            // Flush the bulk if not empty
+            if ((fromNumber - number % bulkSize) == 0) {
+                client.flushDeleteBulk(currencyName, BLOCK_TYPE, bulkRequest);
+                bulkRequest = client.prepareBulk();
+            }
+        }
+
+        // last flush
+        client.flushDeleteBulk(currencyName, BLOCK_TYPE, bulkRequest);
+    }
+
+    /* -- Internal methods -- */
+
+    protected List<BlockchainBlock> toBlocks(SearchResponse response, boolean withHighlight) {
+        // Read query result
+        List<BlockchainBlock> result = Lists.newArrayList();
+        response.getHits().forEach(searchHit -> {
+            BlockchainBlock block;
+            if (searchHit.source() != null) {
+                String jsonString = new String(searchHit.source());
+                try {
+                    block = objectMapper.readValue(jsonString, BlockchainBlock.class);
+                } catch(Exception e) {
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("Error while parsing block from JSON:\n" + jsonString);
+                    }
+                    throw new JsonSyntaxException("Error while read block from JSON: " + e.getMessage(), e);
+                }
+            }
+            else {
+                block = new BlockchainBlock();
+                SearchHitField field = searchHit.getFields().get("hash");
+                block.setHash(field.getValue());
+            }
+            result.add(block);
+
+            // If possible, use highlights
+            if (withHighlight) {
+                Map<String, HighlightField> fields = searchHit.getHighlightFields();
+                for (HighlightField field : fields.values()) {
+                    String blockNameHighLight = field.getFragments()[0].string();
+                    block.setHash(blockNameHighLight);
+                }
+            }
+        });
+
+        return result;
+    }
+
+    protected void safeExecute(ActionRequestBuilder<?, ?, ?> request, boolean wait) {
+        // Execute in a pool
+        if (!wait) {
+            boolean acceptedInPool = false;
+            while(!acceptedInPool)
+                try {
+                    request.execute();
+                    acceptedInPool = true;
+                }
+                catch(EsRejectedExecutionException e) {
+                    // not accepted, so wait
+                    try {
+                        Thread.sleep(1000); // 1s
+                    }
+                    catch(InterruptedException e2) {
+                        // silent
+                    }
+                }
+
+        } else {
+            request.execute().actionGet();
+        }
+    }
+}
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/impl/CurrencyDaoImpl.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/impl/CurrencyDaoImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..a077e54d6a37f6ffbceaafac44062d8ff78705f5
--- /dev/null
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/impl/CurrencyDaoImpl.java
@@ -0,0 +1,245 @@
+package org.duniter.elasticsearch.dao.impl;
+
+/*
+ * #%L
+ * UCoin Java :: Core Client API
+ * %%
+ * Copyright (C) 2014 - 2016 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.common.collect.Lists;
+import org.duniter.core.client.dao.CurrencyDao;
+import org.duniter.core.client.model.local.Currency;
+import org.duniter.core.exception.TechnicalException;
+import org.duniter.core.util.Preconditions;
+import org.duniter.core.util.StringUtils;
+import org.duniter.elasticsearch.dao.AbstractIndexTypeDao;
+import org.duniter.elasticsearch.dao.CurrencyExtendDao;
+import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
+import org.elasticsearch.action.index.IndexRequestBuilder;
+import org.elasticsearch.action.update.UpdateRequestBuilder;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * Created by blavenie on 29/12/15.
+ */
+public class CurrencyDaoImpl extends AbstractIndexTypeDao<CurrencyExtendDao> implements CurrencyExtendDao {
+
+    protected static final String REGEX_WORD_SEPARATOR = "[-\\t@# _]+";
+
+    public static final String INDEX = "currency";
+    public static final String RECORD_TYPE = "record";
+
+    public CurrencyDaoImpl(){
+        super(INDEX, RECORD_TYPE);
+    }
+
+    @Override
+    public org.duniter.core.client.model.local.Currency create(final org.duniter.core.client.model.local.Currency currency) {
+
+        try {
+
+            if (currency instanceof org.duniter.core.client.model.elasticsearch.Currency) {
+                fillTags((org.duniter.core.client.model.elasticsearch.Currency)currency);
+            }
+
+            // Serialize into JSON
+            byte[] json = objectMapper.writeValueAsBytes(currency);
+
+            System.out.println(objectMapper.writeValueAsString(currency));
+
+            // Preparing indexBlocksFromNode
+            IndexRequestBuilder indexRequest = client.prepareIndex(INDEX, RECORD_TYPE)
+                    .setId(currency.getId())
+                    .setSource(json);
+
+            // Execute indexBlocksFromNode
+            indexRequest
+                    .setRefresh(true)
+                    .execute().actionGet();
+
+        } catch(JsonProcessingException e) {
+            throw new TechnicalException(e);
+        }
+
+        return currency;
+    }
+
+    @Override
+    public org.duniter.core.client.model.local.Currency update(final org.duniter.core.client.model.local.Currency currency) {
+        try {
+
+            if (currency instanceof org.duniter.core.client.model.elasticsearch.Currency) {
+                fillTags((org.duniter.core.client.model.elasticsearch.Currency)currency);
+            }
+
+            // Serialize into JSON
+            byte[] json = objectMapper.writeValueAsBytes(currency);
+
+            UpdateRequestBuilder updateRequest = client.prepareUpdate(INDEX, RECORD_TYPE, currency.getId())
+                    .setDoc(json);
+
+            // Execute indexBlocksFromNode
+            updateRequest
+                    .setRefresh(true)
+                    .execute();
+
+        } catch(JsonProcessingException e) {
+            throw new TechnicalException(e);
+        }
+
+
+        return currency;
+    }
+
+    @Override
+    public void remove(final org.duniter.core.client.model.local.Currency currency) {
+        Preconditions.checkNotNull(currency);
+        Preconditions.checkArgument(StringUtils.isNotBlank(currency.getId()));
+
+        // Delete the document
+        client.prepareDelete(INDEX, RECORD_TYPE, currency.getId()).execute().actionGet();
+    }
+
+    @Override
+    public org.duniter.core.client.model.local.Currency getById(String currencyId) {
+        return client.getSourceByIdOrNull(INDEX, RECORD_TYPE, currencyId, org.duniter.core.client.model.elasticsearch.Currency.class);
+    }
+
+    @Override
+    public List<Currency> getCurrencies(long accountId) {
+        throw new TechnicalException("Not implemented yet");
+    }
+
+    @Override
+    public long getLastUD(String currencyId) {
+        org.duniter.core.client.model.local.Currency currency = getById(currencyId);
+        if (currency == null) {
+            return -1;
+        }
+        return currency.getLastUD();
+    }
+
+    @Override
+    public Map<Integer, Long> getAllUD(String currencyId) {
+
+        throw new TechnicalException("Not implemented yet");
+    }
+
+    @Override
+    public void insertUDs(String currencyId,  Map<Integer, Long> newUDs) {
+        throw new TechnicalException("Not implemented yet");
+    }
+
+    public boolean existsIndex() {
+        return client.existsIndex(INDEX);
+    }
+
+    @Override
+    public XContentBuilder createTypeMapping() {
+        try {
+            XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject(RECORD_TYPE)
+                    .startObject("properties")
+
+                    // currency
+                    .startObject("currency")
+                    .field("type", "string")
+                    .endObject()
+
+                    // firstBlockSignature
+                    .startObject("firstBlockSignature")
+                    .field("type", "string")
+                    .field("index", "not_analyzed")
+                    .endObject()
+
+                    // member count
+                    .startObject("membersCount")
+                    .field("type", "long")
+                    .endObject()
+
+                    // lastUD
+                    .startObject("lastUD")
+                    .field("type", "long")
+                    .endObject()
+
+                    // unitbase
+                    .startObject("unitbase")
+                    .field("type", "integer")
+                    .endObject()
+
+                    // issuer
+                    .startObject("issuer")
+                    .field("type", "string")
+                    .field("index", "not_analyzed")
+                    .endObject()
+
+                    // tags
+                    .startObject("tags")
+                    .field("type", "completion")
+                    .field("search_analyzer", "simple")
+                    .field("analyzer", "simple")
+                    .field("preserve_separators", "false")
+
+                    .endObject()
+                    .endObject()
+                    .endObject().endObject();
+
+            return mapping;
+        }
+        catch(IOException ioe) {
+            throw new TechnicalException(String.format("Error while getting mapping for index [%s/%s]: %s", INDEX, RECORD_TYPE, ioe.getMessage()), ioe);
+        }
+    }
+
+    /* -- internal methods -- */
+
+    @Override
+    protected void createIndex() throws JsonProcessingException {
+        logger.info(String.format("Creating index [%s]", INDEX));
+
+        CreateIndexRequestBuilder createIndexRequestBuilder = client.admin().indices().prepareCreate(INDEX);
+        org.elasticsearch.common.settings.Settings indexSettings = org.elasticsearch.common.settings.Settings.settingsBuilder()
+                .put("number_of_shards", 3)
+                .put("number_of_replicas", 1)
+                //.put("analyzer", createDefaultAnalyzer())
+                .build();
+        createIndexRequestBuilder.setSettings(indexSettings);
+        createIndexRequestBuilder.addMapping(RECORD_TYPE, createTypeMapping());
+        createIndexRequestBuilder.execute().actionGet();
+    }
+
+    protected void fillTags(org.duniter.core.client.model.elasticsearch.Currency currency) {
+        String currencyName = currency.getCurrencyName();
+        String[] tags = currencyName.split(REGEX_WORD_SEPARATOR);
+        List<String> tagsList = Lists.newArrayList(tags);
+
+        // Convert as a sentence (replace separator with a space)
+        String sentence = currencyName.replaceAll(REGEX_WORD_SEPARATOR, " ");
+        if (!tagsList.contains(sentence)) {
+            tagsList.add(sentence);
+        }
+
+        currency.setTags(tagsList.toArray(new String[tagsList.size()]));
+    }
+
+}
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/impl/PeerDaoImpl.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/impl/PeerDaoImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..d80d3f0d0f34a47e3b8f53edd31330d5076f2767
--- /dev/null
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/dao/impl/PeerDaoImpl.java
@@ -0,0 +1,195 @@
+package org.duniter.elasticsearch.dao.impl;
+
+/*
+ * #%L
+ * UCoin Java :: Core Client API
+ * %%
+ * Copyright (C) 2014 - 2016 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.duniter.core.client.dao.PeerDao;
+import org.duniter.core.client.model.local.Peer;
+import org.duniter.core.exception.TechnicalException;
+import org.duniter.core.util.Preconditions;
+import org.duniter.core.util.StringUtils;
+import org.duniter.elasticsearch.dao.AbstractDao;
+import org.duniter.elasticsearch.dao.TypeDao;
+import org.elasticsearch.action.index.IndexRequestBuilder;
+import org.elasticsearch.action.update.UpdateRequestBuilder;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+
+import java.io.IOException;
+import java.util.List;
+
+/**
+ * Created by blavenie on 29/12/15.
+ */
+public class PeerDaoImpl extends AbstractDao implements PeerDao, TypeDao<PeerDaoImpl> {
+
+
+    public static final String PEER_TYPE = "peer";
+
+    public PeerDaoImpl(){
+        super("duniter.dao.peer");
+    }
+
+    @Override
+    public String getType() {
+        return PEER_TYPE;
+    }
+
+    @Override
+    public Peer create(Peer peer) {
+        Preconditions.checkNotNull(peer);
+        Preconditions.checkArgument(StringUtils.isNotBlank(peer.getId()));
+        Preconditions.checkArgument(StringUtils.isNotBlank(peer.getCurrency()));
+        Preconditions.checkNotNull(peer.getHash());
+        Preconditions.checkNotNull(peer.getHost());
+        Preconditions.checkNotNull(peer.getApi());
+
+        // Serialize into JSON
+        // WARN: must use GSON, to have same JSON result (e.g identities and joiners field must be converted into String)
+        try {
+            String json = objectMapper.writeValueAsString(peer);
+
+            // Preparing indexBlocksFromNode
+            IndexRequestBuilder indexRequest = client.prepareIndex(peer.getCurrency(), PEER_TYPE)
+                    .setId(peer.getId())
+                    .setSource(json);
+
+            // Execute indexBlocksFromNode
+            indexRequest
+                    .setRefresh(true)
+                    .execute();
+        }
+        catch(JsonProcessingException e) {
+            throw new TechnicalException(e);
+        }
+        return peer;
+    }
+
+    @Override
+    public Peer update(Peer peer) {
+        Preconditions.checkNotNull(peer);
+        Preconditions.checkArgument(StringUtils.isNotBlank(peer.getId()));
+        Preconditions.checkArgument(StringUtils.isNotBlank(peer.getCurrency()));
+        Preconditions.checkNotNull(peer.getHash());
+        Preconditions.checkNotNull(peer.getHost());
+        Preconditions.checkNotNull(peer.getApi());
+
+        // Serialize into JSON
+        try {
+            String json = objectMapper.writeValueAsString(peer);
+
+            // Preparing indexBlocksFromNode
+            UpdateRequestBuilder updateRequest = client.prepareUpdate(peer.getCurrency(), PEER_TYPE, peer.getId())
+                    .setDoc(json);
+
+            // Execute indexBlocksFromNode
+            updateRequest
+                    .setRefresh(true)
+                    .execute();
+        }
+        catch(JsonProcessingException e) {
+            throw new TechnicalException(e);
+        }
+        return peer;
+    }
+
+    @Override
+    public Peer getById(String id) {
+        throw new TechnicalException("not implemented");
+    }
+
+    @Override
+    public void remove(Peer peer) {
+        Preconditions.checkNotNull(peer);
+        Preconditions.checkArgument(StringUtils.isNotBlank(peer.getId()));
+        Preconditions.checkArgument(StringUtils.isNotBlank(peer.getCurrency()));
+
+        // Delete the document
+        client.prepareDelete(peer.getCurrency(), PEER_TYPE, peer.getId()).execute().actionGet();
+    }
+
+    @Override
+    public List<Peer> getPeersByCurrencyId(String currencyId) {
+        throw new TechnicalException("no implemented: loading all peers may be unsafe for memory...");
+    }
+
+    @Override
+    public boolean isExists(String currencyId, String peerId) {
+        return client.isDocumentExists(currencyId, PEER_TYPE, peerId);
+    }
+
+    @Override
+    public XContentBuilder createTypeMapping() {
+        try {
+            XContentBuilder mapping = XContentFactory.jsonBuilder()
+                    .startObject()
+                    .startObject(PEER_TYPE)
+                    .startObject("properties")
+
+                    // currency
+                    .startObject("currency")
+                    .field("type", "string")
+                    .endObject()
+
+                    // pubkey
+                    .startObject("pubkey")
+                    .field("type", "string")
+                    .field("index", "not_analyzed")
+                    .endObject()
+
+                    // api
+                    .startObject("api")
+                    .field("type", "string")
+                    .field("index", "not_analyzed")
+                    .endObject()
+
+                    // uid
+                    .startObject("uid")
+                    .field("type", "string")
+                    .endObject()
+
+                    // dns
+                    .startObject("dns")
+                    .field("type", "string")
+                    .endObject()
+
+                    // ipv4
+                    .startObject("ipv4")
+                    .field("type", "string")
+                    .endObject()
+
+                    // ipv6
+                    .startObject("ipv6")
+                    .field("type", "string")
+                    .endObject()
+
+                    .endObject()
+                    .endObject().endObject();
+
+            return mapping;
+        }
+        catch(IOException ioe) {
+            throw new TechnicalException("Error while getting mapping for peer index: " + ioe.getMessage(), ioe);
+        }
+    }
+}
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/RestModule.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/RestModule.java
index 7957f63d1d1e7e2c7125b07f6cdd3d772796e5e9..fa523902afec82c88d27b34c4019b94cac7d168f 100644
--- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/RestModule.java
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/RestModule.java
@@ -49,7 +49,7 @@ public class RestModule extends AbstractModule implements Module {
         bind(RestImageAttachmentAction.class).asEagerSingleton();
 
         // Currency
-        bind(RestCurrencyIndexAction.class).asEagerSingleton();
+        //bind(RestCurrencyIndexAction.class).asEagerSingleton();
 
     }
 }
\ No newline at end of file
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/currency/RestCurrencyIndexAction.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/currency/RestCurrencyIndexAction.java
index d10d70e394561980b2655fa9e89733bf693c82e4..674b7e720af4139b95600cb7f05b832b30ed3e3e 100644
--- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/currency/RestCurrencyIndexAction.java
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/rest/currency/RestCurrencyIndexAction.java
@@ -22,6 +22,7 @@ package org.duniter.elasticsearch.rest.currency;
  * #L%
  */
 
+import org.duniter.core.exception.TechnicalException;
 import org.duniter.elasticsearch.rest.AbstractRestPostIndexAction;
 import org.duniter.elasticsearch.rest.security.RestSecurityController;
 import org.duniter.elasticsearch.service.CurrencyService;
@@ -41,7 +42,9 @@ public class RestCurrencyIndexAction extends AbstractRestPostIndexAction {
                                    RestSecurityController securityController, CurrencyService currencyService) {
         super(settings, controller, client, securityController,
                 CurrencyService.INDEX, CurrencyService.RECORD_TYPE,
-                (json) -> currencyService.indexCurrencyFromJson(json));
+                (json) -> {
+                    throw new TechnicalException("Not implemented yet");/*currencyService.indexCurrencyFromJson(json)*/
+                });
     }
 
 }
\ No newline at end of file
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/AbstractService.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/AbstractService.java
index eb115021d5cb3c4d88d681423286a220c97c32bb..f4e61aacbed844b003bf4c0cae2552c1a27a28e3 100644
--- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/AbstractService.java
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/AbstractService.java
@@ -23,152 +23,91 @@ package org.duniter.elasticsearch.service;
  */
 
 
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableSet;
-import org.apache.commons.collections4.MapUtils;
 import org.duniter.core.beans.Bean;
 import org.duniter.core.client.model.bma.jackson.JacksonUtils;
 import org.duniter.core.client.model.elasticsearch.Record;
 import org.duniter.core.exception.TechnicalException;
 import org.duniter.core.service.CryptoService;
-import org.duniter.core.util.CollectionUtils;
-import org.duniter.core.util.Preconditions;
-import org.duniter.core.util.StringUtils;
 import org.duniter.elasticsearch.PluginSettings;
-import org.duniter.elasticsearch.exception.AccessDeniedException;
+import org.duniter.elasticsearch.client.Duniter4jClient;
 import org.duniter.elasticsearch.exception.InvalidFormatException;
 import org.duniter.elasticsearch.exception.InvalidSignatureException;
-import org.duniter.elasticsearch.exception.NotFoundException;
 import org.elasticsearch.ElasticsearchException;
-import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequestBuilder;
-import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsRequestBuilder;
-import org.elasticsearch.action.admin.indices.exists.indices.IndicesExistsResponse;
-import org.elasticsearch.action.bulk.BulkItemResponse;
-import org.elasticsearch.action.bulk.BulkRequest;
-import org.elasticsearch.action.bulk.BulkRequestBuilder;
-import org.elasticsearch.action.bulk.BulkResponse;
-import org.elasticsearch.action.get.GetResponse;
-import org.elasticsearch.action.index.IndexResponse;
-import org.elasticsearch.action.search.SearchPhaseExecutionException;
-import org.elasticsearch.action.search.SearchRequestBuilder;
-import org.elasticsearch.action.search.SearchResponse;
-import org.elasticsearch.action.search.SearchType;
 import org.elasticsearch.client.Client;
-import org.elasticsearch.client.Requests;
-import org.elasticsearch.common.bytes.BytesArray;
 import org.elasticsearch.common.logging.ESLogger;
 import org.elasticsearch.common.logging.Loggers;
-import org.elasticsearch.index.query.QueryBuilders;
-import org.elasticsearch.search.SearchHit;
-import org.elasticsearch.search.SearchHitField;
 import org.nuiton.i18n.I18n;
 
-import java.io.*;
-import java.util.*;
-import java.util.regex.Pattern;
+import java.io.IOException;
+import java.util.Set;
 
 /**
  * Created by Benoit on 08/04/2015.
  */
 public abstract class AbstractService implements Bean {
 
-
     protected final ESLogger logger;
-    protected final Client client;
-    protected final PluginSettings pluginSettings;
     protected final ObjectMapper objectMapper;
-    protected final CryptoService cryptoService;
+
+    protected Duniter4jClient client;
+    protected PluginSettings pluginSettings;
+    protected CryptoService cryptoService;
     protected final int retryCount;
     protected final int retryWaitDuration;
 
-    public AbstractService(String loggerName, Client client, PluginSettings pluginSettings) {
+    public AbstractService(String loggerName, Duniter4jClient client, PluginSettings pluginSettings) {
         this(loggerName, client, pluginSettings, null);
     }
 
-    public AbstractService(Client client, PluginSettings pluginSettings) {
+    public AbstractService(Duniter4jClient client, PluginSettings pluginSettings) {
         this(client, pluginSettings, null);
     }
 
-    public AbstractService(Client client, PluginSettings pluginSettings, CryptoService cryptoService) {
+    public AbstractService(Duniter4jClient client, PluginSettings pluginSettings, CryptoService cryptoService) {
         this("duniter", client, pluginSettings, cryptoService);
     }
 
-    public AbstractService(String loggerName, Client client, PluginSettings pluginSettings, CryptoService cryptoService) {
+    public AbstractService(String loggerName, Duniter4jClient client, PluginSettings pluginSettings, CryptoService cryptoService) {
+        super();
         this.logger = Loggers.getLogger(loggerName);
         this.client = client;
+        this.objectMapper = JacksonUtils.newObjectMapper();
         this.pluginSettings = pluginSettings;
         this.cryptoService = cryptoService;
-        this.objectMapper = new ObjectMapper();
-        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
         this.retryCount = pluginSettings.getNodeRetryCount();
         this.retryWaitDuration = pluginSettings.getNodeRetryWaitDuration();
     }
 
-    /* -- protected methods  -- */
-
-    protected boolean existsIndex(String indexes) {
-        IndicesExistsRequestBuilder requestBuilder = client.admin().indices().prepareExists(indexes);
-        IndicesExistsResponse response = requestBuilder.execute().actionGet();
-        return response.isExists();
-    }
-
-    protected void deleteIndexIfExists(String indexName){
-        if (!existsIndex(indexName)) {
-            return;
-        }
-        if (logger.isInfoEnabled()) {
-            logger.info(String.format("Deleting index [%s]", indexName));
-        }
-
-        DeleteIndexRequestBuilder deleteIndexRequestBuilder = client.admin().indices().prepareDelete(indexName);
-        deleteIndexRequestBuilder.execute().actionGet();
-    }
-
-    protected String checkIssuerAndIndexDocumentFromJson(String index, String type, String json) {
-
-        JsonNode actualObj = readAndVerifyIssuerSignature(json);
-        String issuer = getIssuer(actualObj);
-
+    /* -- protected methods --*/
 
-        if (logger.isDebugEnabled()) {
-            logger.debug(String.format("Indexing a %s from issuer [%s]", type, issuer.substring(0, 8)));
-        }
-
-        return indexDocumentFromJson(index, type, json);
-    }
-
-    protected String indexDocumentFromJson(String index, String type, String json) {
-        IndexResponse response = client.prepareIndex(index, type)
-                .setSource(json)
-                .setRefresh(true)
-                .execute().actionGet();
-        return response.getId();
-    }
-    protected void checkIssuerAndUpdateDocumentFromJson(String index, String type, String id, String json) {
+    protected <T> T executeWithRetry(RetryFunction<T> retryFunction) throws TechnicalException{
+        int retry = 0;
+        while (retry < retryCount) {
+            try {
+                return retryFunction.execute();
+            } catch (TechnicalException e) {
+                retry++;
 
-        JsonNode actualObj = readAndVerifyIssuerSignature(json);
-        String issuer = getIssuer(actualObj);
+                if (retry == retryCount) {
+                    throw e;
+                }
 
-        // Check same document issuer
-        checkSameDocumentIssuer(index, type, id, issuer);
+                if (logger.isDebugEnabled()) {
+                    logger.debug(I18n.t("duniter4j.removeServiceUtils.waitThenRetry", e.getMessage(), retry, retryCount));
+                }
 
-        if (logger.isDebugEnabled()) {
-            logger.debug(String.format("Updating %s [%s] from issuer [%s]", type, id, issuer.substring(0, 8)));
+                try {
+                    Thread.sleep(retryWaitDuration); // waiting
+                } catch (InterruptedException e2) {
+                    throw new TechnicalException(e2);
+                }
+            }
         }
 
-        updateDocumentFromJson(index, type, id, json);
-    }
-
-    protected void updateDocumentFromJson(String index, String type, String id, String json) {
-        // Execute indexBlocksFromNode
-        client.prepareUpdate(index, type, id)
-                .setRefresh(true)
-                .setDoc(json)
-                .execute().actionGet();
+        throw new TechnicalException("Error while trying to execute a function with retry");
     }
 
     protected JsonNode readAndVerifyIssuerSignature(String recordJson) throws ElasticsearchException {
@@ -204,38 +143,6 @@ public abstract class AbstractService implements Bean {
         // TODO: check issuer is in the WOT ?
     }
 
-    protected void checkSameDocumentIssuer(String index, String type, String id, String expectedIssuer) throws ElasticsearchException {
-        checkSameDocumentField(index, type, id, Record.PROPERTY_ISSUER, expectedIssuer);
-    }
-
-    protected void checkSameDocumentField(String index, String type, String id, String fieldName, String expectedvalue) throws ElasticsearchException {
-
-        GetResponse response = client.prepareGet(index, type, id)
-                .setFields(fieldName)
-                .execute().actionGet();
-        boolean failed = !response.isExists();
-        if (failed) {
-            throw new NotFoundException(String.format("Document [%s/%s/%s] not exists.", index, type, id));
-        } else {
-            String docValue = (String)response.getFields().get(fieldName).getValue();
-            if (!Objects.equals(expectedvalue, docValue)) {
-                throw new AccessDeniedException(String.format("Could not delete this document: not same [%s].", fieldName));
-            }
-        }
-    }
-
-    protected boolean isDocumentExists(String index, String type, String id) throws ElasticsearchException {
-        GetResponse response = client.prepareGet(index, type, id)
-                .setFetchSource(false)
-                .execute().actionGet();
-        return response.isExists();
-    }
-
-    protected void checkDocumentExists(String index, String type, String id) throws ElasticsearchException {
-        if (!isDocumentExists(index, type, id)) {
-            throw new NotFoundException(String.format("Document [%s/%s/%s] not exists.", index, type, id));
-        }
-    }
 
     protected String getIssuer(JsonNode actualObj) {
         return  getMandatoryField(actualObj, Record.PROPERTY_ISSUER).asText();
@@ -249,360 +156,8 @@ public abstract class AbstractService implements Bean {
         return value;
     }
 
-    /**
-     * Retrieve some field from a document id, and check if all field not null
-     * @param docId
-     * @return
-     */
-    protected Map<String, Object> getMandatoryFieldsById(String index, String type, String docId, String... fieldNames) {
-        Map<String, Object> fields = getFieldsById(index, type, docId, fieldNames);
-        if (MapUtils.isEmpty(fields)) throw new NotFoundException(String.format("Document [%s/%s/%s] not exists.", index, type, docId));
-        Arrays.stream(fieldNames).forEach((fieldName) -> {
-            if (!fields.containsKey(fieldName)) throw new NotFoundException(String.format("Document [%s/%s/%s] should have the mandatory field [%s].", index, type, docId, fieldName));
-        });
-        return fields;
-    }
-
-    /**
-     * Retrieve some field from a document id
-     * @param docId
-     * @return
-     */
-    protected Map<String, Object> getFieldsById(String index, String type, String docId, String... fieldNames) {
-        // Prepare request
-        SearchRequestBuilder searchRequest = client
-                .prepareSearch(index)
-                .setTypes(type)
-                .setSearchType(SearchType.DFS_QUERY_THEN_FETCH);
-
-        searchRequest.setQuery(QueryBuilders.idsQuery().ids(docId));
-        searchRequest.addFields(fieldNames);
-
-        // Execute query
-        try {
-            SearchResponse response = searchRequest.execute().actionGet();
-
-            Map<String, Object> result = new HashMap<>();
-            // Read query result
-            SearchHit[] searchHits = response.getHits().getHits();
-            for (SearchHit searchHit : searchHits) {
-                Map<String, SearchHitField> hitFields = searchHit.getFields();
-                for(String fieldName: hitFields.keySet()) {
-                    result.put(fieldName, hitFields.get(fieldName).getValue());
-                }
-                break;
-            }
-            return result;
-        }
-        catch(SearchPhaseExecutionException e) {
-            // Failed or no item on index
-            throw new TechnicalException(String.format("[%s/%s] Unable to retrieve fields [%s] for id [%s]",
-                    index, type,
-                    Joiner.on(',').join(fieldNames).toString(),
-                    docId), e);
-        }
-    }
-
-    /**
-     * Retrieve some field from a document id
-     * @param index
-     * @param type
-     * @param ids
-     * @param fieldName
-     * @return
-     */
-    protected Map<String, Object> getFieldByIds(String index, String type, Set<String> ids, String fieldName) {
-        // Prepare request
-        SearchRequestBuilder searchRequest = client
-                .prepareSearch(index)
-                .setTypes(type)
-                .setSearchType(SearchType.DFS_QUERY_THEN_FETCH);
-
-        searchRequest.setQuery(QueryBuilders.idsQuery().ids(ids));
-        searchRequest.addFields(fieldName);
-
-        // Execute query
-        try {
-            SearchResponse response = searchRequest.execute().actionGet();
-
-            Map<String, Object> result = new HashMap<>();
-            // Read query result
-            SearchHit[] searchHits = response.getHits().getHits();
-            for (SearchHit searchHit : searchHits) {
-                Map<String, SearchHitField> hitFields = searchHit.getFields();
-                if (hitFields.get(fieldName) != null) {
-                    result.put(searchHit.getId(), hitFields.get(fieldName).getValue());
-                }
-            }
-            return result;
-        }
-        catch(SearchPhaseExecutionException e) {
-            // Failed or no item on index
-            throw new TechnicalException(String.format("[%s/%s] Unable to retrieve field [%s] for ids [%s]",
-                    index, type, fieldName,
-                    Joiner.on(',').join(ids).toString()), e);
-        }
-    }
-
-    /**
-     * Retrieve a field from a document id
-     * @param docId
-     * @return
-     */
-    protected Object getFieldById(String index, String type, String docId, String fieldName) {
-
-        Map<String, Object> result = getFieldsById(index, type, docId, fieldName);
-        if (MapUtils.isEmpty(result)) {
-            return null;
-        }
-        return result.get(fieldName);
-    }
-
-    protected <T> T getTypedFieldById(String index, String type, String docId, String fieldName) {
-        return (T)getFieldById(index, type, docId, fieldName);
-    }
-
-    /**
-     * Retrieve a document by id (safe mode)
-     * @param docId
-     * @return
-     */
-    public <T extends Object> T getSourceByIdOrNull(String index, String type, String docId, Class<T> classOfT, String... fieldNames) {
-        try {
-            return getSourceById(index, type, docId, classOfT, fieldNames);
-        }
-        catch(TechnicalException e) {
-            return null; // not found
-        }
-    }
-
-    /**
-     * Retrieve a document by id
-     * @param docId
-     * @return
-     */
-    public <T extends Object> T getSourceById(String index, String type, String docId, Class<T> classOfT, String... fieldNames) {
-
-        // Prepare request
-        SearchRequestBuilder searchRequest = client
-                .prepareSearch(index)
-                .setSearchType(SearchType.QUERY_AND_FETCH);
-
-        searchRequest.setQuery(QueryBuilders.idsQuery(type).ids(docId));
-        if (CollectionUtils.isNotEmpty(fieldNames)) {
-            searchRequest.setFetchSource(fieldNames, null);
-        }
-        else {
-            searchRequest.setFetchSource(true); // full source
-        }
-
-        // Execute query
-        try {
-            SearchResponse response = searchRequest.execute().actionGet();
-
-            if (response.getHits().getTotalHits() == 0) return null;
-
-            // Read query result
-            SearchHit[] searchHits = response.getHits().getHits();
-
-            for (SearchHit searchHit : searchHits) {
-                if (searchHit.source() != null) {
-                    return objectMapper.readValue(searchHit.source(), classOfT);
-                }
-                break;
-            }
-            return null;
-        }
-        catch(SearchPhaseExecutionException | IOException e) {
-            // Failed to get source
-            throw new TechnicalException(String.format("[%s/%s] Error while getting [%s]",
-                    index, type,
-                    docId), e);
-        }
-    }
-
-    protected void bulkFromClasspathFile(String classpathFile, String indexName, String indexType) {
-        bulkFromClasspathFile(classpathFile, indexName, indexType, null);
-    }
-
-    protected void bulkFromClasspathFile(String classpathFile, String indexName, String indexType, StringReaderHandler handler) {
-        InputStream is = null;
-        try {
-            is = getClass().getClassLoader().getResourceAsStream(classpathFile);
-            if (is == null) {
-                throw new TechnicalException(String.format("Could not retrieve data file [%s] need to fill index [%s]: ", classpathFile, indexName));
-            }
-
-            bulkFromStream(is, indexName, indexType, handler);
-        }
-        finally {
-            if (is != null) {
-                try  {
-                    is.close();
-                }
-                catch(IOException e) {
-                    // Silent is gold
-                }
-            }
-        }
-    }
-
-    protected void bulkFromFile(File file, String indexName, String indexType) {
-        bulkFromFile(file, indexName, indexType, null);
-    }
-
-    protected void bulkFromFile(File file, String indexName, String indexType, StringReaderHandler handler) {
-        Preconditions.checkNotNull(file);
-        Preconditions.checkArgument(file.exists());
-
-        InputStream is = null;
-        try {
-            is = new BufferedInputStream(new FileInputStream(file));
-            bulkFromStream(is, indexName, indexType, handler);
-        }
-        catch(FileNotFoundException e) {
-            throw new TechnicalException(String.format("[%s] Could not find file %s", indexName, file.getPath()), e);
-        }
-        finally {
-            if (is != null) {
-                try  {
-                    is.close();
-                }
-                catch(IOException e) {
-                    // Silent is gold
-                }
-            }
-        }
-    }
-
-    protected void bulkFromStream(InputStream is, String indexName, String indexType) {
-        bulkFromStream(is, indexName, indexType, null);
-    }
-
-    protected void bulkFromStream(InputStream is, String indexName, String indexType, StringReaderHandler handler) {
-        Preconditions.checkNotNull(is);
-        BulkRequest bulkRequest = Requests.bulkRequest();
-
-        BufferedReader br = null;
-
-        try {
-            br = new BufferedReader(new InputStreamReader(is));
-
-            String line = br.readLine();
-            StringBuilder builder = new StringBuilder();
-            while(line != null) {
-                line = line.trim();
-                if (StringUtils.isNotBlank(line)) {
-                    if (logger.isTraceEnabled()) {
-                        logger.trace(String.format("[%s] Add to bulk: %s", indexName, line));
-                    }
-                    if (handler != null) {
-                        line = handler.onReadLine(line.trim());
-                    }
-                    builder.append(line).append('\n');
-                }
-                line = br.readLine();
-            }
-
-            byte[] data = builder.toString().getBytes();
-            bulkRequest.add(new BytesArray(data), indexName, indexType, false);
-
-        } catch(Exception e) {
-            throw new TechnicalException(String.format("[%s] Error while inserting rows into %s", indexName, indexType), e);
-        }
-        finally {
-            if (br != null) {
-                try  {
-                    br.close();
-                }
-                catch(IOException e) {
-                    // Silent is gold
-                }
-            }
-        }
-
-        try {
-            client.bulk(bulkRequest).actionGet();
-        } catch(Exception e) {
-            throw new TechnicalException(String.format("[%s] Error while inserting rows into %s", indexName, indexType), e);
-        }
-    }
-
-    protected void flushDeleteBulk(final String index, final String type, BulkRequestBuilder bulkRequest) {
-        if (bulkRequest.numberOfActions() > 0) {
-
-            BulkResponse bulkResponse = bulkRequest.execute().actionGet();
-            // If failures, continue but save missing blocks
-            if (bulkResponse.hasFailures()) {
-                // process failures by iterating through each bulk response item
-                for (BulkItemResponse itemResponse : bulkResponse) {
-                    boolean skip = !itemResponse.isFailed();
-                    if (!skip) {
-                        logger.debug(String.format("[%s/%s] Error while deleting doc [%s]: %s. Skipping this deletion.", index, type, itemResponse.getId(), itemResponse.getFailureMessage()));
-                    }
-                }
-            }
-        }
-    }
-
-    public interface StringReaderHandler {
-
-        String onReadLine(String line);
-    }
-
-    public class AddSequenceAttributeHandler implements StringReaderHandler {
-        private int order;
-        private final String attributeName;
-        private final Pattern filterPattern;
-        public AddSequenceAttributeHandler(String attributeName, String filterRegex, int startValue) {
-            this.order = startValue;
-            this.attributeName = attributeName;
-            this.filterPattern = Pattern.compile(filterRegex);
-        }
-
-        @Override
-        public String onReadLine(String line) {
-            // add 'order' field into
-            if (filterPattern.matcher(line).matches()) {
-                return String.format("%s, \"%s\": %d}",
-                        line.substring(0, line.length()-1),
-                        attributeName,
-                        order++);
-            }
-            return line;
-        }
-    }
-
-    protected <T> T executeWithRetry(RetryFunction<T> retryFunction) throws TechnicalException{
-        int retry = 0;
-        while (retry < retryCount) {
-            try {
-                return retryFunction.execute();
-            } catch (TechnicalException e) {
-                retry++;
-
-                if (retry == retryCount) {
-                    throw e;
-                }
-
-                if (logger.isDebugEnabled()) {
-                    logger.debug(I18n.t("duniter4j.removeServiceUtils.waitThenRetry", e.getMessage(), retry, retryCount));
-                }
-
-                try {
-                    Thread.sleep(retryWaitDuration); // waiting
-                } catch (InterruptedException e2) {
-                    throw new TechnicalException(e2);
-                }
-            }
-        }
-
-        throw new TechnicalException("Error while trying to execute a function with retry");
-    }
-
     public interface RetryFunction<T> {
 
         T execute() throws TechnicalException;
     }
-
 }
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/AbstractSynchroService.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/AbstractSynchroService.java
index 1ef7130bf95d048234d75e52c6dabf7f2909ee56..906872b71d511febf4b82d2836ead95cf3a80b86 100644
--- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/AbstractSynchroService.java
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/AbstractSynchroService.java
@@ -36,6 +36,7 @@ import org.duniter.core.exception.TechnicalException;
 import org.duniter.core.service.CryptoService;
 import org.duniter.core.util.StringUtils;
 import org.duniter.elasticsearch.PluginSettings;
+import org.duniter.elasticsearch.client.Duniter4jClient;
 import org.duniter.elasticsearch.exception.InvalidFormatException;
 import org.duniter.elasticsearch.model.SynchroResult;
 import org.duniter.elasticsearch.service.AbstractService;
@@ -66,7 +67,9 @@ public abstract class AbstractSynchroService extends AbstractService {
     protected HttpService httpService;
 
     @Inject
-    public AbstractSynchroService(Client client, PluginSettings settings, CryptoService cryptoService,
+    public AbstractSynchroService(Duniter4jClient client,
+                                  PluginSettings settings,
+                                  CryptoService cryptoService,
                                   ThreadPool threadPool, final ServiceLocator serviceLocator) {
         super("duniter.network.p2p", client, settings,cryptoService);
         threadPool.scheduleOnStarted(() -> {
@@ -78,7 +81,7 @@ public abstract class AbstractSynchroService extends AbstractService {
 
     protected Peer getPeerFromAPI(String filterApiName) {
         // TODO : get peers from currency - use peering BMA API, and select peers with ESA (ES API)
-        Peer peer = new Peer(pluginSettings.getDataSyncHost(), pluginSettings.getDataSyncPort());
+        Peer peer = Peer.newBuilder().setHost(pluginSettings.getDataSyncHost()).setPort(pluginSettings.getDataSyncPort()).build();
         return peer;
     }
 
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/BlockchainService.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/BlockchainService.java
index b4b67c3cec7e95bdaca7de9db55f429cf114d4bf..b1934f161ef9523a2542c848d7a0374e1ae41aed 100644
--- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/BlockchainService.java
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/BlockchainService.java
@@ -23,7 +23,6 @@ package org.duniter.elasticsearch.service;
  */
 
 
-import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.base.Objects;
 import com.google.common.collect.ImmutableSet;
@@ -32,12 +31,12 @@ import org.duniter.core.client.model.bma.BlockchainBlock;
 import org.duniter.core.client.model.bma.BlockchainParameters;
 import org.duniter.core.client.model.bma.EndpointApi;
 import org.duniter.core.client.model.local.Peer;
+import org.duniter.core.util.ObjectUtils;
 import org.duniter.core.util.json.JsonAttributeParser;
 import org.duniter.core.client.model.bma.jackson.JacksonUtils;
 import org.duniter.core.client.service.bma.BlockchainRemoteService;
 import org.duniter.core.client.service.bma.NetworkRemoteService;
 import org.duniter.core.client.service.exception.BlockNotFoundException;
-import org.duniter.core.util.json.JsonSyntaxException;
 import org.duniter.core.exception.TechnicalException;
 import org.duniter.core.model.NullProgressionModel;
 import org.duniter.core.model.ProgressionModel;
@@ -47,30 +46,16 @@ import org.duniter.core.util.Preconditions;
 import org.duniter.core.util.StringUtils;
 import org.duniter.core.util.websocket.WebsocketClientEndpoint;
 import org.duniter.elasticsearch.PluginSettings;
+import org.duniter.elasticsearch.client.Duniter4jClient;
+import org.duniter.elasticsearch.dao.BlockDao;
+import org.duniter.elasticsearch.dao.impl.BlockDaoImpl;
 import org.duniter.elasticsearch.exception.DuplicateIndexIdException;
 import org.duniter.elasticsearch.threadpool.ThreadPool;
-import org.elasticsearch.action.ActionFuture;
-import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
 import org.elasticsearch.action.bulk.BulkItemResponse;
 import org.elasticsearch.action.bulk.BulkRequestBuilder;
 import org.elasticsearch.action.bulk.BulkResponse;
-import org.elasticsearch.action.index.IndexRequestBuilder;
-import org.elasticsearch.action.index.IndexResponse;
-import org.elasticsearch.action.search.SearchRequestBuilder;
-import org.elasticsearch.action.search.SearchResponse;
-import org.elasticsearch.action.search.SearchType;
 import org.elasticsearch.client.Client;
-import org.elasticsearch.cluster.health.ClusterHealthStatus;
 import org.elasticsearch.common.inject.Inject;
-import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException;
-import org.elasticsearch.common.xcontent.XContentBuilder;
-import org.elasticsearch.common.xcontent.XContentFactory;
-import org.elasticsearch.index.query.QueryBuilders;
-import org.elasticsearch.search.SearchHitField;
-import org.elasticsearch.search.aggregations.AggregationBuilders;
-import org.elasticsearch.search.aggregations.metrics.max.Max;
-import org.elasticsearch.search.highlight.HighlightField;
-import org.elasticsearch.search.sort.SortOrder;
 import org.nuiton.i18n.I18n;
 
 import java.io.IOException;
@@ -100,14 +85,19 @@ public class BlockchainService extends AbstractService {
     private final JsonAttributeParser blockHashParser = new JsonAttributeParser("hash");
     private final JsonAttributeParser blockPreviousHashParser = new JsonAttributeParser("previousHash");
 
-    private ObjectMapper objectMapper;
+    private Client client;
+    private BlockDao blockDao;
 
     @Inject
-    public BlockchainService(Client client, PluginSettings settings, ThreadPool threadPool,
+    public BlockchainService(Duniter4jClient client,
+                             PluginSettings settings,
+                             ThreadPool threadPool,
+                             BlockDao blockDao,
                              final ServiceLocator serviceLocator){
         super("duniter.blockchain", client, settings);
-        this.objectMapper = JacksonUtils.newObjectMapper();
         this.threadPool = threadPool;
+        this.client = client;
+        this.blockDao = blockDao;
         threadPool.scheduleOnStarted(() -> {
             blockchainRemoteService = serviceLocator.getBlockchainRemoteService();
         });
@@ -140,7 +130,7 @@ public class BlockchainService extends AbstractService {
     }
 
     public BlockchainService listenAndIndexNewBlock(final Peer peer){
-        WebsocketClientEndpoint wsEndPoint = blockchainRemoteService.addBlockListener(peer, message -> indexLastBlockFromJson(peer, message));
+        WebsocketClientEndpoint wsEndPoint = blockchainRemoteService.addBlockListener(peer, message -> indexLastBlockFromJson(peer, message), true /*autoreconnect*/);
         wsEndPoint.registerListener(dispatchConnectionListener);
         return this;
     }
@@ -170,14 +160,6 @@ public class BlockchainService extends AbstractService {
             progressionModel.setTask(I18n.t("duniter4j.blockIndexerService.indexLastBlocks.task", currencyName, peer));
             logger.info(I18n.t("duniter4j.blockIndexerService.indexLastBlocks.task", currencyName, peer));
 
-            // Create index blockchain if need
-            if (!currencyService.isCurrencyExists(currencyName)) {
-                currencyService.indexCurrencyFromPeer(peer);
-            }
-
-            // Check if index exists
-            createIndexIfNotExists(currencyName, true/*wait cluster health*/);
-
             // Then index allOfToList blocks
             BlockchainBlock peerCurrentBlock = blockchainRemoteService.getCurrentBlock(peer);
 
@@ -205,7 +187,7 @@ public class BlockchainService extends AbstractService {
                 // When current block not found,
                 // try to use the max(number), because block with _id='current' may not has been indexed
                 if (startNumber <= 1 ){
-                    startNumber = getMaxBlockNumber(currencyName) + 1;
+                    startNumber = blockDao.getMaxBlockNumber(currencyName) + 1;
                 }
 
                 // If some block has been already indexed: detect and resolve fork
@@ -262,42 +244,6 @@ public class BlockchainService extends AbstractService {
         return this;
     }
 
-    public BlockchainService deleteIndex(String currencyName) {
-        deleteIndexIfExists(currencyName);
-        return this;
-    }
-
-    public boolean existsIndex(String currencyName) {
-        return super.existsIndex(currencyName);
-    }
-
-    public BlockchainService createIndexIfNotExists(String currencyName, boolean waitClusterHealth) {
-        if (!existsIndex(currencyName)) {
-            createIndex(currencyName);
-
-            // when wait cluster state
-            if (waitClusterHealth) {
-                threadPool.waitClusterHealthStatus(ClusterHealthStatus.GREEN, ClusterHealthStatus.YELLOW);
-            }
-        }
-        return this;
-    }
-
-    public void createIndex(String currencyName) {
-        logger.info(String.format("Creating index [%s]", currencyName));
-
-        CreateIndexRequestBuilder createIndexRequestBuilder = client.admin().indices().prepareCreate(currencyName);
-        org.elasticsearch.common.settings.Settings indexSettings = org.elasticsearch.common.settings.Settings.settingsBuilder()
-                .put("number_of_shards", 1)
-                .put("number_of_replicas", 1)
-                //.put("analyzer", createDefaultAnalyzer())
-                .build();
-        createIndexRequestBuilder.setSettings(indexSettings);
-        createIndexRequestBuilder.addMapping(BLOCK_TYPE, createBlockTypeMapping());
-        createIndexRequestBuilder.addMapping(PEER_TYPE, EndpointService.createEndpointTypeMapping());
-        createIndexRequestBuilder.execute().actionGet();
-    }
-
     /**
      * Create or update a block, depending on its existence and hash
      * @param block
@@ -311,7 +257,7 @@ public class BlockchainService extends AbstractService {
         Preconditions.checkNotNull(block.getNumber(), "block attribute 'number' could not be null");
         Preconditions.checkNotNull(block.getHash(), "block attribute 'hash' could not be null");
 
-        BlockchainBlock existingBlock = getBlockById(block.getCurrency(), block.getNumber());
+        BlockchainBlock existingBlock = blockDao.getBlockById(block.getCurrency(), getBlockId(block.getNumber()));
 
         // Currency not exists, or has changed, so create it
         if (existingBlock == null) {
@@ -320,12 +266,12 @@ public class BlockchainService extends AbstractService {
             }
 
             // Create new block
-            indexBlock(block, wait);
+            blockDao.create(block, wait);
         }
 
         // Exists, so check the owner signature
         else {
-            boolean doUpdate = false;
+            boolean doUpdate;
             if (updateWhenSameHash) {
                 doUpdate = true;
                 if (logger.isTraceEnabled() && doUpdate) {
@@ -346,68 +292,9 @@ public class BlockchainService extends AbstractService {
 
             // Update existing block
             if (doUpdate) {
-                indexBlock(block, wait);
-            }
-        }
-    }
-
-    public void indexBlock(BlockchainBlock block, boolean wait) {
-        Preconditions.checkNotNull(block);
-        Preconditions.checkArgument(StringUtils.isNotBlank(block.getCurrency()));
-        Preconditions.checkNotNull(block.getHash());
-        Preconditions.checkNotNull(block.getNumber());
-
-        // Serialize into JSON
-        // WARN: must use GSON, to have same JSON result (e.g identities and joiners field must be converted into String)
-        try {
-            String json = objectMapper.writeValueAsString(block);
-
-            // Preparing indexBlocksFromNode
-            IndexRequestBuilder indexRequest = client.prepareIndex(block.getCurrency(), BLOCK_TYPE)
-                    .setId(block.getNumber().toString())
-                    .setSource(json);
-
-            // Execute indexBlocksFromNode
-            ActionFuture<IndexResponse> futureResponse = indexRequest
-                    .setRefresh(true)
-                    .execute();
-
-            if (wait) {
-                futureResponse.actionGet();
+                blockDao.update(block, wait);
             }
         }
-        catch(JsonProcessingException e) {
-            throw new TechnicalException(e);
-        }
-
-
-    }
-
-    /**
-     *
-     * @param currencyName
-     * @param number the block number
-     * @param json block as JSON
-     */
-    public BlockchainService indexBlockFromJson(String currencyName, int number, byte[] json, boolean refresh, boolean wait) {
-        Preconditions.checkNotNull(json);
-        Preconditions.checkArgument(json.length > 0);
-
-        // Preparing indexBlocksFromNode
-        IndexRequestBuilder indexRequest = client.prepareIndex(currencyName, BLOCK_TYPE)
-                .setId(String.valueOf(number))
-                .setRefresh(refresh)
-                .setSource(json);
-
-        // Execute indexBlocksFromNode
-        if (!wait) {
-            indexRequest.execute();
-        }
-        else {
-            indexRequest.execute().actionGet();
-        }
-
-        return this;
     }
 
     /**
@@ -419,7 +306,7 @@ public class BlockchainService extends AbstractService {
         Preconditions.checkNotNull(json);
         Preconditions.checkArgument(json.length() > 0);
 
-        indexBlockFromJson(peer, json, true /*refresh*/, true /*is current*/, true/*check fork*/, true/*wait*/);
+        indexBlockFromJson(peer, json, true /*is current*/, true/*check fork*/, true/*wait*/);
 
         return this;
     }
@@ -427,10 +314,9 @@ public class BlockchainService extends AbstractService {
     /**
      *
      * @param json block as json
-     * @param refresh Could be an existing block ?
      * @param wait need to wait until processed ?
      */
-    public BlockchainService indexBlockFromJson(Peer peer, String json, boolean refresh, boolean isCurrent, boolean detectFork, boolean wait) {
+    public BlockchainService indexBlockFromJson(Peer peer, String json, boolean isCurrent, boolean detectFork, boolean wait) {
         Preconditions.checkNotNull(json);
         Preconditions.checkArgument(json.length() > 0);
 
@@ -455,18 +341,7 @@ public class BlockchainService extends AbstractService {
         }
 
         // Preparing indexBlocksFromNode
-        IndexRequestBuilder indexRequest = client.prepareIndex(currencyName, BLOCK_TYPE)
-                .setId(String.valueOf(number))
-                .setRefresh(refresh)
-                .setSource(json);
-
-        // Execute indexBlocksFromNode
-        if (!wait) {
-            indexRequest.execute();
-        }
-        else {
-            indexRequest.execute().actionGet();
-        }
+        blockDao.create(currencyName, getBlockId(number), json.getBytes(), wait);
 
         // Update current
         if (isCurrent) {
@@ -510,239 +385,25 @@ public class BlockchainService extends AbstractService {
         Preconditions.checkArgument(StringUtils.isNotBlank(currencyName));
 
         // Preparing indexBlocksFromNode
-        IndexRequestBuilder indexRequest = client.prepareIndex(currencyName, BLOCK_TYPE)
-                .setId(CURRENT_BLOCK_ID)
-                .setRefresh(true)
-                .setSource(json);
-
-        // Execute in a pool
-        if (!wait) {
-            boolean acceptedInPool = false;
-            while(!acceptedInPool)
-                try {
-                    indexRequest.execute();
-                    acceptedInPool = true;
-                }
-                catch(EsRejectedExecutionException e) {
-                    // not accepted, so wait
-                    try {
-                        Thread.sleep(1000); // 1s
-                    }
-                    catch(InterruptedException e2) {
-                        // silent
-                    }
-                }
-
-        } else {
-            indexRequest.execute().actionGet();
+        if (blockDao.isExists(currencyName, CURRENT_BLOCK_ID)) {
+            blockDao.update(currencyName, CURRENT_BLOCK_ID, json.getBytes(), wait);
         }
-    }
-
-    public List<BlockchainBlock> findBlocksByHash(String currencyName, String query) {
-        String[] queryParts = query.split("[\\t ]+");
-
-        // Prepare request
-        SearchRequestBuilder searchRequest = client
-                .prepareSearch(currencyName)
-                .setTypes(BLOCK_TYPE)
-                .setSearchType(SearchType.DFS_QUERY_THEN_FETCH);
-
-        // If only one term, search as prefix
-        if (queryParts.length == 1) {
-            searchRequest.setQuery(QueryBuilders.prefixQuery("hash", query));
-        }
-
-        // If more than a word, search on terms match
         else {
-            searchRequest.setQuery(QueryBuilders.matchQuery("hash", query));
-        }
-
-        // Sort as score/memberCount
-        searchRequest.addSort("_score", SortOrder.DESC)
-                .addSort("number", SortOrder.DESC);
-
-        // Highlight matched words
-        searchRequest.setHighlighterTagsSchema("styled")
-                .addHighlightedField("hash")
-                .addFields("hash")
-                .addFields("*", "_source");
-
-        // Execute query
-        SearchResponse searchResponse = searchRequest.execute().actionGet();
-
-        // Read query result
-        return toBlocks(searchResponse, true);
-    }
-
-    public int getMaxBlockNumber(String currencyName) {
-        // Prepare request
-        SearchRequestBuilder searchRequest = client
-                .prepareSearch(currencyName)
-                .setTypes(BLOCK_TYPE)
-                .setSearchType(SearchType.DFS_QUERY_THEN_FETCH);
-
-        // Get max(number)
-        searchRequest.addAggregation(AggregationBuilders.max("max_number").field("number"));
-
-        // Execute query
-        SearchResponse searchResponse = searchRequest.execute().actionGet();
-
-        // Read query result
-        Max result = searchResponse.getAggregations().get("max_number");
-        if (result == null) {
-            return -1;
+            blockDao.create(currencyName, CURRENT_BLOCK_ID, json.getBytes(), wait);
         }
-
-        return (result.getValue() == Double.NEGATIVE_INFINITY)
-                ? -1
-                : (int)result.getValue();
     }
 
     public BlockchainBlock getBlockById(String currencyName, int number) {
-        return getBlockByIdStr(currencyName, String.valueOf(number));
+        return blockDao.getBlockById(currencyName, String.valueOf(number));
     }
 
     public BlockchainBlock getCurrentBlock(String currencyName) {
-        return getBlockByIdStr(currencyName, CURRENT_BLOCK_ID);
+        return blockDao.getBlockById(currencyName, CURRENT_BLOCK_ID);
     }
 
     /* -- Internal methods -- */
 
-
-    public XContentBuilder createBlockTypeMapping() {
-        try {
-            XContentBuilder mapping = XContentFactory.jsonBuilder()
-                    .startObject()
-                    .startObject(BLOCK_TYPE)
-                    .startObject("properties")
-
-                    // block number
-                    .startObject("number")
-                    .field("type", "integer")
-                    .endObject()
-
-                    // hash
-                    .startObject("hash")
-                    .field("type", "string")
-                    .endObject()
-
-                    // issuer
-                    .startObject("issuer")
-                    .field("type", "string")
-                    .field("index", "not_analyzed")
-                    .endObject()
-
-                    // previous hash
-                    .startObject("previousHash")
-                    .field("type", "string")
-                    .endObject()
-
-                    // membercount
-                    .startObject("memberCount")
-                    .field("type", "integer")
-                    .endObject()
-
-                    // membersChanges
-                    .startObject("membersChanges")
-                    .field("type", "string")
-                    .endObject()
-
-                    // unitbase
-                    .startObject("unitbase")
-                    .field("type", "integer")
-                    .endObject()
-
-                    // membersChanges
-                    .startObject("monetaryMass")
-                    .field("type", "long")
-                    .endObject()
-
-                    // dividend
-                    .startObject("dividend")
-                    .field("type", "integer")
-                    .endObject()
-
-                    // identities:
-                    //.startObject("identities")
-                    //.endObject()
-
-                    .endObject()
-                    .endObject().endObject();
-
-            return mapping;
-        }
-        catch(IOException ioe) {
-            throw new TechnicalException("Error while getting mapping for block index: " + ioe.getMessage(), ioe);
-        }
-    }
-
-
-    public BlockchainBlock getBlockByIdStr(String currencyName, String blockId) {
-
-        // Prepare request
-        SearchRequestBuilder searchRequest = client
-                .prepareSearch(currencyName)
-                .setTypes(BLOCK_TYPE)
-                .setSearchType(SearchType.DFS_QUERY_THEN_FETCH);
-
-        // If more than a word, search on terms match
-        searchRequest.setQuery(QueryBuilders.matchQuery("_id", blockId));
-
-        // Execute query
-        try {
-            SearchResponse searchResponse = searchRequest.execute().actionGet();
-            List<BlockchainBlock> blocks = toBlocks(searchResponse, false);
-            if (CollectionUtils.isEmpty(blocks)) {
-                return null;
-            }
-
-            // Return the unique result
-            return CollectionUtils.extractSingleton(blocks);
-        }
-        catch(JsonSyntaxException e) {
-            throw new TechnicalException(String.format("Error while getting indexed block #%s for blockchain [%s]", blockId, currencyName), e);
-        }
-
-    }
-
-    protected List<BlockchainBlock> toBlocks(SearchResponse response, boolean withHighlight) {
-        // Read query result
-        List<BlockchainBlock> result = Lists.newArrayList();
-        response.getHits().forEach(searchHit -> {
-            BlockchainBlock block;
-            if (searchHit.source() != null) {
-                String jsonString = new String(searchHit.source());
-                try {
-                    block = objectMapper.readValue(jsonString, BlockchainBlock.class);
-                } catch(Exception e) {
-                    if (logger.isDebugEnabled()) {
-                        logger.debug("Error while parsing block from JSON:\n" + jsonString);
-                    }
-                    throw new JsonSyntaxException("Error while read block from JSON: " + e.getMessage(), e);
-                }
-            }
-            else {
-                block = new BlockchainBlock();
-                SearchHitField field = searchHit.getFields().get("hash");
-                block.setHash(field.getValue());
-            }
-            result.add(block);
-
-            // If possible, use highlights
-            if (withHighlight) {
-                Map<String, HighlightField> fields = searchHit.getHighlightFields();
-                for (HighlightField field : fields.values()) {
-                    String blockNameHighLight = field.getFragments()[0].string();
-                    block.setHash(blockNameHighLight);
-                }
-            }
-        });
-
-        return result;
-    }
-
-
-    public Collection<String> indexBlocksNoBulk(Peer peer, String currencyName, int firstNumber, int lastNumber, ProgressionModel progressionModel) {
+    protected Collection<String> indexBlocksNoBulk(Peer peer, String currencyName, int firstNumber, int lastNumber, ProgressionModel progressionModel) {
         Set<String> missingBlockNumbers = new LinkedHashSet<>();
 
         for (int curNumber = firstNumber; curNumber <= lastNumber; curNumber++) {
@@ -763,7 +424,7 @@ public class BlockchainService extends AbstractService {
 
             try {
                 String blockAsJson = blockchainRemoteService.getBlockAsJson(peer, curNumber);
-                indexBlockFromJson(currencyName, curNumber, blockAsJson.getBytes(), false, true /*wait*/);
+                blockDao.create(currencyName, getBlockId(curNumber), blockAsJson.getBytes(), true /*wait*/);
 
                 // If last block
                 if (curNumber == lastNumber - 1) {
@@ -780,7 +441,7 @@ public class BlockchainService extends AbstractService {
         return missingBlockNumbers;
     }
 
-    public Collection<String> indexBlocksUsingBulk(Peer peer, String currencyName, int firstNumber, int lastNumber, ProgressionModel progressionModel) {
+    protected Collection<String> indexBlocksUsingBulk(Peer peer, String currencyName, int firstNumber, int lastNumber, ProgressionModel progressionModel) {
         Set<String> missingBlockNumbers = new LinkedHashSet<>();
 
         boolean debug = logger.isDebugEnabled();
@@ -954,7 +615,7 @@ public class BlockchainService extends AbstractService {
                             }
 
                             // Index the missing block
-                            indexBlockFromJson(currencyName, blockNumber, blockAsJson.getBytes(), false/*refresh*/, true/*wait*/);
+                            blockDao.create(currencyName, getBlockId(blockNumber), blockAsJson.getBytes(), true/*wait*/);
 
                             // Remove this block number from the final missing list
                             newMissingBlocks.remove(blockNumber);
@@ -1018,13 +679,15 @@ public class BlockchainService extends AbstractService {
     }
 
     protected boolean isBlockIndexed(String currencyName, int number, String hash) {
+        Preconditions.checkNotNull(currencyName);
+        Preconditions.checkNotNull(hash);
         // Check if previous block exists
-        BlockchainBlock block = getBlockByIdStr(currencyName, String.valueOf(number));
+        BlockchainBlock block = getBlockById(currencyName, number);
         boolean blockExists = block != null;
         if (!blockExists) {
             return blockExists;
         }
-        return block.getHash() != null && block.getHash().equals(hash);
+        return ObjectUtils.equals(block.getHash(), hash);
     }
 
     protected boolean detectAndResolveFork(Peer peer, final String currencyName, final String hash, final int number){
@@ -1064,7 +727,7 @@ public class BlockchainService extends AbstractService {
         if (forkOriginNumber < number) {
             logger.info(I18n.t("duniter4j.blockIndexerService.detectFork.resync", currencyName, peer, forkOriginNumber));
             // Remove some previous block
-            deleteBlocksFromNumber(currencyName, forkOriginNumber/*from*/, number+forkResyncWindow/*to*/);
+            blockDao.deleteRange(currencyName, forkOriginNumber/*from*/, number+forkResyncWindow/*to*/);
 
             // Re-indexing blocks
             indexBlocksUsingBulk(peer, currencyName, forkOriginNumber/*from*/, number, nullProgressionModel);
@@ -1073,32 +736,8 @@ public class BlockchainService extends AbstractService {
         return true; // sync OK
     }
 
-    /**
-     * Delete blocks from a start number (using bulk)
-     * @param currencyName
-     * @param fromNumber
-     */
-    protected void deleteBlocksFromNumber(final String currencyName, final int fromNumber, final int toNumber) {
-
-        int bulkSize = pluginSettings.getIndexBulkSize();
-
-        BulkRequestBuilder bulkRequest = client.prepareBulk();
-        for (int number=fromNumber; number<=toNumber; number++) {
 
-            bulkRequest.add(
-                client.prepareDelete(currencyName, BLOCK_TYPE, String.valueOf(number))
-            );
-
-            // Flush the bulk if not empty
-            if ((fromNumber - number % bulkSize) == 0) {
-                flushDeleteBulk(currencyName, BLOCK_TYPE, bulkRequest);
-                bulkRequest = client.prepareBulk();
-            }
-        }
-
-        // last flush
-        flushDeleteBulk(currencyName, BLOCK_TYPE, bulkRequest);
+    protected String getBlockId(int number) {
+        return number == -1 ? CURRENT_BLOCK_ID : String.valueOf(number);
     }
-
-
 }
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/CurrencyService.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/CurrencyService.java
index f1d2d180139ca509a36c34f1a2fa93cbeba3dd06..f0cdcd627c60230f010300fb423b38192608a522 100644
--- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/CurrencyService.java
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/CurrencyService.java
@@ -24,9 +24,8 @@ package org.duniter.elasticsearch.service;
 
 
 import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.common.collect.Lists;
-import org.apache.commons.lang3.ArrayUtils;
+import org.duniter.core.client.dao.CurrencyDao;
+import org.duniter.core.client.dao.PeerDao;
 import org.duniter.core.client.model.bma.BlockchainBlock;
 import org.duniter.core.client.model.bma.BlockchainParameters;
 import org.duniter.core.client.model.bma.jackson.JacksonUtils;
@@ -37,27 +36,27 @@ import org.duniter.core.client.service.exception.HttpConnectException;
 import org.duniter.core.exception.TechnicalException;
 import org.duniter.core.service.CryptoService;
 import org.duniter.core.util.Preconditions;
-import org.duniter.core.util.StringUtils;
 import org.duniter.elasticsearch.PluginSettings;
+import org.duniter.elasticsearch.client.Duniter4jClient;
+import org.duniter.elasticsearch.dao.*;
 import org.duniter.elasticsearch.exception.AccessDeniedException;
 import org.duniter.elasticsearch.exception.DuplicateIndexIdException;
 import org.duniter.elasticsearch.exception.InvalidSignatureException;
 import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
-import org.elasticsearch.action.index.IndexRequestBuilder;
 import org.elasticsearch.action.search.SearchPhaseExecutionException;
 import org.elasticsearch.action.search.SearchRequestBuilder;
 import org.elasticsearch.action.search.SearchResponse;
 import org.elasticsearch.action.search.SearchType;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.common.inject.Inject;
-import org.elasticsearch.common.xcontent.XContentBuilder;
-import org.elasticsearch.common.xcontent.XContentFactory;
+import org.elasticsearch.common.inject.Injector;
 import org.elasticsearch.index.query.IdsQueryBuilder;
 import org.elasticsearch.search.SearchHit;
 import org.elasticsearch.search.SearchHitField;
 
 import java.io.IOException;
-import java.util.List;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Objects;
 
 /**
@@ -65,85 +64,39 @@ import java.util.Objects;
  */
 public class CurrencyService extends AbstractService {
 
-    protected static final String REGEX_WORD_SEPARATOR = "[-\\t@# _]+";
-
     public static final String INDEX = "currency";
     public static final String RECORD_TYPE = "record";
-    public static final String PEER_TYPE = "peer";
 
-    private final ObjectMapper objectMapper;
     private BlockchainRemoteService blockchainRemoteService;
+    private CurrencyExtendDao currencyDao;
+    private Map<String, IndexDao<?>> currencyDataDaos = new HashMap<>();
+    private Injector injector;
 
     @Inject
-    public CurrencyService(Client client,
+    public CurrencyService(Duniter4jClient client,
                            PluginSettings settings,
                            CryptoService cryptoService,
-                           BlockchainRemoteService blockchainRemoteService) {
+                           CurrencyDao currencyDao,
+                           BlockchainRemoteService blockchainRemoteService,
+                           Injector injector) {
         super("duniter." + INDEX, client, settings, cryptoService);
-        this.objectMapper = JacksonUtils.newObjectMapper();
         this.blockchainRemoteService = blockchainRemoteService;
+        this.currencyDao = (CurrencyExtendDao)currencyDao;
+        this.injector = injector;
     }
 
-    /**
-     * Create index need for blockchain registry, if need
-     */
     public CurrencyService createIndexIfNotExists() {
-        try {
-            if (!existsIndex(INDEX)) {
-                createIndex();
-            }
-        }
-        catch(JsonProcessingException e) {
-            throw new TechnicalException(String.format("Error while creating index [%s]", INDEX));
-        }
-        return this;
-    }
-
-    /**
-     * Create index for registry
-     * @throws JsonProcessingException
-     */
-    public CurrencyService createIndex() throws JsonProcessingException {
-        logger.info(String.format("Creating index [%s]", INDEX));
-
-        CreateIndexRequestBuilder createIndexRequestBuilder = client.admin().indices().prepareCreate(INDEX);
-        org.elasticsearch.common.settings.Settings indexSettings = org.elasticsearch.common.settings.Settings.settingsBuilder()
-                .put("number_of_shards", 3)
-                .put("number_of_replicas", 1)
-                //.put("analyzer", createDefaultAnalyzer())
-                .build();
-        createIndexRequestBuilder.setSettings(indexSettings);
-        createIndexRequestBuilder.addMapping(RECORD_TYPE, createRecordTypeMapping());
-        createIndexRequestBuilder.execute().actionGet();
-
+        currencyDao.createIndexIfNotExists();
         return this;
     }
 
     public CurrencyService deleteIndex() {
-        deleteIndexIfExists(INDEX);
+        currencyDao.deleteIndex();
         return this;
     }
 
-    public boolean existsIndex() {
-        return super.existsIndex(INDEX);
-    }
-
     public boolean isCurrencyExists(String currencyName) {
-        String pubkey = getSenderPubkeyByCurrencyId(currencyName);
-        return !StringUtils.isEmpty(pubkey);
-    }
-
-
-    /**
-     * Add a new currency
-     * TODO :
-     *  - add security, to allow only request from admin (check signature against settings keyring)
-     *
-     * @param json
-     * @return
-     */
-    public String indexCurrencyFromJson(String json) {
-        throw new TechnicalException("Not implemented yet. Received JSON: " + json);
+        return currencyDao.isExists(currencyName);
     }
 
     /**
@@ -186,104 +139,18 @@ public class CurrencyService extends AbstractService {
 
 
         Currency result = new Currency();
-        result.setCurrency(parameters.getCurrency());
+        result.setCurrencyName(parameters.getCurrency());
         result.setFirstBlockSignature(firstBlock.getSignature());
         result.setMembersCount(currentBlock.getMembersCount());
         result.setLastUD(lastUD);
         result.setParameters(parameters);
 
-        indexCurrency(result);
-
-        indexPeer(parameters.getCurrency(), peer);
+        // Save it
+        saveCurrency(result, pluginSettings.getKeyringPublicKey());
 
         return result;
     }
 
-    /**
-     * Index a blockchain
-     * @param currency
-     */
-    public void indexCurrency(Currency currency) {
-        try {
-            Preconditions.checkNotNull(currency.getCurrency());
-
-            // Fill tags
-            if (ArrayUtils.isEmpty(currency.getTags())) {
-                String currencyName = currency.getCurrency();
-                String[] tags = currencyName.split(REGEX_WORD_SEPARATOR);
-                List<String> tagsList = Lists.newArrayList(tags);
-
-                // Convert as a sentence (replace seprator with a space)
-                String sentence = currencyName.replaceAll(REGEX_WORD_SEPARATOR, " ");
-                if (!tagsList.contains(sentence)) {
-                    tagsList.add(sentence);
-                }
-
-                currency.setTags(tagsList.toArray(new String[tagsList.size()]));
-            }
-
-            // Serialize into JSON
-            byte[] json = objectMapper.writeValueAsBytes(currency);
-
-            // Preparing indexBlocksFromNode
-            IndexRequestBuilder indexRequest = client.prepareIndex(INDEX, RECORD_TYPE)
-                    .setId(currency.getCurrency())
-                    .setSource(json);
-
-            // Execute indexBlocksFromNode
-            indexRequest
-                    .setRefresh(true)
-                    .execute().actionGet();
-
-        } catch(JsonProcessingException e) {
-            throw new TechnicalException(e);
-        }
-    }
-
-    public String indexPeer(String currency, Peer peer) {
-        Preconditions.checkNotNull(currency);
-        Preconditions.checkNotNull(peer);
-        try {
-            // Serialize into JSON
-            byte[] json = objectMapper.writeValueAsBytes(peer);
-
-            // Preparing index
-            IndexRequestBuilder indexRequest = client.prepareIndex(currency, PEER_TYPE)
-                    .setSource(json);
-
-            // Execute index
-            return indexRequest
-                    .setRefresh(true)
-                    .execute().actionGet().getId();
-
-        } catch(JsonProcessingException e) {
-            throw new TechnicalException(e);
-        }
-    }
-
-    /**
-     * Get suggestions from a string query. Useful for web autocomplete field (e.g. text full search)
-     * @param query
-     * @return
-     */
-   /* public List<String> getSuggestions(String query) {
-        CompletionSuggestionBuilder suggestionBuilder = new CompletionSuggestionBuilder(INDEX_TYPE)
-                .text(query)
-                .size(10) // limit to 10 results
-                .field("tags");
-
-        // Prepare request
-        SuggestRequestBuilder suggestRequest = client
-                .prepareSuggest(INDEX_NAME)
-                .addSuggestion(suggestionBuilder);
-
-        // Execute query
-        SuggestResponse response = suggestRequest.execute().actionGet();
-
-        // Read query result
-        return toSuggestions(response, RECORD_CATEGORY_TYPE, query);
-    }*/
-
     /**
      * Save a blockchain (update or create) into the blockchain index.
      * @param currency
@@ -293,9 +160,9 @@ public class CurrencyService extends AbstractService {
      */
     public void saveCurrency(Currency currency, String issuer) throws DuplicateIndexIdException {
         Preconditions.checkNotNull(currency, "currency could not be null") ;
-        Preconditions.checkNotNull(currency.getCurrency(), "currency attribute 'currency' could not be null");
+        Preconditions.checkNotNull(currency.getId(), "currency attribute 'currency' could not be null");
 
-        String previousIssuer = getSenderPubkeyByCurrencyId(currency.getCurrency());
+        String previousIssuer = getSenderPubkeyByCurrencyId(currency.getId());
 
         // Currency not exists, so create it
         if (previousIssuer == null) {
@@ -303,20 +170,32 @@ public class CurrencyService extends AbstractService {
             currency.setIssuer(issuer);
 
             // Save it
-            indexCurrency(currency);
+            currencyDao.create(currency);
+
+            // Create data index (delete first if exists)
+            getCurrencyDataDao(currency.getId())
+                .deleteIndex()
+                .createIndexIfNotExists();
+
         }
 
         // Exists, so check the owner signature
         else {
-            if (!Objects.equals(issuer, previousIssuer)) {
+            if (issuer != null && !Objects.equals(issuer, previousIssuer)) {
                 throw new AccessDeniedException("Could not change the currency, because it has been registered by another public key.");
             }
 
             // Make sure the sender is not changed
-            currency.setIssuer(previousIssuer);
+            if (issuer != null) {
+                currency.setIssuer(previousIssuer);
+            }
 
             // Save changes
-            indexCurrency(currency);
+            currencyDao.update(currency);
+
+            // Create data index (if need)
+            getCurrencyDataDao(currency.getId())
+                    .createIndexIfNotExists();
         }
     }
 
@@ -342,7 +221,7 @@ public class CurrencyService extends AbstractService {
         try {
             currency = objectMapper.readValue(jsonCurrency, Currency.class);
             Preconditions.checkNotNull(currency);
-            Preconditions.checkNotNull(currency.getCurrency());
+            Preconditions.checkNotNull(currency.getCurrencyName());
         } catch(Throwable t) {
             logger.error("Error while reading blockchain JSON: " + jsonCurrency);
             throw new TechnicalException("Error while reading blockchain JSON: " + jsonCurrency, t);
@@ -353,60 +232,7 @@ public class CurrencyService extends AbstractService {
 
     /* -- Internal methods -- */
 
-    public XContentBuilder createRecordTypeMapping() {
-        try {
-            XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject(RECORD_TYPE)
-                    .startObject("properties")
-
-                    // currency
-                    .startObject("currency")
-                    .field("type", "string")
-                    .endObject()
-
-                    // firstBlockSignature
-                    .startObject("firstBlockSignature")
-                    .field("type", "string")
-                    .field("index", "not_analyzed")
-                    .endObject()
-
-                    // member count
-                    .startObject("membersCount")
-                    .field("type", "long")
-                    .endObject()
-
-                    // lastUD
-                    .startObject("lastUD")
-                    .field("type", "long")
-                    .endObject()
-
-                    // unitbase
-                    .startObject("unitbase")
-                    .field("type", "integer")
-                    .endObject()
-
-                    // issuer
-                    .startObject("issuer")
-                    .field("type", "string")
-                    .field("index", "not_analyzed")
-                    .endObject()
-
-                    // tags
-                    .startObject("tags")
-                    .field("type", "completion")
-                    .field("search_analyzer", "simple")
-                    .field("analyzer", "simple")
-                    .field("preserve_separators", "false")
-
-                    .endObject()
-                    .endObject()
-                    .endObject().endObject();
-
-            return mapping;
-        }
-        catch(IOException ioe) {
-            throw new TechnicalException(String.format("Error while getting mapping for index [%s/%s]: %s", INDEX, RECORD_TYPE, ioe.getMessage()), ioe);
-        }
-    }
+
 
     /**
      * Retrieve a blockchain from its name
@@ -415,11 +241,12 @@ public class CurrencyService extends AbstractService {
      */
     protected String getSenderPubkeyByCurrencyId(String currencyId) {
 
-        if (!existsIndex(currencyId)) {
+        if (!isCurrencyExists(currencyId)) {
             return null;
         }
 
         // Prepare request
+
         SearchRequestBuilder searchRequest = client
                 .prepareSearch(INDEX)
                 .setTypes(RECORD_TYPE)
@@ -451,4 +278,41 @@ public class CurrencyService extends AbstractService {
 
         return null;
     }
+
+    protected IndexDao<?> getCurrencyDataDao(final String currencyId) {
+        // Create data
+        IndexDao<?> dataDao = currencyDataDaos.get(currencyId);
+        if (dataDao == null) {
+            dataDao = new AbstractIndexDao(currencyId) {
+                @Override
+                protected void createIndex() throws JsonProcessingException {
+                    logger.info(String.format("Creating index [%s]", currencyId));
+
+                    CreateIndexRequestBuilder createIndexRequestBuilder = client.admin().indices().prepareCreate(currencyId);
+                    org.elasticsearch.common.settings.Settings indexSettings = org.elasticsearch.common.settings.Settings.settingsBuilder()
+                            .put("number_of_shards", 3)
+                            .put("number_of_replicas", 1)
+                            //.put("analyzer", createDefaultAnalyzer())
+                            .build();
+                    createIndexRequestBuilder.setSettings(indexSettings);
+
+                    // Add peer type
+                    TypeDao<?> peerDao = (TypeDao<?>)ServiceLocator.instance().getBean(PeerDao.class);
+                    createIndexRequestBuilder.addMapping(peerDao.getType(), peerDao.createTypeMapping());
+
+                    // Add block type
+                    BlockDao blockDao = ServiceLocator.instance().getBean(BlockDao.class);
+                    createIndexRequestBuilder.addMapping(blockDao.getType(), blockDao.createTypeMapping());
+
+                    createIndexRequestBuilder.execute().actionGet();
+                }
+            };
+            injector.injectMembers(dataDao);
+            currencyDataDaos.put(currencyId, dataDao);
+        }
+
+        return dataDao;
+
+
+    }
 }
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/EndpointService.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/EndpointService.java
deleted file mode 100644
index 1fb2eb9fd812b050e9f4adb40bd583387e79e091..0000000000000000000000000000000000000000
--- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/EndpointService.java
+++ /dev/null
@@ -1,533 +0,0 @@
-package org.duniter.elasticsearch.service;
-
-/*
- * #%L
- * UCoin Java Client :: Core API
- * %%
- * Copyright (C) 2014 - 2015 EIS
- * %%
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU 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 General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public 
- * License along with this program.  If not, see
- * <http://www.gnu.org/licenses/gpl-3.0.html>.
- * #L%
- */
-
-
-import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import org.duniter.core.client.model.bma.BlockchainParameters;
-import org.duniter.core.client.model.bma.EndpointApi;
-import org.duniter.core.client.model.bma.jackson.JacksonUtils;
-import org.duniter.core.client.model.local.Peer;
-import org.duniter.core.exception.TechnicalException;
-import org.duniter.core.model.NullProgressionModel;
-import org.duniter.core.model.ProgressionModel;
-import org.duniter.core.util.CollectionUtils;
-import org.duniter.core.util.Preconditions;
-import org.duniter.core.util.StringUtils;
-import org.duniter.core.util.concurrent.CompletableFutures;
-import org.duniter.core.util.json.JsonAttributeParser;
-import org.duniter.core.util.json.JsonSyntaxException;
-import org.duniter.core.util.websocket.WebsocketClientEndpoint;
-import org.duniter.elasticsearch.PluginSettings;
-import org.duniter.elasticsearch.exception.DuplicateIndexIdException;
-import org.duniter.elasticsearch.threadpool.ThreadPool;
-import org.elasticsearch.action.ActionFuture;
-import org.elasticsearch.action.index.IndexRequestBuilder;
-import org.elasticsearch.action.index.IndexResponse;
-import org.elasticsearch.action.search.SearchRequestBuilder;
-import org.elasticsearch.action.search.SearchResponse;
-import org.elasticsearch.action.search.SearchType;
-import org.elasticsearch.action.update.UpdateRequestBuilder;
-import org.elasticsearch.action.update.UpdateResponse;
-import org.elasticsearch.client.Client;
-import org.elasticsearch.common.inject.Inject;
-import org.elasticsearch.common.xcontent.XContentBuilder;
-import org.elasticsearch.common.xcontent.XContentFactory;
-import org.elasticsearch.index.query.QueryBuilders;
-import org.elasticsearch.search.SearchHitField;
-import org.elasticsearch.search.highlight.HighlightField;
-import org.elasticsearch.search.sort.SortOrder;
-import org.nuiton.i18n.I18n;
-
-import java.io.IOException;
-import java.util.*;
-import java.util.concurrent.ExecutionException;
-import java.util.stream.Collectors;
-
-/**
- * Created by Benoit on 30/03/2015.
- */
-public class EndpointService extends AbstractService {
-
-    public static final String ENDPOINT_TYPE = "endpoint";
-
-    private final ProgressionModel nullProgressionModel = new NullProgressionModel();
-
-    private org.duniter.core.client.service.bma.BlockchainRemoteService blockchainRemoteService;
-    private org.duniter.core.client.service.local.NetworkService networkService;
-    private ThreadPool threadPool;
-    private List<WebsocketClientEndpoint.ConnectionListener> connectionListeners = new ArrayList<>();
-    private final WebsocketClientEndpoint.ConnectionListener dispatchConnectionListener;
-
-    private final JsonAttributeParser blockCurrencyParser = new JsonAttributeParser("currency");
-    private final JsonAttributeParser blockHashParser = new JsonAttributeParser("hash");
-
-    private ObjectMapper objectMapper;
-
-    @Inject
-    public EndpointService(Client client, PluginSettings settings, ThreadPool threadPool,
-                           final ServiceLocator serviceLocator){
-        super("duniter.network", client, settings);
-        this.objectMapper = JacksonUtils.newObjectMapper();
-        this.threadPool = threadPool;
-        threadPool.scheduleOnStarted(() -> {
-            this.blockchainRemoteService = serviceLocator.getBlockchainRemoteService();
-            this.networkService = serviceLocator.getNetworkService();
-        });
-        dispatchConnectionListener = new WebsocketClientEndpoint.ConnectionListener() {
-            @Override
-            public void onSuccess() {
-                synchronized (connectionListeners) {
-                    connectionListeners.stream().forEach(connectionListener -> connectionListener.onSuccess());
-                }
-            }
-            @Override
-            public void onError(Exception e, long lastTimeUp) {
-                synchronized (connectionListeners) {
-                    connectionListeners.stream().forEach(connectionListener -> connectionListener.onError(e, lastTimeUp));
-                }
-            }
-        };
-    }
-
-    public void registerConnectionListener(WebsocketClientEndpoint.ConnectionListener listener) {
-        synchronized (connectionListeners) {
-            connectionListeners.add(listener);
-        }
-    }
-
-    public EndpointService indexAllEndpoints(Peer peer) {
-        indexAllEndpoints(peer, nullProgressionModel);
-        return this;
-    }
-
-    public EndpointService indexAllEndpoints(Peer peer, ProgressionModel progressionModel) {
-
-        try {
-            // Get the blockchain name from node
-            BlockchainParameters parameter = blockchainRemoteService.getParameters(peer);
-            if (parameter == null) {
-                progressionModel.setStatus(ProgressionModel.Status.FAILED);
-                logger.error(I18n.t("duniter4j.es.networkService.indexPeers.remoteParametersError", peer));
-                return this;
-            }
-            String currencyName = parameter.getCurrency();
-
-            indexPeers(currencyName, peer, progressionModel);
-
-        } catch(Exception e) {
-            logger.error("Error during indexAllEndpoints: " + e.getMessage(), e);
-            progressionModel.setStatus(ProgressionModel.Status.FAILED);
-        }
-
-        return this;
-    }
-
-
-    public EndpointService indexPeers(String currencyName, Peer firstPeer, ProgressionModel progressionModel) {
-        progressionModel.setStatus(ProgressionModel.Status.RUNNING);
-        progressionModel.setTotal(100);
-        long timeStart = System.currentTimeMillis();
-
-        try {
-
-            progressionModel.setTask(I18n.t("duniter4j.es.networkService.indexPeers.task", currencyName, firstPeer));
-            logger.info(I18n.t("duniter4j.es.networkService.indexPeers.task", currencyName, firstPeer));
-
-            // Default filter
-            org.duniter.core.client.service.local.NetworkService.Filter filterDef = new org.duniter.core.client.service.local.NetworkService.Filter();
-            filterDef.filterType = null;
-            filterDef.filterStatus = Peer.PeerStatus.UP;
-            filterDef.filterEndpoints = ImmutableList.of(EndpointApi.BASIC_MERKLED_API.name(), EndpointApi.BMAS.name());
-
-            // Default sort
-            org.duniter.core.client.service.local.NetworkService.Sort sortDef = new org.duniter.core.client.service.local.NetworkService.Sort();
-            sortDef.sortType = null;
-
-            try {
-                networkService.asyncGetPeers(firstPeer, threadPool.scheduler())
-                        .thenCompose(CompletableFutures::allOfToList)
-                        .thenApply(networkService::fillPeerStatsConsensus)
-                        .thenApply(peers -> peers.stream()
-                                // filter, then sort
-                                .filter(networkService.peerFilter(filterDef))
-                                .map(peer -> savePeer(peer, false))
-                                .collect(Collectors.toList()))
-                        .thenApply(peers -> {
-                            logger.info(I18n.t("duniter4j.es.networkService.indexPeers.succeed", currencyName, firstPeer, peers.size(), (System.currentTimeMillis() - timeStart)));
-                            progressionModel.setStatus(ProgressionModel.Status.SUCCESS);
-                            return peers;
-                        });
-            } catch (InterruptedException | ExecutionException e) {
-                throw new TechnicalException("Error while loading peers: " + e.getMessage(), e);
-            }
-        } catch(Exception e) {
-            logger.error("Error during indexBlocksFromNode: " + e.getMessage(), e);
-            progressionModel.setStatus(ProgressionModel.Status.FAILED);
-        }
-
-        return this;
-    }
-
-/*
-    public void start(Peer peer, FilterAndSortSpec networkSpec) {
-        Preconditions.checkNotNull(networkSpec);
-        this.networkSpec = networkSpec;
-        start(peer);
-    }
-
-    public void start(Peer peer) {
-        Preconditions.checkNotNull(peer);
-
-        log.debug("Starting network crawler...");
-
-        addListeners(peer);
-
-        this.mainPeer = peer;
-
-        try {
-            this.peers = loadPeers(this.mainPeer).get();
-        }
-        catch(Exception e) {
-            throw new TechnicalException("Error during start load peers", e);
-        }
-
-        isStarted = true;
-        log.info("Network crawler started");
-    }
-
-    public void stop() {
-        if (!isStarted) return;
-        log.debug("Stopping network crawler...");
-
-        removeListeners();
-
-        this.mainPeer = null;
-        this.mainPeerWsEp = null;
-        this.isStarted = false;
-
-        this.executorService.shutdown();
-
-        log.info("Network crawler stopped");
-    }*/
-
-    /**
-     * Create or update a peer, depending on its existence and hash
-     * @param peer
-     * @param wait wait indexBlocksFromNode end
-     * @throws DuplicateIndexIdException
-     */
-    public Peer savePeer(final Peer peer, boolean wait) throws DuplicateIndexIdException {
-        Preconditions.checkNotNull(peer, "peer could not be null") ;
-        Preconditions.checkNotNull(peer.getCurrency(), "peer attribute 'currency' could not be null");
-        Preconditions.checkNotNull(peer.getPubkey(), "peer attribute 'pubkey' could not be null");
-        Preconditions.checkNotNull(peer.getHost(), "peer attribute 'host' could not be null");
-        Preconditions.checkNotNull(peer.getApi(), "peer 'api' could not be null");
-
-        String id = cryptoService.hash(peer.computeKey());
-
-        boolean exists = isDocumentExists(peer.getCurrency(), ENDPOINT_TYPE, id);
-
-        // Currency not exists, or has changed, so create it
-        if (!exists) {
-            if (logger.isTraceEnabled()) {
-                logger.trace(String.format("Insert new peer [%s]", peer));
-            }
-
-            // Index new peer
-            indexPeer(peer, id, wait);
-        }
-
-        // Update existing peer
-        else {
-            logger.trace(String.format("Update peer [%s]", peer));
-            updatePeer(peer, id, wait);
-        }
-        return peer;
-    }
-
-    public void indexPeer(Peer peer, String id, boolean wait) {
-        Preconditions.checkNotNull(peer);
-        Preconditions.checkArgument(StringUtils.isNotBlank(peer.getCurrency()));
-        Preconditions.checkNotNull(peer.getHash());
-        Preconditions.checkNotNull(peer.getHost());
-        Preconditions.checkNotNull(peer.getApi());
-
-        // Serialize into JSON
-        // WARN: must use GSON, to have same JSON result (e.g identities and joiners field must be converted into String)
-        try {
-            String json = objectMapper.writeValueAsString(peer);
-
-            // Preparing indexBlocksFromNode
-            IndexRequestBuilder indexRequest = client.prepareIndex(peer.getCurrency(), ENDPOINT_TYPE)
-                    .setId(id)
-                    .setSource(json);
-
-            // Execute indexBlocksFromNode
-            ActionFuture<IndexResponse> futureResponse = indexRequest
-                    .setRefresh(true)
-                    .execute();
-
-            if (wait) {
-                futureResponse.actionGet();
-            }
-        }
-        catch(JsonProcessingException e) {
-            throw new TechnicalException(e);
-        }
-    }
-
-    public void updatePeer(Peer peer, String id, boolean wait) {
-        Preconditions.checkNotNull(peer);
-        Preconditions.checkArgument(StringUtils.isNotBlank(peer.getCurrency()));
-        Preconditions.checkNotNull(peer.getHash());
-        Preconditions.checkNotNull(peer.getHost());
-        Preconditions.checkNotNull(peer.getApi());
-
-        // Serialize into JSON
-        // WARN: must use GSON, to have same JSON result (e.g identities and joiners field must be converted into String)
-        try {
-            String json = objectMapper.writeValueAsString(peer);
-
-            // Preparing indexBlocksFromNode
-            UpdateRequestBuilder updateRequest = client.prepareUpdate(peer.getCurrency(), ENDPOINT_TYPE, id)
-                    .setDoc(json);
-
-            // Execute indexBlocksFromNode
-            ActionFuture<UpdateResponse> futureResponse = updateRequest
-                    .setRefresh(true)
-                    .execute();
-
-            if (wait) {
-                futureResponse.actionGet();
-            }
-        }
-        catch(JsonProcessingException e) {
-            throw new TechnicalException(e);
-        }
-    }
-
-    /**
-     * Index the given block, as the last (current) block. This will check is a fork has occur, and apply a rollback so.
-     */
-    public void onNetworkChanged() {
-        logger.info("ES network service -> peers changed: TODO: index new peers");
-    }
-
-    /**
-     *
-     * @param json block as json
-     * @param refresh Enable ES update with 'refresh' tag ?
-     * @param wait need to wait until processed ?
-     */
-    public EndpointService indexPeer(Peer peer, String json, boolean refresh, boolean wait) {
-        Preconditions.checkNotNull(json);
-        Preconditions.checkArgument(json.length() > 0);
-
-        String currencyName = blockCurrencyParser.getValueAsString(json);
-        String hash = blockHashParser.getValueAsString(json);
-
-        logger.info(I18n.t("duniter4j.es.networkService.indexPeer", currencyName, peer));
-        if (logger.isTraceEnabled()) {
-            logger.trace(json);
-        }
-
-
-        // Preparing index
-        IndexRequestBuilder indexRequest = client.prepareIndex(currencyName, ENDPOINT_TYPE)
-                .setId(hash)
-                .setRefresh(refresh)
-                .setSource(json);
-
-        // Execute indexBlocksFromNode
-        if (!wait) {
-            indexRequest.execute();
-        }
-        else {
-            indexRequest.execute().actionGet();
-        }
-
-        return this;
-    }
-
-    public List<Peer> findPeersByHash(String currencyName, String query) {
-        String[] queryParts = query.split("[\\t ]+");
-
-        // Prepare request
-        SearchRequestBuilder searchRequest = client
-                .prepareSearch(currencyName)
-                .setTypes(ENDPOINT_TYPE)
-                .setSearchType(SearchType.DFS_QUERY_THEN_FETCH);
-
-        // If only one term, search as prefix
-        if (queryParts.length == 1) {
-            searchRequest.setQuery(QueryBuilders.prefixQuery("hash", query));
-        }
-
-        // If more than a word, search on terms match
-        else {
-            searchRequest.setQuery(QueryBuilders.matchQuery("hash", query));
-        }
-
-        // Sort as score/memberCount
-        searchRequest.addSort("_score", SortOrder.DESC)
-                .addSort("number", SortOrder.DESC);
-
-        // Highlight matched words
-        searchRequest.setHighlighterTagsSchema("styled")
-                .addHighlightedField("hash")
-                .addFields("hash")
-                .addFields("*", "_source");
-
-        // Execute query
-        SearchResponse searchResponse = searchRequest.execute().actionGet();
-
-        // Read query result
-        return toPeers(searchResponse, true);
-    }
-
-    /* -- Internal methods -- */
-
-    public static XContentBuilder createEndpointTypeMapping() {
-        try {
-            XContentBuilder mapping = XContentFactory.jsonBuilder()
-                    .startObject()
-                    .startObject(ENDPOINT_TYPE)
-                    .startObject("properties")
-
-                    // currency
-                    .startObject("currency")
-                    .field("sortType", "string")
-                    .endObject()
-
-                    // pubkey
-                    .startObject("pubkey")
-                    .field("sortType", "string")
-                    .field("index", "not_analyzed")
-                    .endObject()
-
-                    // api
-                    .startObject("api")
-                    .field("sortType", "string")
-                    .field("index", "not_analyzed")
-                    .endObject()
-
-                    // uid
-                    .startObject("uid")
-                    .field("sortType", "string")
-                    .endObject()
-
-                    // dns
-                    .startObject("dns")
-                    .field("sortType", "string")
-                    .endObject()
-
-                    // ipv4
-                    .startObject("ipv4")
-                    .field("sortType", "string")
-                    .endObject()
-
-                    // ipv6
-                    .startObject("ipv6")
-                    .field("sortType", "string")
-                    .endObject()
-
-                    .endObject()
-                    .endObject().endObject();
-
-            return mapping;
-        }
-        catch(IOException ioe) {
-            throw new TechnicalException("Error while getting mapping for peer index: " + ioe.getMessage(), ioe);
-        }
-    }
-
-    public Peer getPeerByHash(String currencyName, String id) {
-
-        // Prepare request
-        SearchRequestBuilder searchRequest = client
-                .prepareSearch(currencyName)
-                .setTypes(ENDPOINT_TYPE)
-                .setSearchType(SearchType.DFS_QUERY_THEN_FETCH);
-
-        // If more than a word, search on terms match
-        searchRequest.setQuery(QueryBuilders.matchQuery("_id", id));
-
-        // Execute query
-        try {
-            SearchResponse searchResponse = searchRequest.execute().actionGet();
-            List<Peer> peers = toPeers(searchResponse, false);
-            if (CollectionUtils.isEmpty(peers)) {
-                return null;
-            }
-
-            // Return the unique result
-            return CollectionUtils.extractSingleton(peers);
-        }
-        catch(JsonSyntaxException e) {
-            throw new TechnicalException(String.format("Error while getting indexed peer #%s for [%s]", id, currencyName), e);
-        }
-
-    }
-
-    protected List<Peer> toPeers(SearchResponse response, boolean withHighlight) {
-        // Read query result
-        List<Peer> result = Lists.newArrayList();
-        response.getHits().forEach(searchHit -> {
-            Peer peer;
-            if (searchHit.source() != null) {
-                String jsonString = new String(searchHit.source());
-                try {
-                    peer = objectMapper.readValue(jsonString, Peer.class);
-                } catch(Exception e) {
-                    if (logger.isDebugEnabled()) {
-                        logger.debug("Error while parsing peer from JSON:\n" + jsonString);
-                    }
-                    throw new JsonSyntaxException("Error while read peer from JSON: " + e.getMessage(), e);
-                }
-            }
-            else {
-                peer = new Peer();
-                SearchHitField field = searchHit.getFields().get("hash");
-                peer.setHash(field.getValue());
-            }
-            result.add(peer);
-
-            // If possible, use highlights
-            if (withHighlight) {
-                Map<String, HighlightField> fields = searchHit.getHighlightFields();
-                for (HighlightField field : fields.values()) {
-                    String blockNameHighLight = field.getFragments()[0].string();
-                    peer.setHash(blockNameHighLight);
-                }
-            }
-        });
-
-        return result;
-    }
-
-
-}
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/PeerService.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/PeerService.java
new file mode 100644
index 0000000000000000000000000000000000000000..e83927673b983312182b9fd5f1fc57d461fea6ed
--- /dev/null
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/PeerService.java
@@ -0,0 +1,258 @@
+package org.duniter.elasticsearch.service;
+
+/*
+ * #%L
+ * UCoin Java Client :: Core API
+ * %%
+ * Copyright (C) 2014 - 2015 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import org.duniter.core.client.dao.PeerDao;
+import org.duniter.core.client.model.bma.BlockchainParameters;
+import org.duniter.core.client.model.bma.EndpointApi;
+import org.duniter.core.client.model.local.Peer;
+import org.duniter.core.client.service.local.NetworkService;
+import org.duniter.core.exception.TechnicalException;
+import org.duniter.core.model.NullProgressionModel;
+import org.duniter.core.model.ProgressionModel;
+import org.duniter.core.service.CryptoService;
+import org.duniter.core.util.CollectionUtils;
+import org.duniter.core.util.Preconditions;
+import org.duniter.core.util.concurrent.CompletableFutures;
+import org.duniter.core.util.json.JsonSyntaxException;
+import org.duniter.elasticsearch.PluginSettings;
+import org.duniter.elasticsearch.client.Duniter4jClient;
+import org.duniter.elasticsearch.exception.DuplicateIndexIdException;
+import org.duniter.elasticsearch.threadpool.ThreadPool;
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.search.SearchHitField;
+import org.elasticsearch.search.highlight.HighlightField;
+import org.nuiton.i18n.I18n;
+
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+import java.util.stream.Collectors;
+
+/**
+ * Created by Benoit on 30/03/2015.
+ */
+public class PeerService extends AbstractService {
+
+    private final ProgressionModel nullProgressionModel = new NullProgressionModel();
+
+    private org.duniter.core.client.service.bma.BlockchainRemoteService blockchainRemoteService;
+    private org.duniter.core.client.service.local.NetworkService networkService;
+    private ThreadPool threadPool;
+    private PeerDao peerDao;
+
+    @Inject
+    public PeerService(Duniter4jClient client, PluginSettings settings, ThreadPool threadPool,
+                       CryptoService cryptoService,
+                       PeerDao peerDao,
+                       final ServiceLocator serviceLocator){
+        super("duniter.peers", client, settings, cryptoService);
+        this.peerDao = peerDao;
+        this.threadPool = threadPool;
+        threadPool.scheduleOnStarted(() -> {
+            this.blockchainRemoteService = serviceLocator.getBlockchainRemoteService();
+            this.networkService = serviceLocator.getNetworkService();
+        });
+    }
+
+    public PeerService indexAllPeers(Peer peer) {
+        indexAllPeers(peer, nullProgressionModel);
+        return this;
+    }
+
+    public PeerService indexAllPeers(Peer peer, ProgressionModel progressionModel) {
+
+        try {
+            // Get the blockchain name from node
+            BlockchainParameters parameter = blockchainRemoteService.getParameters(peer);
+            if (parameter == null) {
+                progressionModel.setStatus(ProgressionModel.Status.FAILED);
+                logger.error(I18n.t("duniter4j.es.networkService.indexPeers.remoteParametersError", peer));
+                return this;
+            }
+            String currencyName = parameter.getCurrency();
+
+            indexPeers(currencyName, peer, progressionModel);
+
+        } catch(Exception e) {
+            logger.error("Error during indexAllPeers: " + e.getMessage(), e);
+            progressionModel.setStatus(ProgressionModel.Status.FAILED);
+        }
+
+        return this;
+    }
+
+
+    public PeerService indexPeers(String currencyName, Peer firstPeer, ProgressionModel progressionModel) {
+        progressionModel.setStatus(ProgressionModel.Status.RUNNING);
+        progressionModel.setTotal(100);
+        long timeStart = System.currentTimeMillis();
+
+        try {
+
+            progressionModel.setTask(I18n.t("duniter4j.es.networkService.indexPeers.task", currencyName, firstPeer));
+            logger.info(I18n.t("duniter4j.es.networkService.indexPeers.task", currencyName, firstPeer));
+
+            // Default filter
+            org.duniter.core.client.service.local.NetworkService.Filter filterDef = new org.duniter.core.client.service.local.NetworkService.Filter();
+            filterDef.filterType = null;
+            filterDef.filterStatus = Peer.PeerStatus.UP;
+            filterDef.filterEndpoints = ImmutableList.of(EndpointApi.BASIC_MERKLED_API.name(), EndpointApi.BMAS.name());
+
+            // Default sort
+            org.duniter.core.client.service.local.NetworkService.Sort sortDef = new org.duniter.core.client.service.local.NetworkService.Sort();
+            sortDef.sortType = null;
+
+            try {
+                networkService.asyncGetPeers(firstPeer, threadPool.scheduler())
+                        .thenCompose(CompletableFutures::allOfToList)
+                        .thenApply(networkService::fillPeerStatsConsensus)
+                        .thenApply(peers -> peers.stream()
+                                // filter, then sort
+                                .filter(networkService.peerFilter(filterDef))
+                                .map(peer -> savePeer(peer))
+                                .collect(Collectors.toList()))
+                        .thenApply(peers -> {
+                            logger.info(I18n.t("duniter4j.es.networkService.indexPeers.succeed", currencyName, firstPeer, peers.size(), (System.currentTimeMillis() - timeStart)));
+                            progressionModel.setStatus(ProgressionModel.Status.SUCCESS);
+                            return peers;
+                        });
+            } catch (InterruptedException | ExecutionException e) {
+                throw new TechnicalException("Error while loading peers: " + e.getMessage(), e);
+            }
+        } catch(Exception e) {
+            logger.error("Error during indexBlocksFromNode: " + e.getMessage(), e);
+            progressionModel.setStatus(ProgressionModel.Status.FAILED);
+        }
+
+        return this;
+    }
+
+    public void listenAndIndexPeers(final Peer mainPeer) {
+        // Get the blockchain name from node
+        BlockchainParameters parameter = blockchainRemoteService.getParameters(mainPeer);
+        if (parameter == null) {
+            logger.error(I18n.t("duniter4j.es.networkService.indexPeers.remoteParametersError", mainPeer));
+            return;
+        }
+        String currencyName = parameter.getCurrency();
+
+        // Default filter
+        NetworkService.Filter filterDef = new NetworkService.Filter();
+        filterDef.filterType = null;
+        filterDef.filterStatus = Peer.PeerStatus.UP;
+        filterDef.filterEndpoints = ImmutableList.of(EndpointApi.BASIC_MERKLED_API.name(), EndpointApi.BMAS.name());
+
+        // Default sort
+        NetworkService.Sort sortDef = new NetworkService.Sort();
+        sortDef.sortType = null;
+
+        networkService.addPeersChangeListener(mainPeer, peers -> {
+            if (CollectionUtils.isNotEmpty(peers)) {
+                logger.info(String.format("[%s] Updating peers endpoints (%s endpoints found)", currencyName, peers.size()));
+                peers.stream().forEach(peer -> savePeer(peer));
+            }
+        }, filterDef, sortDef, true /*autoreconnect*/, threadPool.scheduler());
+    }
+
+    /**
+     * Create or update a peer, depending on its existence and hash
+     * @param peer
+     * @throws DuplicateIndexIdException
+     */
+    public Peer savePeer(final Peer peer) throws DuplicateIndexIdException {
+        Preconditions.checkNotNull(peer, "peer could not be null") ;
+        Preconditions.checkNotNull(peer.getCurrency(), "peer attribute 'currency' could not be null");
+        Preconditions.checkNotNull(peer.getPubkey(), "peer attribute 'pubkey' could not be null");
+        Preconditions.checkNotNull(peer.getHost(), "peer attribute 'host' could not be null");
+        Preconditions.checkNotNull(peer.getApi(), "peer 'api' could not be null");
+
+        String id = cryptoService.hash(peer.computeKey());
+        peer.setId(id);
+
+        boolean exists = peerDao.isExists(peer.getCurrency(), id);
+
+        // Currency not exists, or has changed, so create it
+        if (!exists) {
+            if (logger.isTraceEnabled()) {
+                logger.trace(String.format("Insert new peer [%s]", peer));
+            }
+
+            // Index new peer
+            peer.setId(id);
+            peerDao.create(peer);
+        }
+
+        // Update existing peer
+        else {
+            logger.trace(String.format("Update peer [%s]", peer));
+            peerDao.update(peer);
+        }
+        return peer;
+    }
+
+
+    /* -- protected methods -- */
+
+    protected List<Peer> toPeers(SearchResponse response, boolean withHighlight) {
+        // Read query result
+        List<Peer> result = Lists.newArrayList();
+        response.getHits().forEach(searchHit -> {
+            Peer peer;
+            if (searchHit.source() != null) {
+                String jsonString = new String(searchHit.source());
+                try {
+                    peer = objectMapper.readValue(jsonString, Peer.class);
+                } catch(Exception e) {
+                    if (logger.isDebugEnabled()) {
+                        logger.debug("Error while parsing peer from JSON:\n" + jsonString);
+                    }
+                    throw new JsonSyntaxException("Error while read peer from JSON: " + e.getMessage(), e);
+                }
+            }
+            else {
+                peer = new Peer();
+                SearchHitField field = searchHit.getFields().get("hash");
+                peer.setHash(field.getValue());
+            }
+            result.add(peer);
+
+            // If possible, use highlights
+            if (withHighlight) {
+                Map<String, HighlightField> fields = searchHit.getHighlightFields();
+                for (HighlightField field : fields.values()) {
+                    String blockNameHighLight = field.getFragments()[0].string();
+                    peer.setHash(blockNameHighLight);
+                }
+            }
+        });
+
+        return result;
+    }
+
+
+}
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/ServiceLocator.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/ServiceLocator.java
index b264f91d209a4c23edb2f867aa50260b5879c327..7c773b054cdbe51833eed2556030a5bc96b072c3 100644
--- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/ServiceLocator.java
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/ServiceLocator.java
@@ -24,24 +24,28 @@ package org.duniter.elasticsearch.service;
 
 
 import org.duniter.core.beans.Bean;
-import org.duniter.core.beans.BeanFactory;
 import org.duniter.core.client.dao.CurrencyDao;
 import org.duniter.core.client.dao.PeerDao;
-import org.duniter.core.client.dao.mem.MemoryCurrencyDaoImpl;
-import org.duniter.core.client.dao.mem.MemoryPeerDaoImpl;
 import org.duniter.core.client.service.DataContext;
 import org.duniter.core.client.service.HttpService;
 import org.duniter.core.client.service.HttpServiceImpl;
 import org.duniter.core.client.service.bma.*;
 import org.duniter.core.client.service.local.*;
-import org.duniter.core.client.service.local.NetworkService;
 import org.duniter.core.client.service.local.CurrencyService;
 import org.duniter.core.exception.TechnicalException;
 import org.duniter.core.service.CryptoService;
 import org.duniter.core.service.Ed25519CryptoServiceImpl;
 import org.duniter.core.service.MailService;
 import org.duniter.core.service.MailServiceImpl;
+import org.duniter.elasticsearch.beans.ESBeanFactory;
+import org.duniter.elasticsearch.dao.BlockDao;
+import org.duniter.elasticsearch.client.Duniter4jClient;
+import org.duniter.elasticsearch.dao.impl.BlockDaoImpl;
+import org.duniter.elasticsearch.dao.impl.CurrencyDaoImpl;
+import org.duniter.elasticsearch.client.Duniter4jClientImpl;
+import org.duniter.elasticsearch.dao.impl.PeerDaoImpl;
 import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.inject.Injector;
 import org.elasticsearch.common.inject.Singleton;
 import org.elasticsearch.common.logging.ESLogger;
 import org.elasticsearch.common.logging.ESLoggerFactory;
@@ -54,15 +58,18 @@ public class ServiceLocator
         {
     private static final ESLogger logger = ESLoggerFactory.getLogger("duniter.service");
 
-    private static BeanFactory beanFactory = null;
+    private static ESBeanFactory beanFactory = null;
 
     @Inject
-    public ServiceLocator() {
-        super();
+    public ServiceLocator(Injector injector/*, PeerDao peerDao, CurrencyDao currencyDao*/) {
+        super(getOrCreateBeanFactory());
         if (logger.isDebugEnabled()) {
             logger.debug("Starting Duniter4j ServiceLocator...");
         }
-        setBeanFactory(getOrCreateBeanFactory());
+        beanFactory.setInjector(injector);
+/*
+        beanFactory.setBean(peerDao, PeerDao.class);
+        beanFactory.setBean(currencyDao, CurrencyDao.class);*/
 
         org.duniter.core.client.service.ServiceLocator.setInstance(this);
     }
@@ -80,39 +87,45 @@ public class ServiceLocator
 
     /* -- Internal methods -- */
 
-    protected static BeanFactory getOrCreateBeanFactory() {
+    protected static ESBeanFactory getOrCreateBeanFactory() {
         if (beanFactory != null) {
             return beanFactory;
         }
-        beanFactory = org.duniter.core.client.service.ServiceLocator.instance().getBeanFactory()
-                .bind(BlockchainRemoteService.class, BlockchainRemoteServiceImpl.class)
+        beanFactory = new ESBeanFactory();
+
+        beanFactory.bind(BlockchainRemoteService.class, BlockchainRemoteServiceImpl.class)
                 .bind(NetworkRemoteService.class, NetworkRemoteServiceImpl.class)
                 .bind(WotRemoteService.class, WotRemoteServiceImpl.class)
                 .bind(TransactionRemoteService.class, TransactionRemoteServiceImpl.class)
                 .bind(CryptoService.class, Ed25519CryptoServiceImpl.class)
+                .bind(org.duniter.core.client.service.local.PeerService.class, PeerServiceImpl.class)
                 .bind(MailService.class, MailServiceImpl.class)
-                .bind(PeerService.class, PeerServiceImpl.class)
                 .bind(CurrencyService.class, CurrencyServiceImpl.class)
                 .bind(NetworkService.class, NetworkServiceImpl.class)
                 .bind(HttpService.class, HttpServiceImpl.class)
-                .bind(CurrencyDao.class, MemoryCurrencyDaoImpl.class)
-                .bind(PeerDao.class, MemoryPeerDaoImpl.class)
+                // Dao
+                .bind(CurrencyDao.class, CurrencyDaoImpl.class)
+                .bind(PeerDao.class, PeerDaoImpl.class)
+                .bind(BlockDao.class, BlockDaoImpl.class)
+
                 .add(DataContext.class);
+
         return beanFactory;
     }
 
     public static class Provider<T extends Bean> implements org.elasticsearch.common.inject.Provider<T> {
 
         private final Class<T> clazz;
-        private final BeanFactory beanFactory;
 
         public Provider(Class<T> clazz) {
             this.clazz = clazz;
-            this.beanFactory = getOrCreateBeanFactory();
         }
 
         public T get() {
-            return beanFactory.getBean(clazz);
+            logger.debug("Loading class [" + clazz.getName() + "]...");
+            T result = getOrCreateBeanFactory().getBean(clazz);
+            logger.debug("...end of loading [" + clazz.getName() + "]");
+            return result;
         }
     }
 }
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java
index 7585e95790fac39616ca8ea9a5c6d6fddbb195c9..305b625195e1a7cbbd9f70bc3839b8eef64485a7 100644
--- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java
@@ -32,8 +32,8 @@ import org.duniter.core.client.service.bma.NetworkRemoteService;
 import org.duniter.core.client.service.bma.TransactionRemoteService;
 import org.duniter.core.client.service.bma.WotRemoteService;
 import org.duniter.core.client.service.local.CurrencyService;
-import org.duniter.core.client.service.local.PeerService;
 import org.duniter.core.service.CryptoService;
+import org.duniter.core.service.MailService;
 import org.duniter.elasticsearch.PluginInit;
 import org.duniter.elasticsearch.PluginSettings;
 import org.duniter.elasticsearch.service.changes.ChangeService;
@@ -54,23 +54,23 @@ public class ServiceModule extends AbstractModule implements Module {
 
         // blockchain indexation services
         bind(BlockchainService.class).asEagerSingleton();
-        bind(EndpointService.class).asEagerSingleton();
+        bind(PeerService.class).asEagerSingleton();
 
         // Duniter Client API beans
         bindWithLocator(BlockchainRemoteService.class);
         bindWithLocator(NetworkRemoteService.class);
         bindWithLocator(WotRemoteService.class);
         bindWithLocator(TransactionRemoteService.class);
-        bindWithLocator(PeerService.class);
+        bindWithLocator(org.duniter.core.client.service.local.PeerService.class);
         bindWithLocator(CurrencyService.class);
         bindWithLocator(HttpService.class);
-        bindWithLocator(CurrencyDao.class);
-        bindWithLocator(PeerDao.class);
+        //bindWithLocator(CurrencyDao.class);
+        //bindWithLocator(PeerDao.class);
         bindWithLocator(DataContext.class);
 
         // Duniter Shared API beans
         bindWithLocator(CryptoService.class);
-        bindWithLocator(org.duniter.core.service.MailService.class);
+        bindWithLocator(MailService.class);
     }
 
     /* protected methods */
diff --git a/duniter4j-es-gchange/pom.xml b/duniter4j-es-gchange/pom.xml
index dff7b07d31377710f0151b9e45b8584bdf042788..ab8ce385a9635160987197cd812a1f98779430dc 100644
--- a/duniter4j-es-gchange/pom.xml
+++ b/duniter4j-es-gchange/pom.xml
@@ -7,7 +7,6 @@
     <version>0.9.2-SNAPSHOT</version>
   </parent>
 
-  <groupId>org.duniter</groupId>
   <artifactId>duniter4j-es-gchange</artifactId>
   <packaging>jar</packaging>
   <name>Duniter4j :: ElasticSearch GChange plugin</name>
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/Plugin.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/Plugin.java
index c7b1067c7cdd35bfa0b8174cdde750ad5cdcd59a..f4db995937fa9f104742f7418679abc48b200ebe 100644
--- a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/Plugin.java
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/Plugin.java
@@ -23,6 +23,7 @@ package org.duniter.elasticsearch.gchange;
  */
 
 import com.google.common.collect.Lists;
+import org.duniter.elasticsearch.gchange.dao.DaoModule;
 import org.duniter.elasticsearch.gchange.rest.RestModule;
 import org.duniter.elasticsearch.gchange.service.ServiceModule;
 import org.elasticsearch.common.component.LifecycleComponent;
@@ -61,8 +62,9 @@ public class Plugin extends org.elasticsearch.plugins.Plugin {
             log.warn(description() + " has been disabled.");
             return modules;
         }
-        modules.add(new RestModule());
+        modules.add(new DaoModule());
         modules.add(new ServiceModule());
+        modules.add(new RestModule());
 
         return modules;
     }
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/AbstractCommentDaoImpl.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/AbstractCommentDaoImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..52944b007df0f9f5232db32243a85e36361e83ab
--- /dev/null
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/AbstractCommentDaoImpl.java
@@ -0,0 +1,155 @@
+package org.duniter.elasticsearch.gchange.dao;
+
+/*
+ * #%L
+ * UCoin Java Client :: Core API
+ * %%
+ * Copyright (C) 2014 - 2015 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.duniter.core.client.model.elasticsearch.RecordComment;
+import org.duniter.core.exception.TechnicalException;
+import org.duniter.elasticsearch.dao.AbstractIndexTypeDao;
+import org.duniter.elasticsearch.gchange.PluginSettings;
+import org.duniter.elasticsearch.gchange.dao.CommentDao;
+import org.elasticsearch.action.search.SearchPhaseExecutionException;
+import org.elasticsearch.action.search.SearchRequestBuilder;
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.action.search.SearchType;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.index.query.TermQueryBuilder;
+
+import java.io.IOException;
+
+/**
+ * Created by Benoit on 30/03/2015.
+ */
+public class AbstractCommentDaoImpl<T extends AbstractCommentDaoImpl> extends AbstractIndexTypeDao<T> implements CommentDao<T> {
+
+
+    protected PluginSettings pluginSettings;
+
+    public AbstractCommentDaoImpl(String index, PluginSettings pluginSettings) {
+        super(index, CommentDao.TYPE);
+        this.pluginSettings = pluginSettings;
+    }
+
+    @Override
+    protected void createIndex() throws JsonProcessingException {
+        throw new TechnicalException("not implemented");
+    }
+
+    public String create(final String json) {
+        return super.indexDocumentFromJson(json);
+    }
+
+    public void update(final String id, final String json) {
+        super.updateDocumentFromJson(id, json);
+    }
+
+    @Override
+    public long countReplies(String id) {
+
+        // Prepare count request
+        SearchRequestBuilder searchRequest = client
+                .prepareSearch(getIndex())
+                .setTypes(getType())
+                .setFetchSource(false)
+                .setSearchType(SearchType.QUERY_AND_FETCH)
+                .setSize(0);
+
+        // Query = filter on reference
+        TermQueryBuilder query = QueryBuilders.termQuery(RecordComment.PROPERTY_REPLY_TO_JSON, id);
+        searchRequest.setQuery(query);
+
+        // Execute query
+        try {
+            SearchResponse response = searchRequest.execute().actionGet();
+            return response.getHits().getTotalHits();
+        }
+        catch(SearchPhaseExecutionException e) {
+            // Failed or no item on index
+            logger.error(String.format("Error while counting comment replies: %s", e.getMessage()), e);
+        }
+        return 1;
+    }
+
+
+    public XContentBuilder createTypeMapping() {
+        String stringAnalyzer = pluginSettings.getDefaultStringAnalyzer();
+
+        try {
+            XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject(getType())
+                    .startObject("properties")
+
+                    // issuer
+                    .startObject("issuer")
+                    .field("type", "string")
+                    .field("index", "not_analyzed")
+                    .endObject()
+
+                    // time
+                    .startObject("time")
+                    .field("type", "integer")
+                    .endObject()
+
+                    // message
+                    .startObject("message")
+                    .field("type", "string")
+                    .field("analyzer", stringAnalyzer)
+                    .endObject()
+
+                    // record
+                    .startObject("record")
+                    .field("type", "string")
+                    .field("index", "not_analyzed")
+                    .endObject()
+
+                    // reply to
+                    .startObject("reply_to")
+                    .field("type", "string")
+                    .field("index", "not_analyzed")
+                    .endObject()
+
+                    // aggregations
+                    .startObject("aggregations")
+                    .field("type", "nested")
+                    .field("dynamic", "true")
+                    .startObject("properties")
+                    .startObject("reply_count")
+                    .field("type", "integer")
+                    .field("index", "not_analyzed")
+                    .endObject()
+                    .endObject()
+                    .endObject()
+
+                    .endObject()
+                    .endObject().endObject();
+
+            return mapping;
+        }
+        catch(IOException ioe) {
+            throw new TechnicalException(String.format("Error while getting mapping for index [%s]: %s", getType(), ioe.getMessage()), ioe);
+        }
+    }
+
+}
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/AbstractRecordDaoImpl.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/AbstractRecordDaoImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..648137245cd7df2464ea102ac1c8384179ba770e
--- /dev/null
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/AbstractRecordDaoImpl.java
@@ -0,0 +1,210 @@
+package org.duniter.elasticsearch.gchange.dao;
+
+/*
+ * #%L
+ * UCoin Java Client :: Core API
+ * %%
+ * Copyright (C) 2014 - 2015 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.duniter.core.client.model.elasticsearch.Record;
+import org.duniter.core.exception.TechnicalException;
+import org.duniter.core.util.ObjectUtils;
+import org.duniter.elasticsearch.dao.AbstractIndexTypeDao;
+import org.duniter.elasticsearch.gchange.PluginSettings;
+import org.duniter.elasticsearch.gchange.dao.RecordDao;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+
+import java.io.IOException;
+
+/**
+ * Created by Benoit on 30/03/2015.
+ */
+public class AbstractRecordDaoImpl<T extends AbstractRecordDaoImpl> extends AbstractIndexTypeDao<T> implements RecordDao<T> {
+
+    protected PluginSettings pluginSettings;
+
+    public AbstractRecordDaoImpl(String index, PluginSettings pluginSettings) {
+        super(index, RecordDao.TYPE);
+        this.pluginSettings = pluginSettings;
+    }
+
+    @Override
+    protected void createIndex() throws JsonProcessingException {
+        throw new TechnicalException("not implemented");
+    }
+
+    @Override
+    public void checkSameDocumentIssuer(String id, String expectedIssuer) {
+       String issuer = getMandatoryFieldsById(id, Record.PROPERTY_ISSUER).get(Record.PROPERTY_ISSUER).toString();
+       if (!ObjectUtils.equals(expectedIssuer, issuer)) {
+           throw new TechnicalException("Not same issuer");
+       }
+    }
+
+    public XContentBuilder createTypeMapping() {
+        String stringAnalyzer = pluginSettings.getDefaultStringAnalyzer();
+
+        try {
+            XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject(getType())
+                    .startObject("properties")
+
+                    // title
+                    .startObject("title")
+                    .field("type", "string")
+                    .field("analyzer", stringAnalyzer)
+                    .endObject()
+
+                    // description
+                    .startObject("description")
+                    .field("type", "string")
+                    .field("analyzer", stringAnalyzer)
+                    .endObject()
+
+                    // creationTime
+                    .startObject("creationTime")
+                    .field("type", "integer")
+                    .endObject()
+
+                    // time
+                    .startObject("time")
+                    .field("type", "integer")
+                    .endObject()
+
+                    // issuer
+                    .startObject("issuer")
+                    .field("type", "string")
+                    .field("index", "not_analyzed")
+                    .endObject()
+
+                    // pubkey
+                    .startObject("pubkey")
+                    .field("type", "string")
+                    .field("index", "not_analyzed")
+                    .endObject()
+
+                    // address
+                    .startObject("address")
+                    .field("type", "string")
+                    .field("analyzer", stringAnalyzer)
+                    .endObject()
+
+                    // city
+                    .startObject("city")
+                    .field("type", "string")
+                    .endObject()
+
+                    // geoPoint
+                    .startObject("geoPoint")
+                    .field("type", "geo_point")
+                    .endObject()
+
+                    // thumbnail
+                    .startObject("thumbnail")
+                    .field("type", "attachment")
+                        .startObject("fields") // src
+                        .startObject("content") // title
+                            .field("index", "no")
+                        .endObject()
+                        .startObject("title") // title
+                            .field("type", "string")
+                            .field("store", "no")
+                        .endObject()
+                        .startObject("author") // title
+                            .field("store", "no")
+                        .endObject()
+                        .startObject("content_type") // title
+                            .field("store", "yes")
+                        .endObject()
+                    .endObject()
+                    .endObject()
+
+                    // pictures
+                    .startObject("pictures")
+                    .field("type", "nested")
+                    .field("dynamic", "false")
+                        .startObject("properties")
+                            .startObject("file") // file
+                                .field("type", "attachment")
+                                .startObject("fields")
+                                    .startObject("content") // content
+                                        .field("index", "no")
+                                    .endObject()
+                                    .startObject("title") // title
+                                        .field("type", "string")
+                                        .field("store", "yes")
+                                        .field("analyzer", stringAnalyzer)
+                                    .endObject()
+                                    .startObject("author") // author
+                                        .field("type", "string")
+                                        .field("store", "no")
+                                    .endObject()
+                                    .startObject("content_type") // content_type
+                                        .field("store", "yes")
+                                    .endObject()
+                                .endObject()
+                            .endObject()
+                        .endObject()
+                    .endObject()
+
+                    // picturesCount
+                    .startObject("picturesCount")
+                    .field("type", "integer")
+                    .endObject()
+
+                    // category
+                    .startObject("category")
+                    .field("type", "nested")
+                    .field("dynamic", "false")
+                    .startObject("properties")
+                    .startObject("id") // id
+                    .field("type", "string")
+                    .field("index", "not_analyzed")
+                    .endObject()
+                    .startObject("parent") // parent
+                    .field("type", "string")
+                    .field("index", "not_analyzed")
+                    .endObject()
+                    .startObject("name") // name
+                    .field("type", "string")
+                    .field("analyzer", stringAnalyzer)
+                    .endObject()
+                    .endObject()
+                    .endObject()
+
+                    // tags
+                    .startObject("tags")
+                    .field("type", "completion")
+                    .field("search_analyzer", "simple")
+                    .field("analyzer", "simple")
+                    .field("preserve_separators", "false")
+                    .endObject()
+
+                    .endObject()
+                    .endObject().endObject();
+
+            return mapping;
+        }
+        catch(IOException ioe) {
+            throw new TechnicalException(String.format("Error while getting mapping for index [%s/%s]: %s", getIndex(), getType(), ioe.getMessage()), ioe);
+        }
+    }
+}
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/CommentDao.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/CommentDao.java
new file mode 100644
index 0000000000000000000000000000000000000000..3cefe3997f7fdf4e74ed24cbba6a6a6d882c2d83
--- /dev/null
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/CommentDao.java
@@ -0,0 +1,41 @@
+package org.duniter.elasticsearch.gchange.dao;
+
+/*
+ * #%L
+ * UCoin Java Client :: Core API
+ * %%
+ * Copyright (C) 2014 - 2015 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+
+import org.duniter.elasticsearch.dao.IndexTypeDao;
+
+/**
+ * Created by Benoit on 30/03/2015.
+ */
+public interface CommentDao<T extends CommentDao> extends IndexTypeDao<T> {
+
+    String TYPE = "comment";
+
+    String create(final String json);
+
+    void update(final String id, final String json);
+
+    long countReplies(String id);
+
+}
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/DaoModule.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/DaoModule.java
new file mode 100644
index 0000000000000000000000000000000000000000..65625ab03abd553e5dbf9c4a304dad3e2bdd9d6a
--- /dev/null
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/DaoModule.java
@@ -0,0 +1,52 @@
+package org.duniter.elasticsearch.gchange.dao;
+
+/*
+ * #%L
+ * duniter4j-elasticsearch-plugin
+ * %%
+ * Copyright (C) 2014 - 2016 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+import org.duniter.core.beans.Bean;
+import org.duniter.elasticsearch.client.Duniter4jClientImpl;
+import org.duniter.elasticsearch.gchange.dao.market.*;
+import org.duniter.elasticsearch.gchange.dao.registry.*;
+import org.duniter.elasticsearch.service.ServiceLocator;
+import org.elasticsearch.common.inject.AbstractModule;
+import org.elasticsearch.common.inject.Module;
+
+public class DaoModule extends AbstractModule implements Module {
+
+    @Override protected void configure() {
+
+        bind(RegistryIndexDao.class).to(RegistryIndexDaoImpl.class).asEagerSingleton();
+        bind(RegistryCommentDao.class).to(RegistryCommentDaoImpl.class).asEagerSingleton();
+        bind(RegistryRecordDao.class).to(RegistryRecordDaoImpl.class).asEagerSingleton();
+
+        bind(MarketIndexDao.class).to(MarketIndexDaoImpl.class).asEagerSingleton();
+        bind(MarketCommentDao.class).to(MarketCommentDaoImpl.class).asEagerSingleton();
+        bind(MarketRecordDao.class).to(MarketRecordDaoImpl.class).asEagerSingleton();
+    }
+
+    /* protected methods */
+
+    protected <T extends Bean> void bindWithLocator(Class<T> clazz) {
+        bind(clazz).toProvider(new ServiceLocator.Provider<>(clazz));
+    }
+
+}
\ No newline at end of file
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/RecordDao.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/RecordDao.java
new file mode 100644
index 0000000000000000000000000000000000000000..3313806a9bf69254e10819135870302603557624
--- /dev/null
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/RecordDao.java
@@ -0,0 +1,41 @@
+package org.duniter.elasticsearch.gchange.dao;
+
+/*
+ * #%L
+ * UCoin Java Client :: Core API
+ * %%
+ * Copyright (C) 2014 - 2015 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU 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 General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+
+import org.duniter.elasticsearch.dao.IndexTypeDao;
+
+/**
+ * Created by Benoit on 30/03/2015.
+ */
+public interface RecordDao<T extends RecordDao> extends IndexTypeDao<T> {
+
+    String TYPE = "record";
+
+    String create(final String json);
+
+    void update(final String id, final String json);
+
+    void checkSameDocumentIssuer(String id, String expectedIssuer);
+
+}
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/CitiesRegistryService.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/location/CitiesLocationDaoImpl.java
similarity index 96%
rename from duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/CitiesRegistryService.java
rename to duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/location/CitiesLocationDaoImpl.java
index b4b30dfb38565b4d839cdfad89567f409cb43f54..bcdf9807663fa165369a3834a3bf41bf7fc95cd9 100644
--- a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/CitiesRegistryService.java
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/location/CitiesLocationDaoImpl.java
@@ -1,4 +1,4 @@
-package org.duniter.elasticsearch.gchange.service;
+package org.duniter.elasticsearch.gchange.dao.location;
 
 /*
  * #%L
@@ -26,7 +26,7 @@ package org.duniter.elasticsearch.gchange.service;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import org.duniter.core.exception.TechnicalException;
 import org.duniter.elasticsearch.PluginSettings;
-import org.duniter.elasticsearch.service.AbstractService;
+import org.duniter.elasticsearch.dao.AbstractDao;
 import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.common.inject.Inject;
@@ -40,9 +40,9 @@ import java.io.IOException;
 /**
  * Created by Benoit on 30/03/2015.
  */
-public class CitiesRegistryService extends AbstractService {
+public class CitiesLocationDaoImpl extends AbstractDao {
 
-    private static final ESLogger log = ESLoggerFactory.getLogger(CitiesRegistryService.class.getName());
+    private static final ESLogger log = ESLoggerFactory.getLogger(CitiesLocationDaoImpl.class.getName());
 
     private static final String CITIES_BULK_FILENAME = "registry-cities-bulk-insert.json";
 
@@ -50,12 +50,11 @@ public class CitiesRegistryService extends AbstractService {
 
     private static final String CITIES_SOURCE_FILE2 = "/home/blavenie/git/ucoin-io/duniter4j/duniter4j-elasticsearch/src/main/misc/geoflar-communes-2015.geojson";
 
-    public static final String INDEX = "registry";
+    public static final String INDEX = "location";
     public static final String CITY_TYPE = "city";
 
-    @Inject
-    public CitiesRegistryService(Client client, PluginSettings settings) {
-        super(client, settings);
+    public CitiesLocationDaoImpl() {
+        super("gchange.location.cities");
     }
 
     /**
@@ -63,12 +62,12 @@ public class CitiesRegistryService extends AbstractService {
      * @throws JsonProcessingException
      */
     public void deleteIndex() throws JsonProcessingException {
-        deleteIndexIfExists(INDEX);
+        client.deleteIndexIfExists(INDEX);
     }
 
 
     public boolean existsIndex() {
-        return super.existsIndex(INDEX);
+        return client.existsIndex(INDEX);
     }
 
     /**
@@ -76,7 +75,7 @@ public class CitiesRegistryService extends AbstractService {
      */
     public void createIndexIfNotExists() {
         try {
-            if (!existsIndex(INDEX)) {
+            if (!client.existsIndex(INDEX)) {
                 createIndex();
             }
         }
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/market/MarketCommentDao.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/market/MarketCommentDao.java
new file mode 100644
index 0000000000000000000000000000000000000000..a3f16efab314f737cbf942d0124c0a90faad29ef
--- /dev/null
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/market/MarketCommentDao.java
@@ -0,0 +1,9 @@
+package org.duniter.elasticsearch.gchange.dao.market;
+
+import org.duniter.elasticsearch.gchange.dao.CommentDao;
+
+/**
+ * Created by blavenie on 03/04/17.
+ */
+public interface MarketCommentDao extends CommentDao {
+}
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/market/MarketCommentDaoImpl.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/market/MarketCommentDaoImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..415f3ba193d83e012de37530d5ceb95f3d872038
--- /dev/null
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/market/MarketCommentDaoImpl.java
@@ -0,0 +1,22 @@
+package org.duniter.elasticsearch.gchange.dao.market;
+
+import org.duniter.core.exception.TechnicalException;
+import org.duniter.elasticsearch.gchange.PluginSettings;
+import org.duniter.elasticsearch.gchange.dao.AbstractCommentDaoImpl;
+import org.duniter.elasticsearch.gchange.dao.AbstractRecordDaoImpl;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+
+import java.io.IOException;
+
+/**
+ * Created by blavenie on 03/04/17.
+ */
+public class MarketCommentDaoImpl extends AbstractCommentDaoImpl implements MarketCommentDao {
+
+    @Inject
+    public MarketCommentDaoImpl(PluginSettings pluginSettings) {
+        super(MarketIndexDao.INDEX, pluginSettings);
+    }
+}
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/market/MarketIndexDao.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/market/MarketIndexDao.java
new file mode 100644
index 0000000000000000000000000000000000000000..2e3f36177728d5f8cee36ff2230b6ba875b35ce0
--- /dev/null
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/market/MarketIndexDao.java
@@ -0,0 +1,11 @@
+package org.duniter.elasticsearch.gchange.dao.market;
+
+import org.duniter.elasticsearch.dao.IndexDao;
+
+/**
+ * Created by blavenie on 03/04/17.
+ */
+public interface MarketIndexDao extends IndexDao<MarketIndexDao> {
+    String INDEX = "market";
+    String CATEGORY_TYPE = "category";
+}
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/market/MarketIndexDaoImpl.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/market/MarketIndexDaoImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..69548e97c3b231274df263fdc2d9df79cc7af787
--- /dev/null
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/market/MarketIndexDaoImpl.java
@@ -0,0 +1,111 @@
+package org.duniter.elasticsearch.gchange.dao.market;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.duniter.core.exception.TechnicalException;
+import org.duniter.elasticsearch.dao.AbstractIndexDao;
+import org.duniter.elasticsearch.dao.AbstractIndexTypeDao;
+import org.duniter.elasticsearch.dao.IndexTypeDao;
+import org.duniter.elasticsearch.dao.handler.AddSequenceAttributeHandler;
+import org.duniter.elasticsearch.gchange.PluginSettings;
+import org.duniter.elasticsearch.gchange.dao.CommentDao;
+import org.duniter.elasticsearch.gchange.dao.RecordDao;
+import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+
+import java.io.IOException;
+
+/**
+ * Created by blavenie on 03/04/17.
+ */
+public class MarketIndexDaoImpl extends AbstractIndexDao<MarketIndexDao> implements MarketIndexDao {
+
+    private static final String CATEGORIES_BULK_CLASSPATH_FILE = "registry-categories-bulk-insert.json";
+
+    private PluginSettings pluginSettings;
+    private IndexTypeDao<?> categoryDao;
+    private RecordDao recordDao;
+    private CommentDao commentDao;
+
+    @Inject
+    public MarketIndexDaoImpl(PluginSettings pluginSettings, MarketRecordDao recordDao, MarketCommentDao commentDao) {
+        super(MarketIndexDao.INDEX);
+
+        this.pluginSettings = pluginSettings;
+        this.commentDao = commentDao;
+        this.recordDao = recordDao;
+        this.categoryDao = createCategoryDao(pluginSettings);
+    }
+
+
+    @Override
+    protected void createIndex() throws JsonProcessingException {
+        logger.info(String.format("Creating index [%s]", INDEX));
+
+        CreateIndexRequestBuilder createIndexRequestBuilder = client.admin().indices().prepareCreate(INDEX);
+        org.elasticsearch.common.settings.Settings indexSettings = org.elasticsearch.common.settings.Settings.settingsBuilder()
+                .put("number_of_shards", 3)
+                .put("number_of_replicas", 1)
+                //.put("analyzer", createDefaultAnalyzer())
+                .build();
+        createIndexRequestBuilder.setSettings(indexSettings);
+        createIndexRequestBuilder.addMapping(recordDao.getType(), recordDao.createTypeMapping());
+        createIndexRequestBuilder.addMapping(commentDao.getType(), commentDao.createTypeMapping());
+        createIndexRequestBuilder.addMapping(categoryDao.getType(), categoryDao.createTypeMapping());
+        createIndexRequestBuilder.execute().actionGet();
+
+        // Fill categories
+        fillRecordCategories();
+    }
+
+    public void fillRecordCategories() {
+        if (logger.isDebugEnabled()) {
+            logger.debug(String.format("[%s/%s] Fill data", INDEX, MarketIndexDao.CATEGORY_TYPE));
+        }
+
+        // Insert categories
+        categoryDao.bulkFromClasspathFile(CATEGORIES_BULK_CLASSPATH_FILE,
+                // Add order attribute
+                new AddSequenceAttributeHandler("order", "\\{.*\"name\".*\\}", 1));
+    }
+
+
+    protected IndexTypeDao<?> createCategoryDao(final PluginSettings settings) {
+        return new AbstractIndexTypeDao(INDEX, MarketIndexDao.CATEGORY_TYPE) {
+            @Override
+            protected void createIndex() throws JsonProcessingException {
+                throw new TechnicalException("not implemented");
+            }
+
+            @Override
+            public XContentBuilder createTypeMapping() {
+                try {
+                    XContentBuilder mapping = XContentFactory.jsonBuilder().startObject()
+                            .startObject(getType())
+                            .startObject("properties")
+
+                            // name
+                            .startObject("name")
+                            .field("type", "string")
+                            .field("analyzer", settings.getDefaultStringAnalyzer())
+                            .endObject()
+
+                            // parent
+                            .startObject("parent")
+                            .field("type", "string")
+                            .field("index", "not_analyzed")
+                            .endObject()
+
+                            .endObject()
+                            .endObject().endObject();
+
+                    return mapping;
+                }
+                catch(IOException ioe) {
+                    throw new TechnicalException(String.format("Error while getting mapping for index [%s/%s]: %s", getIndex(), getType(), ioe.getMessage()), ioe);
+                }
+            }
+        };
+    }
+}
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/market/MarketRecordDao.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/market/MarketRecordDao.java
new file mode 100644
index 0000000000000000000000000000000000000000..7b4238a9dc5dfecb935e17012f688a1bf601d271
--- /dev/null
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/market/MarketRecordDao.java
@@ -0,0 +1,9 @@
+package org.duniter.elasticsearch.gchange.dao.market;
+
+import org.duniter.elasticsearch.gchange.dao.RecordDao;
+
+/**
+ * Created by blavenie on 03/04/17.
+ */
+public interface MarketRecordDao extends RecordDao {
+}
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/market/MarketRecordDaoImpl.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/market/MarketRecordDaoImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..f5f855d7326e75f9ee15a6b61710835ff6349cdc
--- /dev/null
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/market/MarketRecordDaoImpl.java
@@ -0,0 +1,182 @@
+package org.duniter.elasticsearch.gchange.dao.market;
+
+import org.duniter.core.exception.TechnicalException;
+import org.duniter.elasticsearch.gchange.PluginSettings;
+import org.duniter.elasticsearch.gchange.dao.AbstractRecordDaoImpl;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+
+import java.io.IOException;
+
+/**
+ * Created by blavenie on 03/04/17.
+ */
+public class MarketRecordDaoImpl extends AbstractRecordDaoImpl implements MarketRecordDao {
+
+    @Inject
+    public MarketRecordDaoImpl(PluginSettings pluginSettings) {
+        super(MarketIndexDao.INDEX, pluginSettings);
+    }
+
+    @Override
+    public XContentBuilder createTypeMapping() {
+        String stringAnalyzer = pluginSettings.getDefaultStringAnalyzer();
+
+        try {
+            XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject(getType())
+                    .startObject("properties")
+
+                    // title
+                    .startObject("title")
+                    .field("type", "string")
+                    .field("analyzer", stringAnalyzer)
+                    .endObject()
+
+                    // description
+                    .startObject("description")
+                    .field("type", "string")
+                    .field("analyzer", stringAnalyzer)
+                    .endObject()
+
+                    // creationTime
+                    .startObject("creationTime")
+                    .field("type", "integer")
+                    .endObject()
+
+                    // time
+                    .startObject("time")
+                    .field("type", "integer")
+                    .endObject()
+
+                    // price
+                    .startObject("price")
+                    .field("type", "double")
+                    .endObject()
+
+                    // price Unit
+                    .startObject("unit")
+                    .field("type", "string")
+                    .field("index", "not_analyzed")
+                    .endObject()
+
+                    // currency
+                    .startObject("currency")
+                    .field("type", "string")
+                    .field("index", "not_analyzed")
+                    .endObject()
+
+                    // issuer
+                    .startObject("issuer")
+                    .field("type", "string")
+                    .field("index", "not_analyzed")
+                    .endObject()
+
+                    // type (offer, need, ...)
+                    .startObject("type")
+                    .field("type", "string")
+                    .field("index", "not_analyzed")
+                    .endObject()
+
+                    // location
+                    .startObject("location")
+                    .field("type", "string")
+                    .field("analyzer", stringAnalyzer)
+                    .endObject()
+
+                    // geoPoint
+                    .startObject("geoPoint")
+                    .field("type", "geo_point")
+                    .endObject()
+
+                    // thumbnail
+                    .startObject("thumbnail")
+                    .field("type", "attachment")
+                    .startObject("fields") // src
+                    .startObject("content") // title
+                    .field("index", "no")
+                    .endObject()
+                    .startObject("title") // title
+                    .field("type", "string")
+                    .field("store", "no")
+                    .endObject()
+                    .startObject("author") // title
+                    .field("store", "no")
+                    .endObject()
+                    .startObject("content_type") // title
+                    .field("store", "yes")
+                    .endObject()
+                    .endObject()
+                    .endObject()
+
+                    // pictures
+                    .startObject("pictures")
+                    .field("type", "nested")
+                    .field("dynamic", "false")
+                    .startObject("properties")
+                    .startObject("file") // file
+                    .field("type", "attachment")
+                    .startObject("fields")
+                    .startObject("content") // content
+                    .field("index", "no")
+                    .endObject()
+                    .startObject("title") // title
+                    .field("type", "string")
+                    .field("store", "yes")
+                    .field("analyzer", stringAnalyzer)
+                    .endObject()
+                    .startObject("author") // author
+                    .field("type", "string")
+                    .field("store", "no")
+                    .endObject()
+                    .startObject("content_type") // content_type
+                    .field("store", "yes")
+                    .endObject()
+                    .endObject()
+                    .endObject()
+                    .endObject()
+                    .endObject()
+
+                    // picturesCount
+                    .startObject("picturesCount")
+                    .field("type", "integer")
+                    .endObject()
+
+                    // category
+                    .startObject("category")
+                    .field("type", "nested")
+                    .field("dynamic", "false")
+                    .startObject("properties")
+                    .startObject("id") // id
+                    .field("type", "string")
+                    .field("index", "not_analyzed")
+                    .endObject()
+                    .startObject("parent") // parent
+                    .field("type", "string")
+                    .field("index", "not_analyzed")
+                    .endObject()
+                    .startObject("name") // name
+                    .field("type", "string")
+                    .field("analyzer", stringAnalyzer)
+                    .endObject()
+                    .endObject()
+                    .endObject()
+
+                    // tags
+                    .startObject("tags")
+                    .field("type", "completion")
+                    .field("search_analyzer", "simple")
+                    .field("analyzer", "simple")
+                    .field("preserve_separators", "false")
+                    .endObject()
+
+                    .endObject()
+                    .endObject().endObject();
+
+            return mapping;
+        }
+        catch(IOException ioe) {
+            throw new TechnicalException(String.format("Error while getting mapping for index [%s/%s]: %s", getIndex(), getType(), ioe.getMessage()), ioe);
+        }
+    }
+}
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/registry/RegistryCommentDao.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/registry/RegistryCommentDao.java
new file mode 100644
index 0000000000000000000000000000000000000000..e70be6d0e371069d344a3ffc4a773a394d584758
--- /dev/null
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/registry/RegistryCommentDao.java
@@ -0,0 +1,9 @@
+package org.duniter.elasticsearch.gchange.dao.registry;
+
+import org.duniter.elasticsearch.gchange.dao.CommentDao;
+
+/**
+ * Created by blavenie on 03/04/17.
+ */
+public interface RegistryCommentDao extends CommentDao {
+}
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/registry/RegistryCommentDaoImpl.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/registry/RegistryCommentDaoImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..c9d4b28ad4f71078b88ebc76800971cdeee2671b
--- /dev/null
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/registry/RegistryCommentDaoImpl.java
@@ -0,0 +1,17 @@
+package org.duniter.elasticsearch.gchange.dao.registry;
+
+import org.duniter.elasticsearch.gchange.PluginSettings;
+import org.duniter.elasticsearch.gchange.dao.AbstractCommentDaoImpl;
+import org.elasticsearch.common.inject.Inject;
+
+/**
+ * Created by blavenie on 03/04/17.
+ */
+public class RegistryCommentDaoImpl extends AbstractCommentDaoImpl implements RegistryCommentDao {
+
+
+    @Inject
+    public RegistryCommentDaoImpl(PluginSettings pluginSettings) {
+        super(RegistryIndexDao.INDEX, pluginSettings);
+    }
+}
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/registry/RegistryIndexDao.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/registry/RegistryIndexDao.java
new file mode 100644
index 0000000000000000000000000000000000000000..f803d1733524c1e3e163c3a5d6ba6847afacffe3
--- /dev/null
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/registry/RegistryIndexDao.java
@@ -0,0 +1,11 @@
+package org.duniter.elasticsearch.gchange.dao.registry;
+
+import org.duniter.elasticsearch.dao.IndexDao;
+
+/**
+ * Created by blavenie on 03/04/17.
+ */
+public interface RegistryIndexDao extends IndexDao<RegistryIndexDao> {
+    String INDEX = "registry";
+    String CATEGORY_TYPE = "category";
+}
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/registry/RegistryIndexDaoImpl.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/registry/RegistryIndexDaoImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..c62ded0962cd57c6008e7c2ba9aa88a1a9a85841
--- /dev/null
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/registry/RegistryIndexDaoImpl.java
@@ -0,0 +1,117 @@
+package org.duniter.elasticsearch.gchange.dao.registry;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import org.duniter.core.exception.TechnicalException;
+import org.duniter.elasticsearch.dao.AbstractIndexDao;
+import org.duniter.elasticsearch.dao.AbstractIndexTypeDao;
+import org.duniter.elasticsearch.dao.IndexDao;
+import org.duniter.elasticsearch.dao.IndexTypeDao;
+import org.duniter.elasticsearch.dao.handler.AddSequenceAttributeHandler;
+import org.duniter.elasticsearch.gchange.PluginSettings;
+import org.duniter.elasticsearch.gchange.dao.AbstractCommentDaoImpl;
+import org.duniter.elasticsearch.gchange.dao.AbstractRecordDaoImpl;
+import org.duniter.elasticsearch.gchange.dao.CommentDao;
+import org.duniter.elasticsearch.gchange.dao.RecordDao;
+import org.duniter.elasticsearch.gchange.service.RegistryService;
+import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.inject.Injector;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.XContentFactory;
+
+import java.io.IOException;
+
+/**
+ * Created by blavenie on 03/04/17.
+ */
+public class RegistryIndexDaoImpl extends AbstractIndexDao<RegistryIndexDao> implements RegistryIndexDao {
+
+
+    private static final String CATEGORIES_BULK_CLASSPATH_FILE = "registry-categories-bulk-insert.json";
+
+    private PluginSettings pluginSettings;
+    private IndexTypeDao<?> categoryDao;
+    private RecordDao recordDao;
+    private CommentDao commentDao;
+
+    @Inject
+    public RegistryIndexDaoImpl(PluginSettings pluginSettings, RegistryRecordDao recordDao, RegistryCommentDao commentDao) {
+        super(RegistryIndexDao.INDEX);
+
+        this.pluginSettings = pluginSettings;
+        this.commentDao = commentDao;
+        this.recordDao = recordDao;
+        this.categoryDao = createCategoryDao(pluginSettings);
+    }
+
+
+    @Override
+    protected void createIndex() throws JsonProcessingException {
+        logger.info(String.format("Creating index [%s]", INDEX));
+
+        CreateIndexRequestBuilder createIndexRequestBuilder = client.admin().indices().prepareCreate(INDEX);
+        org.elasticsearch.common.settings.Settings indexSettings = org.elasticsearch.common.settings.Settings.settingsBuilder()
+                .put("number_of_shards", 3)
+                .put("number_of_replicas", 1)
+                //.put("analyzer", createDefaultAnalyzer())
+                .build();
+        createIndexRequestBuilder.setSettings(indexSettings);
+        createIndexRequestBuilder.addMapping(recordDao.getType(), recordDao.createTypeMapping());
+        createIndexRequestBuilder.addMapping(commentDao.getType(), commentDao.createTypeMapping());
+        createIndexRequestBuilder.addMapping(categoryDao.getType(), categoryDao.createTypeMapping());
+        createIndexRequestBuilder.execute().actionGet();
+
+        // Fill categories
+        fillRecordCategories();
+    }
+
+    public void fillRecordCategories() {
+        if (logger.isDebugEnabled()) {
+            logger.debug(String.format("[%s/%s] Fill data", INDEX, RegistryIndexDao.CATEGORY_TYPE));
+        }
+
+        // Insert categories
+        categoryDao.bulkFromClasspathFile(CATEGORIES_BULK_CLASSPATH_FILE,
+                // Add order attribute
+                new AddSequenceAttributeHandler("order", "\\{.*\"name\".*\\}", 1));
+    }
+
+
+    protected IndexTypeDao<?> createCategoryDao(final PluginSettings settings) {
+        return new AbstractIndexTypeDao(INDEX, RegistryIndexDao.CATEGORY_TYPE) {
+            @Override
+            protected void createIndex() throws JsonProcessingException {
+                throw new TechnicalException("not implemented");
+            }
+
+            @Override
+            public XContentBuilder createTypeMapping() {
+                try {
+                    XContentBuilder mapping = XContentFactory.jsonBuilder().startObject()
+                            .startObject(getType())
+                            .startObject("properties")
+
+                            // name
+                            .startObject("name")
+                            .field("type", "string")
+                            .field("analyzer", settings.getDefaultStringAnalyzer())
+                            .endObject()
+
+                            // parent
+                            .startObject("parent")
+                            .field("type", "string")
+                            .field("index", "not_analyzed")
+                            .endObject()
+
+                            .endObject()
+                            .endObject().endObject();
+
+                    return mapping;
+                }
+                catch(IOException ioe) {
+                    throw new TechnicalException(String.format("Error while getting mapping for index [%s/%s]: %s", getIndex(), getType(), ioe.getMessage()), ioe);
+                }
+            }
+        };
+    }
+}
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/registry/RegistryRecordDao.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/registry/RegistryRecordDao.java
new file mode 100644
index 0000000000000000000000000000000000000000..163ebea84380c11af3bb8f4a977c97b84cc68979
--- /dev/null
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/registry/RegistryRecordDao.java
@@ -0,0 +1,9 @@
+package org.duniter.elasticsearch.gchange.dao.registry;
+
+import org.duniter.elasticsearch.gchange.dao.RecordDao;
+
+/**
+ * Created by blavenie on 03/04/17.
+ */
+public interface RegistryRecordDao extends RecordDao {
+}
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/registry/RegistryRecordDaoImpl.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/registry/RegistryRecordDaoImpl.java
new file mode 100644
index 0000000000000000000000000000000000000000..b9ae257b326024b1d88d4d197b5729326c060f70
--- /dev/null
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/dao/registry/RegistryRecordDaoImpl.java
@@ -0,0 +1,16 @@
+package org.duniter.elasticsearch.gchange.dao.registry;
+
+import org.duniter.elasticsearch.gchange.PluginSettings;
+import org.duniter.elasticsearch.gchange.dao.AbstractRecordDaoImpl;
+import org.elasticsearch.common.inject.Inject;
+
+/**
+ * Created by blavenie on 03/04/17.
+ */
+public class RegistryRecordDaoImpl extends AbstractRecordDaoImpl implements RegistryRecordDao {
+
+    @Inject
+    public RegistryRecordDaoImpl(PluginSettings pluginSettings) {
+        super(RegistryIndexDao.INDEX, pluginSettings);
+    }
+}
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketCategoryAction.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketCategoryAction.java
index 7aa19360824d38cc8b56850dcbe924169f8d2c36..f82ce5a75403b02fcede06741eb484dec6a7e4f6 100644
--- a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketCategoryAction.java
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketCategoryAction.java
@@ -22,8 +22,8 @@ package org.duniter.elasticsearch.gchange.rest.market;
  * #L%
  */
 
+import org.duniter.elasticsearch.gchange.dao.market.MarketIndexDao;
 import org.duniter.elasticsearch.rest.security.RestSecurityController;
-import org.duniter.elasticsearch.gchange.service.MarketService;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.rest.RestRequest;
 
@@ -32,7 +32,7 @@ public class RestMarketCategoryAction {
     @Inject
     public RestMarketCategoryAction(RestSecurityController securityController) {
         // Add security rule for category
-        securityController.allowIndexType(RestRequest.Method.GET, MarketService.INDEX, MarketService.RECORD_CATEGORY_TYPE);
+        securityController.allowIndexType(RestRequest.Method.GET, MarketIndexDao.INDEX, MarketIndexDao.CATEGORY_TYPE);
     }
 
 }
\ No newline at end of file
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketCommentIndexAction.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketCommentIndexAction.java
index a65f154c946d241c15f93a7416d69fc1311cd3ae..e0a39533181d76af21abf5a5c1234d73323630b1 100644
--- a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketCommentIndexAction.java
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketCommentIndexAction.java
@@ -22,6 +22,8 @@ package org.duniter.elasticsearch.gchange.rest.market;
  * #L%
  */
 
+import org.duniter.elasticsearch.gchange.dao.market.MarketCommentDao;
+import org.duniter.elasticsearch.gchange.dao.market.MarketIndexDao;
 import org.duniter.elasticsearch.rest.AbstractRestPostIndexAction;
 import org.duniter.elasticsearch.rest.security.RestSecurityController;
 import org.duniter.elasticsearch.gchange.service.MarketService;
@@ -36,7 +38,7 @@ public class RestMarketCommentIndexAction extends AbstractRestPostIndexAction {
     public RestMarketCommentIndexAction(Settings settings, RestController controller, Client client, RestSecurityController securityController,
                                         MarketService service) {
         super(settings, controller, client, securityController,
-                MarketService.INDEX, MarketService.RECORD_COMMENT_TYPE,
+                MarketIndexDao.INDEX, MarketCommentDao.TYPE,
                 json -> service.indexCommentFromJson(json));
     }
 
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketCommentUpdateAction.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketCommentUpdateAction.java
index 7872d14dd5271f519f801beee0a07fee12b6f9c9..0052f6aafa158b32eacd1d607e9e0bb8c8bb68a3 100644
--- a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketCommentUpdateAction.java
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketCommentUpdateAction.java
@@ -22,6 +22,8 @@ package org.duniter.elasticsearch.gchange.rest.market;
  * #L%
  */
 
+import org.duniter.elasticsearch.gchange.dao.market.MarketCommentDao;
+import org.duniter.elasticsearch.gchange.dao.market.MarketIndexDao;
 import org.duniter.elasticsearch.rest.AbstractRestPostUpdateAction;
 import org.duniter.elasticsearch.rest.security.RestSecurityController;
 import org.duniter.elasticsearch.gchange.service.MarketService;
@@ -36,7 +38,7 @@ public class RestMarketCommentUpdateAction extends AbstractRestPostUpdateAction
     public RestMarketCommentUpdateAction(Settings settings, RestController controller, Client client, RestSecurityController securityController,
                                          MarketService service) {
         super(settings, controller, client, securityController,
-                MarketService.INDEX, MarketService.RECORD_COMMENT_TYPE,
+                MarketIndexDao.INDEX, MarketCommentDao.TYPE,
                 (id, json) -> service.updateCommentFromJson(id, json));
     }
 
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketImageAction.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketImageAction.java
index 20a075618736885d36bc21f3d679a283314d46f2..dbc054d50def487ac70f8652d8ca7dd3d3f85163 100644
--- a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketImageAction.java
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketImageAction.java
@@ -22,6 +22,8 @@ package org.duniter.elasticsearch.gchange.rest.market;
  * #L%
  */
 
+import org.duniter.elasticsearch.gchange.dao.market.MarketIndexDao;
+import org.duniter.elasticsearch.gchange.dao.market.MarketRecordDao;
 import org.duniter.elasticsearch.gchange.model.market.MarketRecord;
 import org.duniter.elasticsearch.gchange.model.registry.RegistryRecord;
 import org.duniter.elasticsearch.gchange.service.MarketService;
@@ -36,7 +38,7 @@ public class RestMarketImageAction {
     public RestMarketImageAction(RestSecurityController securityController) {
 
         // Allow to get thumbnail
-        securityController.allowImageAttachment(MarketService.INDEX, MarketService.RECORD_TYPE, MarketRecord.PROPERTY_THUMBNAIL);
+        securityController.allowImageAttachment(MarketIndexDao.INDEX, MarketRecordDao.TYPE, MarketRecord.PROPERTY_THUMBNAIL);
 
         // TODO : allow to get pictures
     }
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketRecordIndexAction.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketRecordIndexAction.java
index 55f4c5fbf4b093e9442a14621f734656cc9f4fe2..7af1deee1241626079c0dc5238852db60bd6cac1 100644
--- a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketRecordIndexAction.java
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketRecordIndexAction.java
@@ -22,6 +22,8 @@ package org.duniter.elasticsearch.gchange.rest.market;
  * #L%
  */
 
+import org.duniter.elasticsearch.gchange.dao.market.MarketIndexDao;
+import org.duniter.elasticsearch.gchange.dao.market.MarketRecordDao;
 import org.duniter.elasticsearch.rest.AbstractRestPostIndexAction;
 import org.duniter.elasticsearch.rest.security.RestSecurityController;
 import org.duniter.elasticsearch.gchange.service.MarketService;
@@ -36,7 +38,7 @@ public class RestMarketRecordIndexAction extends AbstractRestPostIndexAction {
     public RestMarketRecordIndexAction(Settings settings, RestController controller, Client client, RestSecurityController securityController,
                                        MarketService service) {
         super(settings, controller, client, securityController,
-                MarketService.INDEX, MarketService.RECORD_TYPE,
+                MarketIndexDao.INDEX, MarketRecordDao.TYPE,
                 json -> service.indexRecordFromJson(json));
     }
 
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketRecordUpdateAction.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketRecordUpdateAction.java
index 258dc66870bf700ce07e3247acf3afa121c0c5cd..7fd0a76e0907402d6a426f24a1962d31533734ff 100644
--- a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketRecordUpdateAction.java
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/market/RestMarketRecordUpdateAction.java
@@ -22,9 +22,11 @@ package org.duniter.elasticsearch.gchange.rest.market;
  * #L%
  */
 
+import org.duniter.elasticsearch.gchange.dao.market.MarketIndexDao;
+import org.duniter.elasticsearch.gchange.dao.market.MarketRecordDao;
+import org.duniter.elasticsearch.gchange.service.MarketService;
 import org.duniter.elasticsearch.rest.AbstractRestPostUpdateAction;
 import org.duniter.elasticsearch.rest.security.RestSecurityController;
-import org.duniter.elasticsearch.gchange.service.MarketService;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.settings.Settings;
@@ -36,7 +38,7 @@ public class RestMarketRecordUpdateAction extends AbstractRestPostUpdateAction {
     public RestMarketRecordUpdateAction(Settings settings, RestController controller, Client client, RestSecurityController securityController,
                                         MarketService service) {
         super(settings, controller, client, securityController,
-                MarketService.INDEX, MarketService.RECORD_TYPE,
+                MarketIndexDao.INDEX, MarketRecordDao.TYPE,
                 (id, json) -> service.updateRecordFromJson(id, json));
     }
 
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryCategoryAction.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryCategoryAction.java
index 0fdb34fa17095d187e74a24269d5c3e30f36ec7c..ff58d64bcb6b65cd5f2d321b66410147daaeaea7 100644
--- a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryCategoryAction.java
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryCategoryAction.java
@@ -22,6 +22,7 @@ package org.duniter.elasticsearch.gchange.rest.registry;
  * #L%
  */
 
+import org.duniter.elasticsearch.gchange.dao.registry.RegistryIndexDao;
 import org.duniter.elasticsearch.gchange.service.RegistryService;
 import org.duniter.elasticsearch.rest.security.RestSecurityController;
 import org.elasticsearch.common.inject.Inject;
@@ -32,7 +33,7 @@ public class RestRegistryCategoryAction {
     @Inject
     public RestRegistryCategoryAction(RestSecurityController securityController) {
         // Add security rule for category
-        securityController.allowIndexType(RestRequest.Method.GET, RegistryService.INDEX, RegistryService.RECORD_CATEGORY_TYPE);
+        securityController.allowIndexType(RestRequest.Method.GET, RegistryIndexDao.INDEX, RegistryIndexDao.CATEGORY_TYPE);
     }
 
 }
\ No newline at end of file
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryCommentIndexAction.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryCommentIndexAction.java
index bd0047608733aca88299aa1b8a577db679aefb01..7ee815c944e65835bd32e0758803ceb53e0220f8 100644
--- a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryCommentIndexAction.java
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryCommentIndexAction.java
@@ -22,6 +22,8 @@ package org.duniter.elasticsearch.gchange.rest.registry;
  * #L%
  */
 
+import org.duniter.elasticsearch.gchange.dao.registry.RegistryCommentDao;
+import org.duniter.elasticsearch.gchange.dao.registry.RegistryIndexDao;
 import org.duniter.elasticsearch.rest.AbstractRestPostIndexAction;
 import org.duniter.elasticsearch.rest.security.RestSecurityController;
 import org.duniter.elasticsearch.gchange.service.RegistryService;
@@ -36,7 +38,7 @@ public class RestRegistryCommentIndexAction extends AbstractRestPostIndexAction
     public RestRegistryCommentIndexAction(Settings settings, RestController controller, Client client, RestSecurityController securityController,
                                           RegistryService service) {
         super(settings, controller, client, securityController,
-                RegistryService.INDEX, RegistryService.RECORD_COMMENT_TYPE,
+                RegistryIndexDao.INDEX, RegistryCommentDao.TYPE,
                 json -> service.indexCommentFromJson(json));
     }
 
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryCommentUpdateAction.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryCommentUpdateAction.java
index 5b4433b4894635212b78f2d71164abb4b706fe43..e724f60bf5cef85dc8c6f4dc66c2c51040466f18 100644
--- a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryCommentUpdateAction.java
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryCommentUpdateAction.java
@@ -22,9 +22,11 @@ package org.duniter.elasticsearch.gchange.rest.registry;
  * #L%
  */
 
+import org.duniter.elasticsearch.gchange.dao.registry.RegistryCommentDao;
+import org.duniter.elasticsearch.gchange.dao.registry.RegistryIndexDao;
+import org.duniter.elasticsearch.gchange.service.RegistryService;
 import org.duniter.elasticsearch.rest.AbstractRestPostUpdateAction;
 import org.duniter.elasticsearch.rest.security.RestSecurityController;
-import org.duniter.elasticsearch.gchange.service.RegistryService;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.settings.Settings;
@@ -36,7 +38,7 @@ public class RestRegistryCommentUpdateAction extends AbstractRestPostUpdateActio
     public RestRegistryCommentUpdateAction(Settings settings, RestController controller, Client client, RestSecurityController securityController,
                                            RegistryService service) {
         super(settings, controller, client, securityController,
-                RegistryService.INDEX, RegistryService.RECORD_COMMENT_TYPE,
+                RegistryIndexDao.INDEX, RegistryCommentDao.TYPE,
                 (id, json) -> service.updateCommentFromJson(id, json));
     }
 
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryImageAction.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryImageAction.java
index 7a2293955671bc79c62c2441fed93c3cf71bbba5..197ba6066dd9004c64e43762387ae459a583325e 100644
--- a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryImageAction.java
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryImageAction.java
@@ -22,6 +22,9 @@ package org.duniter.elasticsearch.gchange.rest.registry;
  * #L%
  */
 
+import org.duniter.elasticsearch.gchange.dao.registry.RegistryCommentDao;
+import org.duniter.elasticsearch.gchange.dao.registry.RegistryIndexDao;
+import org.duniter.elasticsearch.gchange.dao.registry.RegistryRecordDao;
 import org.duniter.elasticsearch.gchange.model.registry.RegistryRecord;
 import org.duniter.elasticsearch.gchange.service.RegistryService;
 import org.duniter.elasticsearch.rest.security.RestSecurityController;
@@ -35,7 +38,7 @@ public class RestRegistryImageAction {
     public RestRegistryImageAction(RestSecurityController securityController) {
 
         // Allow to get thumbnail
-        securityController.allowImageAttachment(RegistryService.INDEX, RegistryService.RECORD_TYPE, RegistryRecord.PROPERTY_THUMBNAIL);
+        securityController.allowImageAttachment(RegistryIndexDao.INDEX, RegistryRecordDao.TYPE, RegistryRecord.PROPERTY_THUMBNAIL);
 
         // TODO : allow to get pictures
     }
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryRecordIndexAction.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryRecordIndexAction.java
index 02c2e963b271e0ef09d21e116498284a417044dd..297562526cf4b88ee97016a223618902e545545e 100644
--- a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryRecordIndexAction.java
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryRecordIndexAction.java
@@ -22,6 +22,8 @@ package org.duniter.elasticsearch.gchange.rest.registry;
  * #L%
  */
 
+import org.duniter.elasticsearch.gchange.dao.registry.RegistryIndexDao;
+import org.duniter.elasticsearch.gchange.dao.registry.RegistryRecordDao;
 import org.duniter.elasticsearch.rest.AbstractRestPostIndexAction;
 import org.duniter.elasticsearch.rest.security.RestSecurityController;
 import org.duniter.elasticsearch.gchange.service.RegistryService;
@@ -37,7 +39,7 @@ public class RestRegistryRecordIndexAction extends AbstractRestPostIndexAction {
     public RestRegistryRecordIndexAction(Settings settings, RestController controller, Client client, RestSecurityController securityController,
                                          RegistryService service) {
         super(settings, controller, client, securityController,
-                RegistryService.INDEX, RegistryService.RECORD_TYPE,
+                RegistryIndexDao.INDEX, RegistryRecordDao.TYPE,
                 json -> service.indexRecordFromJson(json));
     }
 }
\ No newline at end of file
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryRecordUpdateAction.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryRecordUpdateAction.java
index 9942fc7fa304dd85d21c508b6efe0ad0b0cd90e2..4131c882dac6b3142f192462c16f9611e72ef8e8 100644
--- a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryRecordUpdateAction.java
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/rest/registry/RestRegistryRecordUpdateAction.java
@@ -22,9 +22,11 @@ package org.duniter.elasticsearch.gchange.rest.registry;
  * #L%
  */
 
+import org.duniter.elasticsearch.gchange.dao.registry.RegistryIndexDao;
+import org.duniter.elasticsearch.gchange.dao.registry.RegistryRecordDao;
+import org.duniter.elasticsearch.gchange.service.RegistryService;
 import org.duniter.elasticsearch.rest.AbstractRestPostUpdateAction;
 import org.duniter.elasticsearch.rest.security.RestSecurityController;
-import org.duniter.elasticsearch.gchange.service.RegistryService;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.settings.Settings;
@@ -36,7 +38,7 @@ public class RestRegistryRecordUpdateAction extends AbstractRestPostUpdateAction
     public RestRegistryRecordUpdateAction(Settings settings, RestController controller, Client client, RestSecurityController securityController,
                                           RegistryService service) {
         super(settings, controller, client, securityController,
-                RegistryService.INDEX, RegistryService.RECORD_TYPE,
+                RegistryIndexDao.INDEX, RegistryRecordDao.TYPE,
                 (id, json) -> service.updateRecordFromJson(id, json));
     }
 
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/AbstractService.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/AbstractService.java
index e394717a2eaec4c0d0633179fc1ed4d7cd22eabf..9ac606915729fb4b9c6370e5daf2b12191dc232b 100644
--- a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/AbstractService.java
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/AbstractService.java
@@ -23,6 +23,7 @@ package org.duniter.elasticsearch.gchange.service;
  */
 
 import org.duniter.core.service.CryptoService;
+import org.duniter.elasticsearch.client.Duniter4jClient;
 import org.duniter.elasticsearch.gchange.PluginSettings;
 import org.elasticsearch.client.Client;
 
@@ -33,19 +34,19 @@ public abstract class AbstractService extends org.duniter.elasticsearch.user.ser
 
     protected PluginSettings pluginSettings;
 
-    public AbstractService(String loggerName, Client client, PluginSettings pluginSettings) {
+    public AbstractService(String loggerName, Duniter4jClient client, PluginSettings pluginSettings) {
         this(loggerName, client, pluginSettings, null);
     }
 
-    public AbstractService(Client client, PluginSettings pluginSettings) {
+    public AbstractService(Duniter4jClient client, PluginSettings pluginSettings) {
         this(client, pluginSettings, null);
     }
 
-    public AbstractService(Client client, PluginSettings pluginSettings, CryptoService cryptoService) {
+    public AbstractService(Duniter4jClient client, PluginSettings pluginSettings, CryptoService cryptoService) {
         this("duniter.gchange", client, pluginSettings, cryptoService);
     }
 
-    public AbstractService(String loggerName, Client client, PluginSettings pluginSettings, CryptoService cryptoService) {
+    public AbstractService(String loggerName, Duniter4jClient client, PluginSettings pluginSettings, CryptoService cryptoService) {
         super(loggerName, client, pluginSettings.getDelegate(), cryptoService);
         this.pluginSettings = pluginSettings;
     }
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/CommentService.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/CommentService.java
deleted file mode 100644
index 8bd5dff7f200e303a38f718f3efea1d0addd13c1..0000000000000000000000000000000000000000
--- a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/CommentService.java
+++ /dev/null
@@ -1,254 +0,0 @@
-package org.duniter.elasticsearch.gchange.service;
-
-/*
- * #%L
- * UCoin Java Client :: Core API
- * %%
- * Copyright (C) 2014 - 2015 EIS
- * %%
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU 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 General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public 
- * License along with this program.  If not, see
- * <http://www.gnu.org/licenses/gpl-3.0.html>.
- * #L%
- */
-
-
-import com.fasterxml.jackson.databind.JsonNode;
-import org.apache.commons.collections4.MapUtils;
-import org.duniter.core.client.model.elasticsearch.RecordComment;
-import org.duniter.core.exception.TechnicalException;
-import org.duniter.core.service.CryptoService;
-import org.duniter.elasticsearch.exception.DocumentNotFoundException;
-import org.duniter.elasticsearch.exception.NotFoundException;
-import org.duniter.elasticsearch.gchange.PluginSettings;
-import org.duniter.elasticsearch.gchange.model.event.GchangeEventCodes;
-import org.duniter.elasticsearch.gchange.model.market.MarketRecord;
-import org.duniter.elasticsearch.threadpool.ThreadPool;
-import org.duniter.elasticsearch.user.model.UserEvent;
-import org.duniter.elasticsearch.user.service.HistoryService;
-import org.duniter.elasticsearch.user.service.UserEventService;
-import org.duniter.elasticsearch.user.service.UserService;
-import org.elasticsearch.action.search.SearchPhaseExecutionException;
-import org.elasticsearch.action.search.SearchRequestBuilder;
-import org.elasticsearch.action.search.SearchResponse;
-import org.elasticsearch.action.search.SearchType;
-import org.elasticsearch.client.Client;
-import org.elasticsearch.common.inject.Inject;
-import org.elasticsearch.common.xcontent.XContentBuilder;
-import org.elasticsearch.common.xcontent.XContentFactory;
-import org.elasticsearch.index.query.QueryBuilders;
-import org.elasticsearch.index.query.TermQueryBuilder;
-import org.nuiton.i18n.I18n;
-
-import java.io.IOException;
-import java.util.Map;
-
-/**
- * Created by Benoit on 30/03/2015.
- */
-public class CommentService extends AbstractService {
-
-    private UserEventService userEventService;
-    private UserService userService;
-    private ThreadPool threadPool;
-    private HistoryService historyService;
-
-    @Inject
-    public CommentService(Client client,
-                          PluginSettings pluginSettings,
-                          CryptoService cryptoService,
-                          UserService userService,
-                          UserEventService userEventService,
-                          HistoryService historyService,
-                          ThreadPool threadPool) {
-        super("gchange.comment", client, pluginSettings, cryptoService);
-        this.userEventService = userEventService;
-        this.userService = userService;
-        this.historyService = historyService;
-        this.threadPool = threadPool;
-    }
-
-
-    public String indexCommentFromJson(final String index, final String recordType, final String type, final String json) {
-        JsonNode commentObj = readAndVerifyIssuerSignature(json);
-        String issuer = getMandatoryField(commentObj, RecordComment.PROPERTY_ISSUER).asText();
-
-        // Check the record document exists
-        String recordId = getMandatoryField(commentObj, RecordComment.PROPERTY_RECORD).asText();
-        checkDocumentExistsOrDeleted(index, recordType, recordId);
-
-        if (logger.isDebugEnabled()) {
-            logger.debug(String.format("Indexing a %s from issuer [%s]", type, issuer.substring(0, 8)));
-        }
-        return indexDocumentFromJson(index, type, json);
-    }
-
-    public void updateCommentFromJson(final String index, final String recordType, final String type, final String id, final String json) {
-        JsonNode commentObj = readAndVerifyIssuerSignature(json);
-
-        // Check the record document exists
-        String recordId = getMandatoryField(commentObj, RecordComment.PROPERTY_RECORD).asText();
-        checkDocumentExistsOrDeleted(index, recordType, recordId);
-
-        if (logger.isDebugEnabled()) {
-            String issuer = getMandatoryField(commentObj, RecordComment.PROPERTY_ISSUER).asText();
-            logger.debug(String.format("[%s] Indexing a %s from issuer [%s] on [%s]", index, type, issuer.substring(0, 8)));
-        }
-
-        updateDocumentFromJson(index, type, id, json);
-    }
-
-    public XContentBuilder createRecordCommentType(String index, String type) {
-        String stringAnalyzer = pluginSettings.getDefaultStringAnalyzer();
-
-        try {
-            XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject(type)
-                    .startObject("properties")
-
-                    // issuer
-                    .startObject("issuer")
-                    .field("type", "string")
-                    .field("index", "not_analyzed")
-                    .endObject()
-
-                    // time
-                    .startObject("time")
-                    .field("type", "integer")
-                    .endObject()
-
-                    // message
-                    .startObject("message")
-                    .field("type", "string")
-                    .field("analyzer", stringAnalyzer)
-                    .endObject()
-
-                    // record
-                    .startObject("record")
-                    .field("type", "string")
-                    .field("index", "not_analyzed")
-                    .endObject()
-
-                    // reply to
-                    .startObject("reply_to")
-                    .field("type", "string")
-                    .field("index", "not_analyzed")
-                    .endObject()
-
-                    // aggregations
-                    .startObject("aggregations")
-                        .field("type", "nested")
-                        .field("dynamic", "true")
-                        .startObject("properties")
-                            .startObject("reply_count")
-                            .field("type", "integer")
-                            .field("index", "not_analyzed")
-                            .endObject()
-                        .endObject()
-                    .endObject()
-
-                    .endObject()
-                    .endObject().endObject();
-
-            return mapping;
-        }
-        catch(IOException ioe) {
-            throw new TechnicalException(String.format("Error while getting mapping for index [%s/%s]: %s", index, type, ioe.getMessage()), ioe);
-        }
-    }
-
-
-    /* -- Internal methods -- */
-
-    // Check the record document exists (or has been deleted)
-    private void checkDocumentExistsOrDeleted(String index, String type, String id) {
-        boolean recordExists;
-        try {
-            recordExists = isDocumentExists(index, type, id);
-        } catch (NotFoundException e) {
-            // Check if exists in delete history
-            recordExists = historyService.existsInDeleteHistory(index, type, id);
-        }
-        if (!recordExists) {
-            throw new NotFoundException(String.format("Comment refers a non-existent document [%s/%s/%s].", index, type, id));
-        }
-    }
-
-    /**
-     * Notify user when new comment
-     */
-    private void notifyRecordIssuerForComment(final String index, final String recordType, JsonNode actualObj, boolean isNewComment, String commentId) {
-        String issuer = getMandatoryField(actualObj, RecordComment.PROPERTY_ISSUER).asText();
-
-        // Notify issuer of record (is not same as comment writer)
-        String recordId = getMandatoryField(actualObj, RecordComment.PROPERTY_RECORD).asText();
-        Map<String, Object> recordFields = getFieldsById(index, recordType, recordId,
-                MarketRecord.PROPERTY_TITLE, MarketRecord.PROPERTY_ISSUER);
-        if (MapUtils.isEmpty(recordFields)) { // record not found
-            throw new DocumentNotFoundException(I18n.t("duniter.market.error.comment.recordNotFound", recordId));
-        }
-        String recordIssuer = recordFields.get(MarketRecord.PROPERTY_ISSUER).toString();
-
-        // Get user title
-        String issuerTitle = userService.getProfileTitle(issuer);
-
-        String recordTitle = recordFields.get(MarketRecord.PROPERTY_TITLE).toString();
-        if (!issuer.equals(recordIssuer)) {
-            userEventService.notifyUser(
-                    UserEvent.newBuilder(UserEvent.EventType.INFO, GchangeEventCodes.NEW_COMMENT.name())
-                    .setMessage(
-                            isNewComment ? I18n.n("duniter.market.event.newComment") : I18n.n("duniter.market.event.updateComment"),
-                            issuer,
-                            issuerTitle != null ? issuerTitle : issuer.substring(0, 8),
-                            recordTitle
-                            )
-                    .setRecipient(recordIssuer)
-                    .setReference(index, recordType, recordId)
-                    .setReferenceAnchor(commentId)
-                    .build());
-        }
-    }
-
-    private void updateCommentAggregations(String index, String type, String id) {
-        long replyCount = countCommentReplies(index, type, id);
-        if (replyCount > 0) {
-            logger.warn("Comment [%s] has %s replies. Need to be updated", id, replyCount);
-           // TODO update aggregations
-        }
-    }
-
-    private long countCommentReplies(String index, String type, String id) {
-
-        // Prepare count request
-        SearchRequestBuilder searchRequest = client
-                .prepareSearch(index)
-                .setTypes(type)
-                .setFetchSource(false)
-                .setSearchType(SearchType.QUERY_AND_FETCH)
-                .setSize(0);
-
-        // Query = filter on reference
-        TermQueryBuilder query = QueryBuilders.termQuery(RecordComment.PROPERTY_REPLY_TO_JSON, id);
-        searchRequest.setQuery(query);
-
-        // Execute query
-        try {
-            SearchResponse response = searchRequest.execute().actionGet();
-            return response.getHits().getTotalHits();
-        }
-        catch(SearchPhaseExecutionException e) {
-            // Failed or no item on index
-            logger.error(String.format("Error while counting comment replies: %s", e.getMessage()), e);
-        }
-        return 1;
-    }
-}
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/CommentUserEventService.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/CommentUserEventService.java
index bc8bb500157a8069e4d73f839e90b81c5bca3f66..76da0a6e0267fe649aab2a9eba646a5d7f22f8c0 100644
--- a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/CommentUserEventService.java
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/CommentUserEventService.java
@@ -25,28 +25,28 @@ package org.duniter.elasticsearch.gchange.service;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.fasterxml.jackson.databind.DeserializationFeature;
-import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.collect.ImmutableList;
 import org.apache.commons.collections4.MapUtils;
 import org.duniter.core.client.model.ModelUtils;
-import org.duniter.core.client.model.bma.jackson.JacksonUtils;
 import org.duniter.core.client.model.elasticsearch.RecordComment;
 import org.duniter.core.exception.TechnicalException;
 import org.duniter.core.service.CryptoService;
 import org.duniter.core.util.StringUtils;
-import org.duniter.core.util.websocket.WebsocketClientEndpoint;
+import org.duniter.elasticsearch.client.Duniter4jClient;
 import org.duniter.elasticsearch.gchange.PluginSettings;
+import org.duniter.elasticsearch.gchange.dao.RecordDao;
+import org.duniter.elasticsearch.gchange.dao.market.MarketCommentDao;
+import org.duniter.elasticsearch.gchange.dao.market.MarketIndexDao;
+import org.duniter.elasticsearch.gchange.dao.registry.RegistryCommentDao;
+import org.duniter.elasticsearch.gchange.dao.registry.RegistryIndexDao;
 import org.duniter.elasticsearch.gchange.model.event.GchangeEventCodes;
 import org.duniter.elasticsearch.gchange.model.market.MarketRecord;
-import org.duniter.elasticsearch.service.BlockchainService;
 import org.duniter.elasticsearch.service.changes.ChangeEvent;
 import org.duniter.elasticsearch.service.changes.ChangeService;
 import org.duniter.elasticsearch.service.changes.ChangeSource;
 import org.duniter.elasticsearch.user.model.UserEvent;
-import org.duniter.elasticsearch.user.model.UserEventCodes;
 import org.duniter.elasticsearch.user.service.UserEventService;
 import org.duniter.elasticsearch.user.service.UserService;
-import org.elasticsearch.client.Client;
 import org.elasticsearch.common.inject.Inject;
 import org.nuiton.i18n.I18n;
 
@@ -74,43 +74,30 @@ public class CommentUserEventService extends AbstractService implements ChangeSe
         I18n.n("duniter.registry.event.updateReplyComment");
     }
 
-    public final UserService userService;
-
-    public final UserEventService userEventService;
-
-    public final ObjectMapper objectMapper;
-
-    public final List<ChangeSource> changeListenSources;
-
-    public final boolean enable;
-
-    public final String recordType;
-
-    public final boolean trace;
+    private final UserService userService;
+    private final UserEventService userEventService;
+    private final List<ChangeSource> changeListenSources;
+    private final String recordType;
+    private final boolean trace;
 
     @Inject
-    public CommentUserEventService(Client client, PluginSettings settings, CryptoService cryptoService,
-                                   BlockchainService blockchainService,
+    public CommentUserEventService(Duniter4jClient client,
+                                   PluginSettings settings,
+                                   CryptoService cryptoService,
                                    UserService userService,
                                    UserEventService userEventService) {
         super("duniter.user.event.comment", client, settings, cryptoService);
         this.userService = userService;
         this.userEventService = userEventService;
-        this.objectMapper = JacksonUtils.newObjectMapper();
         objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
         this.changeListenSources = ImmutableList.of(
-                new ChangeSource(MarketService.INDEX, MarketService.RECORD_COMMENT_TYPE),
-                new ChangeSource(RegistryService.INDEX, RegistryService.RECORD_COMMENT_TYPE));
+                new ChangeSource(MarketIndexDao.INDEX, MarketCommentDao.TYPE),
+                new ChangeSource(RegistryIndexDao.INDEX, RegistryCommentDao.TYPE));
         ChangeService.registerListener(this);
 
-        this.enable = pluginSettings.enableBlockchainSync();
         this.trace = logger.isTraceEnabled();
 
-        this.recordType = MarketService.RECORD_TYPE; // same as RegistryService.RECORD_TYPE
-
-        if (this.enable) {
-            blockchainService.registerConnectionListener(createConnectionListeners());
-        }
+        this.recordType = RecordDao.TYPE;
     }
 
     @Override
@@ -152,48 +139,6 @@ public class CommentUserEventService extends AbstractService implements ChangeSe
 
     /* -- internal method -- */
 
-    /**
-     * Create a listener that notify admin when the Duniter node connection is lost or retrieve
-     */
-    private WebsocketClientEndpoint.ConnectionListener createConnectionListeners() {
-        return new WebsocketClientEndpoint.ConnectionListener() {
-            private boolean errorNotified = false;
-
-            @Override
-            public void onSuccess() {
-                // Send notify on reconnection
-                if (errorNotified) {
-                    errorNotified = false;
-                    userEventService.notifyAdmin(UserEvent.newBuilder(UserEvent.EventType.INFO, UserEventCodes.NODE_BMA_UP.name())
-                            .setMessage(I18n.n("duniter.event.NODE_BMA_UP"),
-                                    pluginSettings.getNodeBmaHost(),
-                                    String.valueOf(pluginSettings.getNodeBmaPort()),
-                                    pluginSettings.getClusterName())
-                            .build());
-                }
-            }
-
-            @Override
-            public void onError(Exception e, long lastTimeUp) {
-                if (errorNotified) return; // already notify
-
-                // Wait 1 min, then notify admin (once)
-                long now = System.currentTimeMillis() / 1000;
-                boolean wait = now - lastTimeUp < 60;
-                if (!wait) {
-                    errorNotified = true;
-                    userEventService.notifyAdmin(UserEvent.newBuilder(UserEvent.EventType.ERROR, UserEventCodes.NODE_BMA_DOWN.name())
-                            .setMessage(I18n.n("duniter.event.NODE_BMA_DOWN"),
-                                    pluginSettings.getNodeBmaHost(),
-                                    String.valueOf(pluginSettings.getNodeBmaPort()),
-                                    pluginSettings.getClusterName(),
-                                    String.valueOf(lastTimeUp))
-                            .build());
-                }
-            }
-        };
-    }
-
     /**
      * Send notification from a new comment
      *
@@ -238,7 +183,7 @@ public class CommentUserEventService extends AbstractService implements ChangeSe
                                               GchangeEventCodes eventCodeForParentCommentIssuer, String messageKeyForParentCommentIssuer) {
         // Get record issuer
         String recordId = comment.getRecord();
-        Map<String, Object> record = getFieldsById(index, this.recordType, recordId,
+        Map<String, Object> record = client.getFieldsById(index, this.recordType, recordId,
                 MarketRecord.PROPERTY_TITLE, MarketRecord.PROPERTY_ISSUER);
 
         // Record not found : nothing to emit
@@ -275,7 +220,7 @@ public class CommentUserEventService extends AbstractService implements ChangeSe
         // Notify comment is a reply to another comment
         if (StringUtils.isNotBlank(comment.getReplyTo())) {
 
-            String parentCommentIssuer = getTypedFieldById(index, type, comment.getReplyTo(), RecordComment.PROPERTY_ISSUER);
+            String parentCommentIssuer = client.getTypedFieldById(index, type, comment.getReplyTo(), RecordComment.PROPERTY_ISSUER);
 
             if (StringUtils.isNotBlank(parentCommentIssuer) &&
                     !issuer.equals(parentCommentIssuer) &&
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/MarketService.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/MarketService.java
index 9e9a5a208366d26d54b018fb37faedfd7a076f49..78581d99962e758a6ee1953867c8e3f7a6945816 100644
--- a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/MarketService.java
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/MarketService.java
@@ -23,367 +23,128 @@ package org.duniter.elasticsearch.gchange.service;
  */
 
 
-import com.fasterxml.jackson.core.JsonProcessingException;
-import org.duniter.core.client.service.bma.WotRemoteService;
-import org.duniter.core.exception.TechnicalException;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.duniter.core.client.model.elasticsearch.RecordComment;
 import org.duniter.core.service.CryptoService;
+import org.duniter.elasticsearch.client.Duniter4jClient;
+import org.duniter.elasticsearch.exception.NotFoundException;
 import org.duniter.elasticsearch.gchange.PluginSettings;
-import org.duniter.elasticsearch.gchange.service.AbstractService;
-import org.duniter.elasticsearch.service.ServiceLocator;
-import org.duniter.elasticsearch.threadpool.ThreadPool;
-import org.duniter.elasticsearch.user.service.UserEventService;
-import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
-import org.elasticsearch.action.index.IndexRequestBuilder;
-import org.elasticsearch.action.index.IndexResponse;
+import org.duniter.elasticsearch.gchange.dao.market.MarketCommentDao;
+import org.duniter.elasticsearch.gchange.dao.market.MarketIndexDao;
+import org.duniter.elasticsearch.gchange.dao.market.MarketRecordDao;
+import org.duniter.elasticsearch.user.service.HistoryService;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.common.inject.Inject;
-import org.elasticsearch.common.settings.Settings;
-import org.elasticsearch.common.xcontent.XContentBuilder;
-import org.elasticsearch.common.xcontent.XContentFactory;
-
-import java.io.IOException;
 
 /**
  * Created by Benoit on 30/03/2015.
  */
 public class MarketService extends AbstractService {
 
-    public static final String INDEX = "market";
-    public static final String RECORD_CATEGORY_TYPE = "category";
-    public static final String RECORD_TYPE = "record";
-    public static final String RECORD_COMMENT_TYPE = "comment";
-
-    private static final String CATEGORIES_BULK_CLASSPATH_FILE = "market-categories-bulk-insert.json";
-
-    private WotRemoteService wotRemoteService;
-    private UserEventService userEventService;
-    private CommentService commentService;
+    private MarketIndexDao indexDao;
+    private MarketRecordDao recordDao;
+    private MarketCommentDao commentDao;
+    private HistoryService historyService;
 
     @Inject
-    public MarketService(Client client, PluginSettings settings,
+    public MarketService(Duniter4jClient client, PluginSettings settings,
                          CryptoService cryptoService,
-                         CommentService commentService,
-                         UserEventService userEventService,
-                         ThreadPool threadPool,
-                         final ServiceLocator serviceLocator
+                         HistoryService historyService,
+                         MarketIndexDao indexDao,
+                         MarketCommentDao commentDao,
+                         MarketRecordDao recordDao
                          ) {
-        super("gchange." + INDEX, client, settings, cryptoService);
-        this.commentService = commentService;
-        this.userEventService = userEventService;
-        threadPool.scheduleOnStarted(() -> {
-            wotRemoteService = serviceLocator.getWotRemoteService();
-        });
-    }
+        super("gchange.service.market", client, settings, cryptoService);
+        this.indexDao = indexDao;
+        this.commentDao = commentDao;
+        this.recordDao = recordDao;
 
-    /**
-     * Delete blockchain index, and all data
-     * @throws JsonProcessingException
-     */
-    public MarketService  deleteIndex() {
-        deleteIndexIfExists(INDEX);
-        return this;
+        this.historyService = historyService;
     }
 
-    public boolean existsIndex() {
-        return super.existsIndex(INDEX);
-    }
 
     /**
      * Create index need for blockchain registry, if need
      */
     public MarketService createIndexIfNotExists() {
-        try {
-            if (!existsIndex(INDEX)) {
-                createIndex();
-
-                // Fill categories
-                fillRecordCategories();
-            }
-        }
-        catch(JsonProcessingException e) {
-            throw new TechnicalException(String.format("Error while creating index [%s]", INDEX));
-        }
-
+        indexDao.createIndexIfNotExists();
         return this;
     }
 
-    /**
-     * Create index need for category registry
-     * @throws JsonProcessingException
-     */
-    public MarketService createIndex() throws JsonProcessingException {
-        logger.info(String.format("Creating index [%s]", INDEX));
-
-        CreateIndexRequestBuilder createIndexRequestBuilder = client.admin().indices().prepareCreate(INDEX);
-        Settings indexSettings = Settings.settingsBuilder()
-                .put("number_of_shards", 2)
-                .put("number_of_replicas", 1)
-                //.put("analyzer", createDefaultAnalyzer())
-                .build();
-        createIndexRequestBuilder.setSettings(indexSettings);
-        createIndexRequestBuilder.addMapping(RECORD_CATEGORY_TYPE, createRecordCategoryType());
-        createIndexRequestBuilder.addMapping(RECORD_TYPE, createRecordType());
-        createIndexRequestBuilder.addMapping(RECORD_COMMENT_TYPE, commentService.createRecordCommentType(INDEX, RECORD_COMMENT_TYPE));
-        createIndexRequestBuilder.execute().actionGet();
-
+    public MarketService deleteIndex() {
+        indexDao.deleteIndex();
         return this;
     }
 
-    /**
-     *
-     * @param jsonCategory
-     * @return the product id
-     */
-    public String indexCategoryFromJson(String jsonCategory) {
-        if (logger.isDebugEnabled()) {
-            logger.debug("Indexing a category");
-        }
 
-        // Preparing indexBlocksFromNode
-        IndexRequestBuilder indexRequest = client.prepareIndex(INDEX, RECORD_CATEGORY_TYPE)
-                .setSource(jsonCategory);
+    public String indexRecordFromJson(String json) {
+        JsonNode actualObj = readAndVerifyIssuerSignature(json);
+        String issuer = getIssuer(actualObj);
 
-        // Execute indexBlocksFromNode
-        IndexResponse response = indexRequest
-                .setRefresh(true)
-                .execute().actionGet();
+        if (logger.isDebugEnabled()) {
+            logger.debug(String.format("Indexing a %s from issuer [%s]", recordDao.getType(), issuer.substring(0, 8)));
+        }
 
-        return response.getId();
-    }
-
-    public String indexRecordFromJson(String json) {
-        return checkIssuerAndIndexDocumentFromJson(INDEX, RECORD_TYPE, json);
+        return recordDao.create(json);
     }
 
     public void updateRecordFromJson(String id, String json) {
-        checkIssuerAndUpdateDocumentFromJson(INDEX, RECORD_TYPE, id, json);
-    }
-
-    public String indexCommentFromJson(String json) {
-        return commentService.indexCommentFromJson(INDEX, RECORD_TYPE, RECORD_COMMENT_TYPE, json);
-    }
+        JsonNode actualObj = readAndVerifyIssuerSignature(json);
+        String issuer = getIssuer(actualObj);
 
-    public void updateCommentFromJson(String id, String json) {
-        commentService.updateCommentFromJson(INDEX, RECORD_TYPE, RECORD_COMMENT_TYPE, id, json);
-    }
+        // Check same document issuer
+        recordDao.checkSameDocumentIssuer(id, issuer);
 
-    public MarketService fillRecordCategories() {
         if (logger.isDebugEnabled()) {
-            logger.debug(String.format("[%s/%s] Fill data", INDEX, RECORD_CATEGORY_TYPE));
+            logger.debug(String.format("Updating %s [%s] from issuer [%s]", recordDao.getType(), id, issuer.substring(0, 8)));
         }
 
-        // Insert categories
-        bulkFromClasspathFile(CATEGORIES_BULK_CLASSPATH_FILE, INDEX, RECORD_CATEGORY_TYPE,
-                // Add order attribute (auto incremented)
-                new AddSequenceAttributeHandler("order", "\\{.*\"name\".*\\}", 1));
-
-        return this;
+        recordDao.update(id, json);
     }
 
-    /* -- Internal methods -- */
-
-
-    public XContentBuilder createRecordCategoryType() {
-        try {
-            XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject(RECORD_CATEGORY_TYPE)
-                    .startObject("properties")
-
-                    // name
-                    .startObject("name")
-                    .field("type", "string")
-                    .endObject()
-
-                    // order
-                    .startObject("order")
-                    .field("type", "integer")
-                    .endObject()
-
-                    // description
-                    /*.startObject("description")
-                    .field("type", "string")
-                    .endObject()*/
-
-                    // parent
-                    .startObject("parent")
-                    .field("type", "string")
-                    .endObject()
-
-                    // tags
-                    /*.startObject("tags")
-                    .field("type", "completion")
-                    .field("search_analyzer", "simple")
-                    .field("analyzer", "simple")
-                    .field("preserve_separators", "false")
-                    .endObject()*/
+    public String indexCommentFromJson(String json) {
+        JsonNode commentObj = readAndVerifyIssuerSignature(json);
+        String issuer = getMandatoryField(commentObj, RecordComment.PROPERTY_ISSUER).asText();
 
-                    .endObject()
-                    .endObject().endObject();
+        // Check the record document exists
+        String recordId = getMandatoryField(commentObj, RecordComment.PROPERTY_RECORD).asText();
+        checkRecordExistsOrDeleted(recordId);
 
-            return mapping;
-        }
-        catch(IOException ioe) {
-            throw new TechnicalException(String.format("Error while getting mapping for index [%s/%s]: %s", INDEX, RECORD_CATEGORY_TYPE, ioe.getMessage()), ioe);
+        if (logger.isDebugEnabled()) {
+            logger.debug(String.format("Indexing a %s from issuer [%s]", commentDao.getType(), issuer.substring(0, 8)));
         }
+        return commentDao.create(json);
     }
 
-    public XContentBuilder createRecordType() {
-        String stringAnalyzer = pluginSettings.getDefaultStringAnalyzer();
-
-        try {
-            XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject(RECORD_TYPE)
-                    .startObject("properties")
-
-                    // title
-                    .startObject("title")
-                    .field("type", "string")
-                    .field("analyzer", stringAnalyzer)
-                    .endObject()
-
-                    // description
-                    .startObject("description")
-                    .field("type", "string")
-                    .field("analyzer", stringAnalyzer)
-                    .endObject()
-
-                    // creationTime
-                    .startObject("creationTime")
-                    .field("type", "integer")
-                    .endObject()
-
-                    // time
-                    .startObject("time")
-                    .field("type", "integer")
-                    .endObject()
-
-                    // price
-                    .startObject("price")
-                    .field("type", "double")
-                    .endObject()
-
-                    // price Unit
-                    .startObject("unit")
-                    .field("type", "string")
-                    .field("index", "not_analyzed")
-                    .endObject()
-
-                    // currency
-                    .startObject("currency")
-                    .field("type", "string")
-                    .field("index", "not_analyzed")
-                    .endObject()
-
-                    // issuer
-                    .startObject("issuer")
-                    .field("type", "string")
-                    .field("index", "not_analyzed")
-                    .endObject()
-
-                    // type (offer, need, ...)
-                    .startObject("type")
-                    .field("type", "string")
-                    .field("index", "not_analyzed")
-                    .endObject()
-
-                    // location
-                    .startObject("location")
-                    .field("type", "string")
-                    .field("analyzer", stringAnalyzer)
-                    .endObject()
-
-                    // geoPoint
-                    .startObject("geoPoint")
-                    .field("type", "geo_point")
-                    .endObject()
-
-                    // thumbnail
-                    .startObject("thumbnail")
-                    .field("type", "attachment")
-                        .startObject("fields") // src
-                        .startObject("content") // title
-                            .field("index", "no")
-                        .endObject()
-                        .startObject("title") // title
-                            .field("type", "string")
-                            .field("store", "no")
-                        .endObject()
-                        .startObject("author") // title
-                            .field("store", "no")
-                        .endObject()
-                        .startObject("content_type") // title
-                            .field("store", "yes")
-                        .endObject()
-                    .endObject()
-                    .endObject()
+    public void updateCommentFromJson(String id, String json) {
+        JsonNode commentObj = readAndVerifyIssuerSignature(json);
 
-                    // pictures
-                    .startObject("pictures")
-                    .field("type", "nested")
-                    .field("dynamic", "false")
-                        .startObject("properties")
-                            .startObject("file") // file
-                                .field("type", "attachment")
-                                .startObject("fields")
-                                    .startObject("content") // content
-                                        .field("index", "no")
-                                    .endObject()
-                                    .startObject("title") // title
-                                        .field("type", "string")
-                                        .field("store", "yes")
-                                        .field("analyzer", stringAnalyzer)
-                                    .endObject()
-                                    .startObject("author") // author
-                                        .field("type", "string")
-                                        .field("store", "no")
-                                    .endObject()
-                                    .startObject("content_type") // content_type
-                                        .field("store", "yes")
-                                    .endObject()
-                                .endObject()
-                            .endObject()
-                        .endObject()
-                    .endObject()
+        // Check the record document exists
+        String recordId = getMandatoryField(commentObj, RecordComment.PROPERTY_RECORD).asText();
+        checkRecordExistsOrDeleted(recordId);
 
-                    // picturesCount
-                    .startObject("picturesCount")
-                    .field("type", "integer")
-                    .endObject()
+        if (logger.isDebugEnabled()) {
+            String issuer = getMandatoryField(commentObj, RecordComment.PROPERTY_ISSUER).asText();
+            logger.debug(String.format("[%s] Indexing a %s from issuer [%s] on [%s]", commentDao.getType(), commentDao.getType(), issuer.substring(0, 8)));
+        }
 
-                    // category
-                    .startObject("category")
-                    .field("type", "nested")
-                    .field("dynamic", "false")
-                    .startObject("properties")
-                    .startObject("id") // author
-                    .field("type", "string")
-                    .field("index", "not_analyzed")
-                    .endObject()
-                    .startObject("parent") // author
-                    .field("type", "string")
-                    .field("index", "not_analyzed")
-                    .endObject()
-                    .startObject("name") // author
-                    .field("type", "string")
-                    .field("analyzer", stringAnalyzer)
-                    .endObject()
-                    .endObject()
-                    .endObject()
+        commentDao.update(id, json);
+    }
 
-                    // tags
-                    .startObject("tags")
-                    .field("type", "completion")
-                    .field("search_analyzer", "simple")
-                    .field("analyzer", "simple")
-                    .field("preserve_separators", "false")
-                    .endObject()
 
-                    .endObject()
-                    .endObject().endObject();
+    /* -- Internal methods -- */
 
-            return mapping;
+    // Check the record document exists (or has been deleted)
+    private void checkRecordExistsOrDeleted(String id) {
+        boolean recordExists;
+        try {
+            recordExists = recordDao.isExists(id);
+        } catch (NotFoundException e) {
+            // Check if exists in delete history
+            recordExists = historyService.existsInDeleteHistory(recordDao.getIndex(), recordDao.getType(), id);
         }
-        catch(IOException ioe) {
-            throw new TechnicalException(String.format("Error while getting mapping for index [%s/%s]: %s", INDEX, RECORD_TYPE, ioe.getMessage()), ioe);
+        if (!recordExists) {
+            throw new NotFoundException(String.format("Comment refers a non-existent document [%s/%s/%s].", recordDao.getIndex(), recordDao.getType(), id));
         }
     }
-
 }
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/RegistryService.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/RegistryService.java
index b090936bb9a7f7d192b686997d28c2a09679dbea..ca51cb87372a987f264b201909c9c76dfdb9dd93 100644
--- a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/RegistryService.java
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/RegistryService.java
@@ -23,334 +23,125 @@ package org.duniter.elasticsearch.gchange.service;
  */
 
 
-import com.fasterxml.jackson.core.JsonProcessingException;
-import org.duniter.core.exception.TechnicalException;
+import com.fasterxml.jackson.databind.JsonNode;
+import org.duniter.core.client.model.elasticsearch.RecordComment;
 import org.duniter.core.service.CryptoService;
+import org.duniter.elasticsearch.client.Duniter4jClient;
+import org.duniter.elasticsearch.exception.NotFoundException;
 import org.duniter.elasticsearch.gchange.PluginSettings;
-import org.duniter.elasticsearch.gchange.service.AbstractService;
-import org.duniter.elasticsearch.user.service.UserEventService;
-import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
-import org.elasticsearch.action.index.IndexRequestBuilder;
-import org.elasticsearch.action.index.IndexResponse;
-import org.elasticsearch.client.Client;
+import org.duniter.elasticsearch.gchange.dao.registry.RegistryCommentDao;
+import org.duniter.elasticsearch.gchange.dao.registry.RegistryIndexDao;
+import org.duniter.elasticsearch.gchange.dao.registry.RegistryRecordDao;
+import org.duniter.elasticsearch.user.service.HistoryService;
 import org.elasticsearch.common.inject.Inject;
-import org.elasticsearch.common.xcontent.XContentBuilder;
-import org.elasticsearch.common.xcontent.XContentFactory;
-
-import java.io.IOException;
 
 /**
  * Created by Benoit on 30/03/2015.
  */
 public class RegistryService extends AbstractService {
 
-    public static final String INDEX = "registry";
-    public static final String RECORD_TYPE = "record";
-    public static final String RECORD_CATEGORY_TYPE = "category";
-    public static final String RECORD_COMMENT_TYPE = "comment";
-    private static final String CATEGORIES_BULK_CLASSPATH_FILE = "registry-categories-bulk-insert.json";
-
-    private CommentService commentService;
-    private UserEventService userEventService;
+    private RegistryIndexDao indexDao;
+    private RegistryRecordDao recordDao;
+    private RegistryCommentDao commentDao;
+    private HistoryService historyService;
 
     @Inject
-    public RegistryService(Client client,
+    public RegistryService(Duniter4jClient client,
                            PluginSettings settings,
                            CryptoService cryptoService,
-                           CommentService commentService,
-                            UserEventService userEventService) {
-        super("gchange." + INDEX, client, settings, cryptoService);
-        this.commentService = commentService;
-        this.userEventService = userEventService;
+                           HistoryService historyService,
+                           RegistryIndexDao registryIndexDao,
+                           RegistryCommentDao commentDao,
+                           RegistryRecordDao recordDao) {
+        super("gchange.service.registry", client, settings, cryptoService);
+        this.indexDao = registryIndexDao;
+        this.commentDao = commentDao;
+        this.recordDao = recordDao;
+        this.historyService = historyService;
     }
 
     /**
      * Create index need for blockchain registry, if need
      */
     public RegistryService createIndexIfNotExists() {
-        try {
-            if (!existsIndex(INDEX)) {
-                createIndex();
-
-                fillRecordCategories();
-            }
-        }
-        catch(JsonProcessingException e) {
-            throw new TechnicalException(String.format("Error while creating index [%s]", INDEX));
-        }
-        return this;
-    }
-
-    /**
-     * Create index for registry
-     * @throws JsonProcessingException
-     */
-    public RegistryService createIndex() throws JsonProcessingException {
-        logger.info(String.format("Creating index [%s]", INDEX));
-
-        CreateIndexRequestBuilder createIndexRequestBuilder = client.admin().indices().prepareCreate(INDEX);
-        org.elasticsearch.common.settings.Settings indexSettings = org.elasticsearch.common.settings.Settings.settingsBuilder()
-                .put("number_of_shards", 3)
-                .put("number_of_replicas", 1)
-                //.put("analyzer", createDefaultAnalyzer())
-                .build();
-        createIndexRequestBuilder.setSettings(indexSettings);
-        createIndexRequestBuilder.addMapping(RECORD_CATEGORY_TYPE, createRecordCategoryType());
-        createIndexRequestBuilder.addMapping(RECORD_TYPE, createRecordType());
-        createIndexRequestBuilder.addMapping(RECORD_COMMENT_TYPE, commentService.createRecordCommentType(INDEX, RECORD_COMMENT_TYPE));
-        createIndexRequestBuilder.execute().actionGet();
-
+        indexDao.createIndexIfNotExists();
         return this;
     }
 
     public RegistryService deleteIndex() {
-        deleteIndexIfExists(INDEX);
+        indexDao.deleteIndex();
         return this;
     }
 
-    public boolean existsIndex() {
-        return super.existsIndex(INDEX);
-    }
+    public String indexRecordFromJson(String json) {
+        JsonNode actualObj = readAndVerifyIssuerSignature(json);
+        String issuer = getIssuer(actualObj);
 
-    public RegistryService fillRecordCategories() {
         if (logger.isDebugEnabled()) {
-            logger.debug(String.format("[%s/%s] Fill data", INDEX, RECORD_CATEGORY_TYPE));
+            logger.debug(String.format("Indexing a %s from issuer [%s]", recordDao.getType(), issuer.substring(0, 8)));
         }
 
-        // Insert categories
-        bulkFromClasspathFile(CATEGORIES_BULK_CLASSPATH_FILE, INDEX, RECORD_CATEGORY_TYPE,
-                // Add order attribute
-                new AddSequenceAttributeHandler("order", "\\{.*\"name\".*\\}", 1));
-
-        return this;
-    }
-
-    public String indexRecordFromJson(String json) {
-        return checkIssuerAndIndexDocumentFromJson(INDEX, RECORD_TYPE, json);
+        return recordDao.create(json);
     }
 
     public void updateRecordFromJson(String id, String json) {
-        checkIssuerAndUpdateDocumentFromJson(INDEX, RECORD_TYPE, id, json);
-    }
-
-    public String indexCommentFromJson(String json) {
-        return commentService.indexCommentFromJson(INDEX, RECORD_TYPE, RECORD_COMMENT_TYPE, json);
-    }
-
-    public void updateCommentFromJson(String id, String json) {
-        commentService.updateCommentFromJson(INDEX, RECORD_TYPE, RECORD_COMMENT_TYPE, id, json);
-    }
-
-    /* -- Internal methods -- */
-
-    public XContentBuilder createRecordType() {
-        String stringAnalyzer = pluginSettings.getDefaultStringAnalyzer();
-
-        try {
-            XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject(RECORD_TYPE)
-                    .startObject("properties")
+        JsonNode actualObj = readAndVerifyIssuerSignature(json);
+        String issuer = getIssuer(actualObj);
 
-                    // title
-                    .startObject("title")
-                    .field("type", "string")
-                    .field("analyzer", stringAnalyzer)
-                    .endObject()
+        // Check same document issuer
+        recordDao.checkSameDocumentIssuer(id, issuer);
 
-                    // description
-                    .startObject("description")
-                    .field("type", "string")
-                    .field("analyzer", stringAnalyzer)
-                    .endObject()
-
-                    // creationTime
-                    .startObject("creationTime")
-                    .field("type", "integer")
-                    .endObject()
-
-                    // time
-                    .startObject("time")
-                    .field("type", "integer")
-                    .endObject()
-
-                    // issuer
-                    .startObject("issuer")
-                    .field("type", "string")
-                    .field("index", "not_analyzed")
-                    .endObject()
-
-                    // pubkey
-                    .startObject("pubkey")
-                    .field("type", "string")
-                    .field("index", "not_analyzed")
-                    .endObject()
-
-                    // address
-                    .startObject("address")
-                    .field("type", "string")
-                    .field("analyzer", stringAnalyzer)
-                    .endObject()
-
-                    // city
-                    .startObject("city")
-                    .field("type", "string")
-                    .endObject()
-
-                    // geoPoint
-                    .startObject("geoPoint")
-                    .field("type", "geo_point")
-                    .endObject()
-
-                    // thumbnail
-                    .startObject("thumbnail")
-                    .field("type", "attachment")
-                    .startObject("fields") // src
-                    .startObject("content") // title
-                    .field("index", "no")
-                    .endObject()
-                    .startObject("title") // title
-                    .field("type", "string")
-                    .field("store", "no")
-                    .endObject()
-                    .startObject("author") // title
-                    .field("store", "no")
-                    .endObject()
-                    .startObject("content_type") // title
-                    .field("store", "yes")
-                    .endObject()
-                    .endObject()
-                    .endObject()
-
-                    // pictures
-                    .startObject("pictures")
-                    .field("type", "nested")
-                    .field("dynamic", "false")
-                    .startObject("properties")
-                    .startObject("file") // file
-                    .field("type", "attachment")
-                    .startObject("fields")
-                    .startObject("content") // content
-                    .field("index", "no")
-                    .endObject()
-                    .startObject("title") // title
-                    .field("type", "string")
-                    .field("store", "yes")
-                    .field("analyzer", stringAnalyzer)
-                    .endObject()
-                    .startObject("author") // author
-                    .field("type", "string")
-                    .field("store", "no")
-                    .endObject()
-                    .startObject("content_type") // content_type
-                    .field("store", "yes")
-                    .endObject()
-                    .endObject()
-                    .endObject()
-                    .endObject()
-                    .endObject()
-
-                    // picturesCount
-                    .startObject("picturesCount")
-                    .field("type", "integer")
-                    .endObject()
+        if (logger.isDebugEnabled()) {
+            logger.debug(String.format("Updating %s [%s] from issuer [%s]", recordDao.getType(), id, issuer.substring(0, 8)));
+        }
 
-                    // category
-                    .startObject("category")
-                    .field("type", "nested")
-                    .field("dynamic", "false")
-                    .startObject("properties")
-                    .startObject("id") // id
-                    .field("type", "string")
-                    .field("index", "not_analyzed")
-                    .endObject()
-                    .startObject("parent") // parent
-                    .field("type", "string")
-                    .field("index", "not_analyzed")
-                    .endObject()
-                    .startObject("name") // name
-                    .field("type", "string")
-                    .field("analyzer", stringAnalyzer)
-                    .endObject()
-                    .endObject()
-                    .endObject()
+        recordDao.update(id, json);
+    }
 
-                    // tags
-                    .startObject("tags")
-                    .field("type", "completion")
-                    .field("search_analyzer", "simple")
-                    .field("analyzer", "simple")
-                    .field("preserve_separators", "false")
-                    .endObject()
+    public String indexCommentFromJson(String json) {
+        JsonNode commentObj = readAndVerifyIssuerSignature(json);
+        String issuer = getMandatoryField(commentObj, RecordComment.PROPERTY_ISSUER).asText();
 
-                    .endObject()
-                    .endObject().endObject();
+        // Check the record document exists
+        String recordId = getMandatoryField(commentObj, RecordComment.PROPERTY_RECORD).asText();
+        checkRecordExistsOrDeleted(recordId);
 
-            return mapping;
-        }
-        catch(IOException ioe) {
-            throw new TechnicalException(String.format("Error while getting mapping for index [%s/%s]: %s", INDEX, RECORD_TYPE, ioe.getMessage()), ioe);
+        if (logger.isDebugEnabled()) {
+            logger.debug(String.format("Indexing a %s from issuer [%s]", commentDao.getType(), issuer.substring(0, 8)));
         }
+        return commentDao.create(json);
     }
 
-    public XContentBuilder createRecordCategoryType() {
-        try {
-            XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject(RECORD_CATEGORY_TYPE)
-                    .startObject("properties")
+    public void updateCommentFromJson(String id, String json) {
+        JsonNode commentObj = readAndVerifyIssuerSignature(json);
 
-                    // name
-                    .startObject("name")
-                    .field("type", "string")
-                    .field("analyzer", pluginSettings.getDefaultStringAnalyzer())
-                    .endObject()
+        // Check the record document exists
+        String recordId = getMandatoryField(commentObj, RecordComment.PROPERTY_RECORD).asText();
+        checkRecordExistsOrDeleted(recordId);
 
-                    // description
-                    /*.startObject("description")
-                    .field("type", "string")
-                    .endObject()*/
+        if (logger.isDebugEnabled()) {
+            String issuer = getMandatoryField(commentObj, RecordComment.PROPERTY_ISSUER).asText();
+            logger.debug(String.format("[%s] Indexing a %s from issuer [%s] on [%s]", commentDao.getType(), commentDao.getType(), issuer.substring(0, 8)));
+        }
 
-                    // parent
-                    .startObject("parent")
-                    .field("type", "string")
-                    .field("index", "not_analyzed")
-                    .endObject()
+        commentDao.update(id, json);
+    }
 
-                    // tags
-                    /*.startObject("tags")
-                    .field("type", "completion")
-                    .field("search_analyzer", "simple")
-                    .field("analyzer", "simple")
-                    .field("preserve_separators", "false")
-                    .endObject()*/
 
-                    .endObject()
-                    .endObject().endObject();
+    /* -- Internal methods -- */
 
-            return mapping;
-        }
-        catch(IOException ioe) {
-            throw new TechnicalException(String.format("Error while getting mapping for index [%s/%s]: %s", INDEX, RECORD_CATEGORY_TYPE, ioe.getMessage()), ioe);
+    // Check the record document exists (or has been deleted)
+    private void checkRecordExistsOrDeleted(String id) {
+        boolean recordExists;
+        try {
+            recordExists = recordDao.isExists(id);
+        } catch (NotFoundException e) {
+            // Check if exists in delete history
+            recordExists = historyService.existsInDeleteHistory(recordDao.getIndex(), recordDao.getType(), id);
         }
-    }
-
-    /**
-     *
-     * @param jsonCategory
-     * @return the product id
-     */
-    public String indexCategoryFromJson(String jsonCategory) {
-        if (logger.isDebugEnabled()) {
-            logger.debug("Indexing a category");
+        if (!recordExists) {
+            throw new NotFoundException(String.format("Comment refers a non-existent document [%s/%s/%s].", recordDao.getIndex(), recordDao.getType(), id));
         }
-
-        // Preparing indexBlocksFromNode
-        IndexRequestBuilder indexRequest = client.prepareIndex(INDEX, RECORD_CATEGORY_TYPE)
-                .setSource(jsonCategory);
-
-        // Execute indexBlocksFromNode
-        IndexResponse response = indexRequest
-                .setRefresh(false)
-                .execute().actionGet();
-
-        return response.getId();
     }
 
 }
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/ServiceModule.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/ServiceModule.java
index ce56b692f84bd6bdd3e5fca64dcbffcff166ca5b..a3d5f3d32290d7d4d21cc65003dceb5772fb27c7 100644
--- a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/ServiceModule.java
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/ServiceModule.java
@@ -29,8 +29,6 @@ public class ServiceModule extends AbstractModule implements Module {
 
     @Override protected void configure() {
         bind(RegistryService.class).asEagerSingleton();
-        bind(CitiesRegistryService.class).asEagerSingleton();
-        bind(CommentService.class).asEagerSingleton();
         bind(CommentUserEventService.class).asEagerSingleton();
         bind(MarketService.class).asEagerSingleton();
         bind(SynchroService.class).asEagerSingleton();
diff --git a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/SynchroService.java b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/SynchroService.java
index 6498406dd5ecf8027e8f7afdfe31b857f43ec67e..977f23303b90c82c3565935d680579c1392b13a8 100644
--- a/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/SynchroService.java
+++ b/duniter4j-es-gchange/src/main/java/org/duniter/elasticsearch/gchange/service/SynchroService.java
@@ -24,6 +24,13 @@ package org.duniter.elasticsearch.gchange.service;
 
 import org.duniter.core.client.model.local.Peer;
 import org.duniter.core.service.CryptoService;
+import org.duniter.elasticsearch.client.Duniter4jClient;
+import org.duniter.elasticsearch.gchange.dao.market.MarketCommentDao;
+import org.duniter.elasticsearch.gchange.dao.market.MarketIndexDao;
+import org.duniter.elasticsearch.gchange.dao.market.MarketRecordDao;
+import org.duniter.elasticsearch.gchange.dao.registry.RegistryCommentDao;
+import org.duniter.elasticsearch.gchange.dao.registry.RegistryIndexDao;
+import org.duniter.elasticsearch.gchange.dao.registry.RegistryRecordDao;
 import org.duniter.elasticsearch.gchange.model.Protocol;
 import org.duniter.elasticsearch.model.SynchroResult;
 import org.duniter.elasticsearch.service.AbstractSynchroService;
@@ -39,7 +46,7 @@ import org.elasticsearch.common.inject.Inject;
 public class SynchroService extends AbstractSynchroService {
 
     @Inject
-    public SynchroService(Client client, PluginSettings settings, CryptoService cryptoService,
+    public SynchroService(Duniter4jClient client, PluginSettings settings, CryptoService cryptoService,
                           ThreadPool threadPool, final ServiceLocator serviceLocator) {
         super(client, settings.getDelegate(), cryptoService, threadPool, serviceLocator);
     }
@@ -69,12 +76,12 @@ public class SynchroService extends AbstractSynchroService {
     }
 
     protected void importMarketChanges(SynchroResult result, Peer peer, long sinceTime) {
-        importChanges(result, peer, MarketService.INDEX, MarketService.RECORD_TYPE,  sinceTime);
-        importChanges(result, peer, MarketService.INDEX, MarketService.RECORD_COMMENT_TYPE,  sinceTime);
+        importChanges(result, peer, MarketIndexDao.INDEX, MarketRecordDao.TYPE,  sinceTime);
+        importChanges(result, peer, MarketIndexDao.INDEX, MarketCommentDao.TYPE,  sinceTime);
     }
 
     protected void importRegistryChanges(SynchroResult result, Peer peer, long sinceTime) {
-        importChanges(result, peer, RegistryService.INDEX, RegistryService.RECORD_TYPE,  sinceTime);
-        importChanges(result, peer, RegistryService.INDEX, RegistryService.RECORD_COMMENT_TYPE,  sinceTime);
+        importChanges(result, peer, RegistryIndexDao.INDEX, RegistryRecordDao.TYPE,  sinceTime);
+        importChanges(result, peer, RegistryIndexDao.INDEX, RegistryCommentDao.TYPE,  sinceTime);
     }
 }
diff --git a/duniter4j-es-gchange/src/test/java/org/duniter/elasticsearch/service/BlockchainServiceTest.java b/duniter4j-es-gchange/src/test/java/org/duniter/elasticsearch/service/BlockchainServiceTest.java
deleted file mode 100644
index a305ac1b40ff0fca3ed27b843d7c737d1f117d50..0000000000000000000000000000000000000000
--- a/duniter4j-es-gchange/src/test/java/org/duniter/elasticsearch/service/BlockchainServiceTest.java
+++ /dev/null
@@ -1,172 +0,0 @@
-package org.duniter.elasticsearch.service;
-
-/*
- * #%L
- * UCoin Java Client :: Core API
- * %%
- * Copyright (C) 2014 - 2015 EIS
- * %%
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU 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 General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public 
- * License along with this program.  If not, see
- * <http://www.gnu.org/licenses/gpl-3.0.html>.
- * #L%
- */
-
-
-import org.duniter.core.client.config.Configuration;
-import org.duniter.core.client.model.bma.BlockchainBlock;
-import org.duniter.core.client.model.local.Peer;
-import org.duniter.core.client.service.bma.BlockchainRemoteService;
-import org.duniter.elasticsearch.TestResource;
-import org.junit.*;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.List;
-
-@Ignore
-public class BlockchainServiceTest {
-
-	private static final Logger log = LoggerFactory.getLogger(BlockchainServiceTest.class);
-
-	@ClassRule
-	public static final TestResource resource = TestResource.create();
-
-    private BlockchainService service;
-    private BlockchainRemoteService blockchainRemoteService;
-    private Configuration config;
-    private Peer peer;
-
-    @Before
-    public void setUp() throws Exception {
-        //service = ServiceLocator.instance().getBlockIndexerService();
-        //blockchainRemoteService = ServiceLocator.instance().getBlockchainRemoteService();
-        config = Configuration.instance();
-        peer = createTestPeer();
-
-        initLocalNode();
-    }
-
-    @Test
-    public void createIndex() throws Exception {
-        String currencyName = resource.getFixtures().getCurrency();
-
-        // drop and recreate index
-        service.deleteIndex(currencyName);
-
-        service.createIndex(currencyName);
-    }
-
-
-    @Test
-	public void indexBlock() throws Exception {
-        // Read a block
-        BlockchainBlock currentBlock = blockchainRemoteService.getCurrentBlock(peer);
-
-        // Create a new non-existing block
-        service.indexBlock(currentBlock, true);
-
-        // Update a existing block
-        {
-            currentBlock.setMembersCount(1000000);
-
-            service.indexBlock(currentBlock, true);
-        }
-	}
-
-    @Test
-    public void indexCurrentBlock() throws Exception {
-        // Create a block with a fake hash
-        BlockchainBlock aBlock = blockchainRemoteService.getBlock(peer, 8450);
-        service.indexCurrentBlock(aBlock, true);
-    }
-
-    @Test
-    // FIXME make this works
-    @Ignore
-    public void searchBlocks() throws Exception {
-        String currencyName = resource.getFixtures().getCurrency();
-
-        // Create a block with a fake hash
-        BlockchainBlock aBlock = blockchainRemoteService.getCurrentBlock(peer);
-        aBlock.setHash("myUnitTestHash");
-        service.saveBlock(aBlock, true, true);
-
-        Thread.sleep(5 * 1000); // wait 5s that ES process the block
-
-        // match multi words
-        String queryText = aBlock.getHash();
-        List<BlockchainBlock> blocks = service.findBlocksByHash(currencyName, queryText);
-        //assertResults(queryText, blocks);
-
-        Thread.sleep(5 * 1000); // wait 5s that ES process the block
-
-        BlockchainBlock loadBlock = service.getBlockById(currencyName, aBlock.getNumber());
-        Assert.assertNotNull(loadBlock);
-        Assert.assertEquals(aBlock.getHash(), loadBlock.getHash());
-    }
-
-    @Test
-    public void getMaxBlockNumber() throws Exception {
-        String currencyName = resource.getFixtures().getCurrency();
-
-        // match multi words
-        Integer maxBlockNumber = service.getMaxBlockNumber(currencyName);
-        Assert.assertNotNull(maxBlockNumber);
-    }
-
-
-    @Test
-    @Ignore
-    public void allInOne() throws Exception {
-
-        createIndex();
-        indexBlock();
-        searchBlocks();
-    }
-
-	/* -- internal methods */
-
-    protected void initLocalNode() throws Exception {
-        String currencyName = resource.getFixtures().getCurrency();
-
-        // Make sure the index exists
-        service.deleteIndex(currencyName);
-        service.createIndex(currencyName);
-
-        // Get the first block from peer
-        BlockchainBlock firstBlock = blockchainRemoteService.getBlock(peer, 0);
-
-        // Make sure the block has been indexed
-        service.indexBlock(firstBlock, true);
-
-    }
-
-    protected void assertResults(String queryText, List<BlockchainBlock> result) {
-        log.info(String.format("Results for a search on [%s]", queryText));
-        Assert.assertNotNull(result);
-        Assert.assertTrue(result.size() > 0);
-        for (BlockchainBlock block: result) {
-            log.info("  - " + block.getNumber());
-        }
-    }
-
-    protected Peer createTestPeer() {
-        Peer peer = new Peer(
-                Configuration.instance().getNodeHost(),
-                Configuration.instance().getNodePort());
-
-        return peer;
-    }
-
-}
diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/PluginInit.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/PluginInit.java
index c827707dc7a89585f5f7722b9752688386a1899c..d06b1f759ca28f5ad87282e82ba066d9cc50e1c6 100644
--- a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/PluginInit.java
+++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/PluginInit.java
@@ -44,7 +44,7 @@ public class PluginInit extends AbstractLifecycleComponent<PluginInit> {
     private final PluginSettings pluginSettings;
     private final ThreadPool threadPool;
     private final Injector injector;
-    private final static ESLogger logger = Loggers.getLogger("node");
+    private final static ESLogger logger = Loggers.getLogger("duniter.user");
     private final String clusterName;
 
     @Inject
@@ -111,12 +111,12 @@ public class PluginInit extends AbstractLifecycleComponent<PluginInit> {
                     .createIndexIfNotExists();
 
             if (logger.isInfoEnabled()) {
-                logger.info("Reloading all Duniter indices... [OK]");
+                logger.info("Reloading all Duniter User indices... [OK]");
             }
         }
         else {
             if (logger.isInfoEnabled()) {
-                logger.info("Checking Duniter indices...");
+                logger.info("Checking Duniter User indices...");
             }
             injector.getInstance(HistoryService.class).createIndexIfNotExists();
             injector.getInstance(UserService.class).createIndexIfNotExists();
@@ -125,7 +125,7 @@ public class PluginInit extends AbstractLifecycleComponent<PluginInit> {
             injector.getInstance(UserInvitationService.class).createIndexIfNotExists();
 
             if (logger.isInfoEnabled()) {
-                logger.info("Checking Duniter indices... [OK]");
+                logger.info("Checking Duniter User indices... [OK]");
             }
         }
     }
diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/AbstractService.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/AbstractService.java
index 58555b9de9f77c11ee135d7dc719923aff64b804..4ae645da796d716430e6c64057bdb6ac3b2a29f0 100644
--- a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/AbstractService.java
+++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/AbstractService.java
@@ -23,8 +23,8 @@ package org.duniter.elasticsearch.user.service;
  */
 
 import org.duniter.core.service.CryptoService;
+import org.duniter.elasticsearch.client.Duniter4jClient;
 import org.duniter.elasticsearch.user.PluginSettings;
-import org.elasticsearch.client.Client;
 
 /**
  * Created by blavenie on 10/01/17.
@@ -33,19 +33,19 @@ public abstract class AbstractService extends org.duniter.elasticsearch.service.
 
     protected PluginSettings pluginSettings;
 
-    public AbstractService(String loggerName, Client client, PluginSettings pluginSettings) {
+    public AbstractService(String loggerName, Duniter4jClient client, PluginSettings pluginSettings) {
         this(loggerName, client, pluginSettings, null);
     }
 
-    public AbstractService(Client client, PluginSettings pluginSettings) {
+    public AbstractService(Duniter4jClient client, PluginSettings pluginSettings) {
         this(client, pluginSettings, null);
     }
 
-    public AbstractService(Client client, PluginSettings pluginSettings, CryptoService cryptoService) {
+    public AbstractService(Duniter4jClient client, PluginSettings pluginSettings, CryptoService cryptoService) {
         this("duniter.user", client, pluginSettings, cryptoService);
     }
 
-    public AbstractService(String loggerName, Client client, PluginSettings pluginSettings, CryptoService cryptoService) {
+    public AbstractService(String loggerName, Duniter4jClient client, PluginSettings pluginSettings, CryptoService cryptoService) {
         super(loggerName, client, pluginSettings.getDelegate(), cryptoService);
         this.pluginSettings = pluginSettings;
     }
diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/BlockchainUserEventService.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/BlockchainUserEventService.java
index 8420fe9dbc5900e44a5419a56809ccd662896acf..2c1a3ccf526deb508f5062490739143a1be595b5 100644
--- a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/BlockchainUserEventService.java
+++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/BlockchainUserEventService.java
@@ -33,6 +33,7 @@ import org.duniter.core.exception.TechnicalException;
 import org.duniter.core.service.CryptoService;
 import org.duniter.core.util.CollectionUtils;
 import org.duniter.core.util.websocket.WebsocketClientEndpoint;
+import org.duniter.elasticsearch.client.Duniter4jClient;
 import org.duniter.elasticsearch.service.BlockchainService;
 import org.duniter.elasticsearch.service.changes.ChangeEvent;
 import org.duniter.elasticsearch.service.changes.ChangeService;
@@ -40,7 +41,6 @@ import org.duniter.elasticsearch.service.changes.ChangeSource;
 import org.duniter.elasticsearch.user.PluginSettings;
 import org.duniter.elasticsearch.user.model.UserEvent;
 import org.duniter.elasticsearch.user.model.UserEventCodes;
-import org.elasticsearch.client.Client;
 import org.elasticsearch.common.inject.Inject;
 import org.nuiton.i18n.I18n;
 
@@ -69,7 +69,7 @@ public class BlockchainUserEventService extends AbstractService implements Chang
     public final boolean enable;
 
     @Inject
-    public BlockchainUserEventService(Client client, PluginSettings settings, CryptoService cryptoService,
+    public BlockchainUserEventService(Duniter4jClient client, PluginSettings settings, CryptoService cryptoService,
                                       BlockchainService blockchainService,
                                       UserService userService,
                                       UserEventService userEventService) {
diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/GroupService.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/GroupService.java
index 8a06c09d9893234844ed4cff6456e32c1f851330..51e6a05802f55746fd1d6de6e3253b21fa2344fd 100644
--- a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/GroupService.java
+++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/GroupService.java
@@ -29,6 +29,7 @@ import org.apache.commons.collections4.MapUtils;
 import org.duniter.core.client.model.elasticsearch.UserGroup;
 import org.duniter.core.exception.TechnicalException;
 import org.duniter.core.service.CryptoService;
+import org.duniter.elasticsearch.client.Duniter4jClient;
 import org.duniter.elasticsearch.exception.AccessDeniedException;
 import org.duniter.elasticsearch.user.service.AbstractService;
 import org.duniter.elasticsearch.user.PluginSettings;
@@ -56,7 +57,7 @@ public class GroupService extends AbstractService {
     public static final String RECORD_TYPE = "record";
 
     @Inject
-    public GroupService(Client client,
+    public GroupService(Duniter4jClient client,
                         PluginSettings settings,
                         CryptoService cryptoService) {
         super("duniter." + INDEX, client, settings, cryptoService);
@@ -67,7 +68,7 @@ public class GroupService extends AbstractService {
      */
     public GroupService createIndexIfNotExists() {
         try {
-            if (!existsIndex(INDEX)) {
+            if (!client.existsIndex(INDEX)) {
                 createIndex();
             }
         }
@@ -98,12 +99,12 @@ public class GroupService extends AbstractService {
     }
 
     public GroupService deleteIndex() {
-        deleteIndexIfExists(INDEX);
+        client.deleteIndexIfExists(INDEX);
         return this;
     }
 
     public boolean existsIndex() {
-        return super.existsIndex(INDEX);
+        return client.existsIndex(INDEX);
     }
 
     /**
@@ -150,17 +151,17 @@ public class GroupService extends AbstractService {
 
     public String getTitleById(String id) {
 
-        Object title = getFieldById(INDEX, RECORD_TYPE, id, UserGroup.PROPERTY_TITLE);
+        Object title = client.getFieldById(INDEX, RECORD_TYPE, id, UserGroup.PROPERTY_TITLE);
         if (title == null) return null;
         return title.toString();
     }
 
     public Map<String, String> getTitlesByNames(Set<String> ids) {
 
-        Map<String, Object> titles = getFieldByIds(INDEX, RECORD_TYPE, ids, UserGroup.PROPERTY_TITLE);
+        Map<String, Object> titles = client.getFieldByIds(INDEX, RECORD_TYPE, ids, UserGroup.PROPERTY_TITLE);
         if (MapUtils.isEmpty(titles)) return null;
         Map<String, String> result = new HashMap<>();
-        titles.entrySet().stream().forEach((entry) -> result.put(entry.getKey(), entry.getValue().toString()));
+        titles.entrySet().forEach((entry) -> result.put(entry.getKey(), entry.getValue().toString()));
         return result;
     }
 
@@ -183,7 +184,7 @@ public class GroupService extends AbstractService {
             id += "_" + counter;
         }
 
-        if (!isDocumentExists(INDEX, RECORD_TYPE, id)) {
+        if (!client.isDocumentExists(INDEX, RECORD_TYPE, id)) {
             return id;
         }
 
diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/HistoryService.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/HistoryService.java
index 8cfb537bb19a119287c8be8f30649e4248205a5f..7df495965f4ff5270db75af9aa67eca70672cfa9 100644
--- a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/HistoryService.java
+++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/HistoryService.java
@@ -29,6 +29,7 @@ import org.duniter.core.client.model.elasticsearch.DeleteRecord;
 import org.duniter.core.client.model.elasticsearch.MessageRecord;
 import org.duniter.core.exception.TechnicalException;
 import org.duniter.core.service.CryptoService;
+import org.duniter.elasticsearch.client.Duniter4jClient;
 import org.duniter.elasticsearch.exception.NotFoundException;
 import org.duniter.elasticsearch.user.service.AbstractService;
 import org.duniter.elasticsearch.user.PluginSettings;
@@ -57,7 +58,7 @@ public class HistoryService extends AbstractService {
     public static final String DELETE_TYPE = "delete";
 
     @Inject
-    public HistoryService(Client client, PluginSettings settings, CryptoService cryptoService) {
+    public HistoryService(Duniter4jClient client, PluginSettings settings, CryptoService cryptoService) {
         super("gchange." + INDEX, client, settings, cryptoService);
     }
 
@@ -66,13 +67,13 @@ public class HistoryService extends AbstractService {
      * @throws JsonProcessingException
      */
     public HistoryService deleteIndex() {
-        deleteIndexIfExists(INDEX);
+        client.deleteIndexIfExists(INDEX);
         return this;
     }
 
 
     public boolean existsIndex() {
-        return super.existsIndex(INDEX);
+        return client.existsIndex(INDEX);
     }
 
     /**
@@ -80,7 +81,7 @@ public class HistoryService extends AbstractService {
      */
     public HistoryService createIndexIfNotExists() {
         try {
-            if (!existsIndex(INDEX)) {
+            if (!client.existsIndex(INDEX)) {
                 createIndex();
             }
         }
@@ -120,21 +121,21 @@ public class HistoryService extends AbstractService {
         String type = actualObj.get(DeleteRecord.PROPERTY_TYPE).asText();
         String id = actualObj.get(DeleteRecord.PROPERTY_ID).asText();
 
-        if (!existsIndex(index)) {
+        if (!client.existsIndex(index)) {
             throw new NotFoundException(String.format("Index [%s] not exists.", index));
         }
 
         // Special case for message: check if deletion issuer is the message recipient
         if (MessageService.INDEX.equals(index) && MessageService.INBOX_TYPE.equals(type)) {
-            checkSameDocumentField(index, type, id, MessageRecord.PROPERTY_RECIPIENT, issuer);
+            client.checkSameDocumentField(index, type, id, MessageRecord.PROPERTY_RECIPIENT, issuer);
         }
         // Special case for invitation: check if deletion issuer is the invitation recipient
         else if (UserInvitationService.INDEX.equals(index)) {
-            checkSameDocumentField(index, type, id, MessageRecord.PROPERTY_RECIPIENT, issuer);
+            client.checkSameDocumentField(index, type, id, MessageRecord.PROPERTY_RECIPIENT, issuer);
         }
         else {
             // Check document issuer
-            checkSameDocumentIssuer(index, type, id, issuer);
+            client.checkSameDocumentIssuer(index, type, id, issuer);
         }
 
         if (logger.isDebugEnabled()) {
@@ -177,7 +178,7 @@ public class HistoryService extends AbstractService {
     /* -- Internal methods -- */
 
 
-    public XContentBuilder createDeleteType() {
+    protected XContentBuilder createDeleteType() {
         try {
             XContentBuilder mapping = XContentFactory.jsonBuilder().startObject().startObject(DELETE_TYPE)
                     .startObject("properties")
diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/MessageService.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/MessageService.java
index 40bb0e1526ce8218c7d4b3b2295c77fcf2db4f5a..5a4a2a3835e61a0fd9fd06915c79023833da29dc 100644
--- a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/MessageService.java
+++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/MessageService.java
@@ -28,6 +28,7 @@ import com.fasterxml.jackson.databind.JsonNode;
 import org.duniter.core.client.model.ModelUtils;
 import org.duniter.core.exception.TechnicalException;
 import org.duniter.core.service.CryptoService;
+import org.duniter.elasticsearch.client.Duniter4jClient;
 import org.duniter.elasticsearch.user.PluginSettings;
 import org.duniter.elasticsearch.exception.InvalidSignatureException;
 import org.duniter.elasticsearch.user.service.AbstractService;
@@ -63,7 +64,7 @@ public class MessageService extends AbstractService {
     private final UserEventService userEventService;
 
     @Inject
-    public MessageService(Client client, PluginSettings settings,
+    public MessageService(Duniter4jClient client, PluginSettings settings,
                           CryptoService cryptoService, UserEventService userEventService) {
         super("duniter." + INDEX, client, settings, cryptoService);
         this.userEventService = userEventService;
@@ -74,12 +75,12 @@ public class MessageService extends AbstractService {
      * @throws JsonProcessingException
      */
     public MessageService deleteIndex() {
-        deleteIndexIfExists(INDEX);
+        client.deleteIndexIfExists(INDEX);
         return this;
     }
 
     public boolean existsIndex() {
-        return super.existsIndex(INDEX);
+        return client.existsIndex(INDEX);
     }
 
     /**
@@ -87,7 +88,7 @@ public class MessageService extends AbstractService {
      */
     public MessageService createIndexIfNotExists() {
         try {
-            if (!existsIndex(INDEX)) {
+            if (!client.existsIndex(INDEX)) {
                 createIndex();
             }
         }
@@ -165,7 +166,7 @@ public class MessageService extends AbstractService {
     }
 
     public void markMessageAsRead(String id, String signature) {
-        Map<String, Object> fields = getMandatoryFieldsById(INDEX, INBOX_TYPE, id, Message.PROPERTY_HASH, Message.PROPERTY_RECIPIENT);
+        Map<String, Object> fields = client.getMandatoryFieldsById(INDEX, INBOX_TYPE, id, Message.PROPERTY_HASH, Message.PROPERTY_RECIPIENT);
         String recipient = fields.get(UserEvent.PROPERTY_RECIPIENT).toString();
         String hash = fields.get(UserEvent.PROPERTY_HASH).toString();
 
diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/SynchroService.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/SynchroService.java
index 6d683a46d56d02e670cfc017ae0c54ecd673874b..1c32e078c86b173e534eaa036b8fd160e9bab341 100644
--- a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/SynchroService.java
+++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/SynchroService.java
@@ -25,6 +25,7 @@ package org.duniter.elasticsearch.user.service;
 import org.duniter.core.client.model.elasticsearch.Protocol;
 import org.duniter.core.client.model.local.Peer;
 import org.duniter.core.service.CryptoService;
+import org.duniter.elasticsearch.client.Duniter4jClient;
 import org.duniter.elasticsearch.user.PluginSettings;
 import org.duniter.elasticsearch.model.SynchroResult;
 import org.duniter.elasticsearch.service.ServiceLocator;
@@ -39,7 +40,7 @@ import org.elasticsearch.common.inject.Inject;
 public class SynchroService extends AbstractSynchroService {
 
    @Inject
-    public SynchroService(Client client, PluginSettings settings, CryptoService cryptoService,
+    public SynchroService(Duniter4jClient client, PluginSettings settings, CryptoService cryptoService,
                           ThreadPool threadPool, final ServiceLocator serviceLocator) {
         super(client, settings.getDelegate(), cryptoService, threadPool, serviceLocator);
     }
diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/UserEventService.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/UserEventService.java
index 8b48a96ae92ef2b1d84cd253e37e7234c20fd33a..eee64c1b4c3be56fde488512f82416e18710f831 100644
--- a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/UserEventService.java
+++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/UserEventService.java
@@ -33,6 +33,7 @@ import org.duniter.core.util.Preconditions;
 import org.duniter.core.util.StringUtils;
 import org.duniter.core.util.crypto.CryptoUtils;
 import org.duniter.core.util.crypto.KeyPair;
+import org.duniter.elasticsearch.client.Duniter4jClient;
 import org.duniter.elasticsearch.exception.InvalidSignatureException;
 import org.duniter.elasticsearch.service.changes.ChangeEvent;
 import org.duniter.elasticsearch.service.changes.ChangeService;
@@ -101,7 +102,7 @@ public class UserEventService extends AbstractService implements ChangeService.C
     public final boolean trace;
 
     @Inject
-    public UserEventService(final Client client,
+    public UserEventService(final Duniter4jClient client,
                             final PluginSettings pluginSettings,
                             final CryptoService cryptoService,
                             final MailService mailService,
@@ -238,7 +239,7 @@ public class UserEventService extends AbstractService implements ChangeService.C
 
     public ListenableActionFuture<UpdateResponse> markEventAsRead(String id, String signature) {
 
-        Map<String, Object> fields = getMandatoryFieldsById(INDEX, EVENT_TYPE, id, UserEvent.PROPERTY_HASH, UserEvent.PROPERTY_RECIPIENT);
+        Map<String, Object> fields = client.getMandatoryFieldsById(INDEX, EVENT_TYPE, id, UserEvent.PROPERTY_HASH, UserEvent.PROPERTY_RECIPIENT);
         String recipient = fields.get(UserEvent.PROPERTY_RECIPIENT).toString();
         String hash = fields.get(UserEvent.PROPERTY_HASH).toString();
 
@@ -446,13 +447,13 @@ public class UserEventService extends AbstractService implements ChangeService.C
 
                 // Flush the bulk if not empty
                 if ((counter % bulkSize) == 0) {
-                    flushDeleteBulk(INDEX, EVENT_TYPE, bulkRequest);
+                    client.flushDeleteBulk(INDEX, EVENT_TYPE, bulkRequest);
                     bulkRequest = client.prepareBulk();
                 }
             }
 
             // last flush
-            flushDeleteBulk(INDEX, EVENT_TYPE, bulkRequest);
+            client.flushDeleteBulk(INDEX, EVENT_TYPE, bulkRequest);
         }
         catch(SearchPhaseExecutionException e) {
             // Failed or no item on index
@@ -461,13 +462,13 @@ public class UserEventService extends AbstractService implements ChangeService.C
     }
 
     private UserProfile getUserProfile(String pubkey, String... fieldnames) {
-        UserProfile result = getSourceByIdOrNull(UserService.INDEX, UserService.PROFILE_TYPE, pubkey, UserProfile.class, fieldnames);
+        UserProfile result = client.getSourceByIdOrNull(UserService.INDEX, UserService.PROFILE_TYPE, pubkey, UserProfile.class, fieldnames);
         if (result == null) result = new UserProfile();
         return result;
     }
 
     private UserProfile getUserProfileOrNull(String pubkey, String... fieldnames) {
-        return getSourceByIdOrNull(UserService.INDEX, UserService.PROFILE_TYPE, pubkey, UserProfile.class, fieldnames);
+        return client.getSourceByIdOrNull(UserService.INDEX, UserService.PROFILE_TYPE, pubkey, UserProfile.class, fieldnames);
     }
 
     private String toJson(UserEvent userEvent) {
@@ -531,7 +532,7 @@ public class UserEventService extends AbstractService implements ChangeService.C
         // Notify listeners
         threadPool.schedule(() -> {
             synchronized (LISTENERS) {
-                LISTENERS.values().stream().forEach(listener -> {
+                LISTENERS.values().forEach(listener -> {
                     if (event.getRecipient().equals(listener.getPubkey())) {
                         listener.onEvent(event);
                     }
diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/UserInvitationService.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/UserInvitationService.java
index ebb8cde1b58bfeedf90d22b0a96154073d613a2a..441b7e3106d96bef0ca17b45e13b3088a255d134 100644
--- a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/UserInvitationService.java
+++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/UserInvitationService.java
@@ -28,15 +28,13 @@ import com.fasterxml.jackson.databind.JsonNode;
 import org.duniter.core.client.model.ModelUtils;
 import org.duniter.core.exception.TechnicalException;
 import org.duniter.core.service.CryptoService;
-import org.duniter.elasticsearch.exception.InvalidSignatureException;
+import org.duniter.elasticsearch.client.Duniter4jClient;
 import org.duniter.elasticsearch.user.PluginSettings;
 import org.duniter.elasticsearch.user.model.Message;
 import org.duniter.elasticsearch.user.model.UserEvent;
 import org.duniter.elasticsearch.user.model.UserEventCodes;
 import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
 import org.elasticsearch.action.index.IndexResponse;
-import org.elasticsearch.action.update.UpdateRequestBuilder;
-import org.elasticsearch.client.Client;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.xcontent.XContentBuilder;
@@ -44,7 +42,6 @@ import org.elasticsearch.common.xcontent.XContentFactory;
 import org.nuiton.i18n.I18n;
 
 import java.io.IOException;
-import java.util.Map;
 
 /**
  * Created by Benoit on 30/03/2015.
@@ -54,12 +51,12 @@ public class UserInvitationService extends AbstractService {
     public static final String INDEX = "invitation";
     public static final String CERTIFICATION_TYPE = "certification";
 
-
     private final UserEventService userEventService;
 
     @Inject
-    public UserInvitationService(Client client, PluginSettings settings,
-                                 CryptoService cryptoService, UserEventService userEventService) {
+    public UserInvitationService(Duniter4jClient client, PluginSettings settings,
+                                 CryptoService cryptoService,
+                                 UserEventService userEventService) {
         super("duniter." + INDEX, client, settings, cryptoService);
         this.userEventService = userEventService;
     }
@@ -69,20 +66,16 @@ public class UserInvitationService extends AbstractService {
      * @throws JsonProcessingException
      */
     public UserInvitationService deleteIndex() {
-        deleteIndexIfExists(INDEX);
+        client.deleteIndexIfExists(INDEX);
         return this;
     }
 
-    public boolean existsIndex() {
-        return super.existsIndex(INDEX);
-    }
-
     /**
      * Create index need for blockchain registry, if need
      */
     public UserInvitationService createIndexIfNotExists() {
         try {
-            if (!existsIndex(INDEX)) {
+            if (!client.existsIndex(INDEX)) {
                 createIndex();
             }
         }
diff --git a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/UserService.java b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/UserService.java
index 75a4c039b19ba403cfe2385b9365a85a6772350e..11f62c689b835b425cdbd8eb72d62152e24d64f9 100644
--- a/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/UserService.java
+++ b/duniter4j-es-user/src/main/java/org/duniter/elasticsearch/user/service/UserService.java
@@ -31,7 +31,7 @@ import org.duniter.core.client.model.ModelUtils;
 import org.duniter.core.client.model.elasticsearch.UserProfile;
 import org.duniter.core.exception.TechnicalException;
 import org.duniter.core.service.CryptoService;
-import org.duniter.core.service.MailService;
+import org.duniter.elasticsearch.client.Duniter4jClient;
 import org.duniter.elasticsearch.user.PluginSettings;
 import org.duniter.elasticsearch.exception.AccessDeniedException;
 import org.duniter.elasticsearch.service.AbstractService;
@@ -60,7 +60,7 @@ public class UserService extends AbstractService {
     public static final String SETTINGS_TYPE = "settings";
 
     @Inject
-    public UserService(Client client,
+    public UserService(Duniter4jClient client,
                        PluginSettings settings,
                        CryptoService cryptoService) {
         super("duniter." + INDEX, client, settings.getDelegate(), cryptoService);
@@ -71,7 +71,7 @@ public class UserService extends AbstractService {
      */
     public UserService createIndexIfNotExists() {
         try {
-            if (!existsIndex(INDEX)) {
+            if (!client.existsIndex(INDEX)) {
                 createIndex();
             }
         }
@@ -104,14 +104,10 @@ public class UserService extends AbstractService {
     }
 
     public UserService deleteIndex() {
-        deleteIndexIfExists(INDEX);
+        client.deleteIndexIfExists(INDEX);
         return this;
     }
 
-    public boolean existsIndex() {
-        return super.existsIndex(INDEX);
-    }
-
     /**
      *
      * Index an user profile
@@ -203,17 +199,17 @@ public class UserService extends AbstractService {
 
     public String getProfileTitle(String issuer) {
 
-        Object title = getFieldById(INDEX, PROFILE_TYPE, issuer, UserProfile.PROPERTY_TITLE);
+        Object title = client.getFieldById(INDEX, PROFILE_TYPE, issuer, UserProfile.PROPERTY_TITLE);
         if (title == null) return null;
         return title.toString();
     }
 
     public Map<String, String> getProfileTitles(Set<String> issuers) {
 
-        Map<String, Object> titles = getFieldByIds(INDEX, PROFILE_TYPE, issuers, UserProfile.PROPERTY_TITLE);
+        Map<String, Object> titles = client.getFieldByIds(INDEX, PROFILE_TYPE, issuers, UserProfile.PROPERTY_TITLE);
         if (MapUtils.isEmpty(titles)) return null;
         Map<String, String> result = new HashMap<>();
-        titles.entrySet().stream().forEach((entry) -> result.put(entry.getKey(), entry.getValue().toString()));
+        titles.entrySet().forEach((entry) -> result.put(entry.getKey(), entry.getValue().toString()));
         return result;
     }
 
@@ -230,7 +226,7 @@ public class UserService extends AbstractService {
 
         Map<String, String> profileTitles = getProfileTitles(pubkeys);
         StringBuilder sb = new StringBuilder();
-        pubkeys.stream().forEach((pubkey)-> {
+        pubkeys.forEach((pubkey)-> {
             String title = profileTitles != null ? profileTitles.get(pubkey) : null;
             sb.append(separator);
             sb.append(title != null ? title :
@@ -242,7 +238,6 @@ public class UserService extends AbstractService {
 
     /* -- Internal methods -- */
 
-
     public XContentBuilder createProfileType() {
         String stringAnalyzer = pluginSettings.getDefaultStringAnalyzer();
 
diff --git a/pom.xml b/pom.xml
index 446da6f8318b6eabf8300826b65aa1e3680a5309..88eddb0f088c6db29c768a364c55b97ab592402f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -285,11 +285,6 @@
         <version>${tyrus.version}</version>
       </dependency>
 
-      <dependency>
-        <groupId>com.github.spullara.mustache.java</groupId>
-        <artifactId>mustache.java</artifactId>
-        <version>0.9.4</version>
-      </dependency>
     </dependencies>
   </dependencyManagement>