diff --git a/duniter4j-cmd/lib/j-text-utils-0.3.3.jar b/duniter4j-cmd/lib/j-text-utils-0.3.3.jar
new file mode 100644
index 0000000000000000000000000000000000000000..42a9c6fa4ce334d113eba5af341d8a06b1a651e7
Binary files /dev/null and b/duniter4j-cmd/lib/j-text-utils-0.3.3.jar differ
diff --git a/duniter4j-cmd/lib/j-text-utils-0.3.3.pom b/duniter4j-cmd/lib/j-text-utils-0.3.3.pom
new file mode 100644
index 0000000000000000000000000000000000000000..1a68c2db5fbeda3d9dfdb110c2cef99fe2a50235
--- /dev/null
+++ b/duniter4j-cmd/lib/j-text-utils-0.3.3.pom
@@ -0,0 +1,65 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <groupId>dnl.utils</groupId>
+    <artifactId>j-text-utils</artifactId>
+    <packaging>jar</packaging>
+    <version>0.3.3</version>
+    <name>Java Text Utilities</name>
+    <url>http://code.google.com/p/j-text-utils</url>
+
+    <dependencies>
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <version>2.4</version>
+        </dependency>
+        <dependency>
+            <groupId>net.sf.opencsv</groupId>
+            <artifactId>opencsv</artifactId>
+            <version>2.3</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>com.google.guava</groupId>
+            <artifactId>guava</artifactId>
+            <version>14.0.1</version>
+            <scope>provided</scope>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <version>4.7</version>
+            <scope>test</scope>
+        </dependency>
+    </dependencies>
+
+    <build>
+        <extensions>
+            <extension>
+                <groupId>org.jvnet.wagon-svn</groupId>
+                <artifactId>wagon-svn</artifactId>
+                <version>1.9</version>
+            </extension>
+        </extensions>
+        <plugins>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-compiler-plugin</artifactId>
+                <configuration>
+                    <source>1.7</source>
+                    <target>1.7</target>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
+
+    <distributionManagement>
+        <repository>
+            <uniqueVersion>false</uniqueVersion>
+            <id>googlecode</id>
+            <url>svn:https://j-text-utils.googlecode.com/svn/trunk/repo/</url>
+        </repository>
+    </distributionManagement>
+
+</project>
diff --git a/duniter4j-cmd/pom.xml b/duniter4j-cmd/pom.xml
new file mode 100644
index 0000000000000000000000000000000000000000..0491353b51545e4e1ebc3fde7d3df65e6ed439fa
--- /dev/null
+++ b/duniter4j-cmd/pom.xml
@@ -0,0 +1,156 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <parent>
+        <artifactId>duniter4j</artifactId>
+        <groupId>org.duniter</groupId>
+        <version>0.9.2-SNAPSHOT</version>
+    </parent>
+    <modelVersion>4.0.0</modelVersion>
+
+    <artifactId>duniter4j-cmd</artifactId>
+
+    <properties>
+        <jTextUtilsVersion>0.3.3</jTextUtilsVersion>
+    </properties>
+
+    <repositories>
+        <repository>
+            <id>d-maven</id>
+            <url>https://github.com/neilpanchal/j-text-utils/tree/master/repo</url>
+        </repository>
+    </repositories>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.duniter</groupId>
+            <artifactId>duniter4j-core-client</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+
+        <!-- logging -->
+        <dependency>
+            <groupId>org.slf4j</groupId>
+            <artifactId>slf4j-log4j12</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>log4j</groupId>
+            <artifactId>log4j</artifactId>
+        </dependency>
+
+
+        <dependency>
+            <groupId>com.beust</groupId>
+            <artifactId>jcommander</artifactId>
+            <version>1.60</version>
+        </dependency>
+
+        <dependency>
+            <groupId>dnl.utils</groupId>
+            <artifactId>j-text-utils</artifactId>
+            <version>${jTextUtilsVersion}</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-lang</groupId>
+            <artifactId>commons-lang</artifactId>
+            <version>2.6</version>
+        </dependency>
+
+    </dependencies>
+
+    <profiles>
+        <profile>
+            <id>install-missing-libs</id>
+            <activation>
+                <file>
+                    <missing>${settings.localRepository}/dnl/utils/j-text-utils/${jTextUtilsVersion}/j-text-utils-${jTextUtilsVersion}.jar</missing>
+                </file>
+            </activation>
+            <build>
+                <plugins>
+                    <plugin>
+                        <artifactId>maven-install-plugin</artifactId>
+                        <version>2.5.2</version>
+                        <executions>
+                            <execution>
+                                <id>installing j-text-utils.jar</id>
+                                <phase>initialize</phase>
+                                <goals>
+                                    <goal>install-file</goal>
+                                </goals>
+                                <configuration>
+                                    <groupId>dnl.utils</groupId>
+                                    <artifactId>j-text-utils</artifactId>
+                                    <version>${jTextUtilsVersion}</version>
+                                    <packaging>jar</packaging>
+                                    <file>${project.basedir}/lib/j-text-utils-${jTextUtilsVersion}.jar</file>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+
+                    <plugin>
+                        <artifactId>maven-antrun-plugin</artifactId>
+                        <executions>
+                            <execution>
+                                <id>enforce-dependencies-exists</id>
+                                <phase>generate-sources</phase>
+                                <goals>
+                                    <goal>run</goal>
+                                </goals>
+                                <configuration>
+                                    <target>
+
+                                        <condition property="displayMessage">
+                                            <and>
+                                                <not><available file="${project.basedir}/.maven/install.log" /></not>
+                                                <!-- do not failed here if performRelease -->
+                                                <isfalse value="${performRelease}" />
+                                            </and>
+                                        </condition>
+                                        <property name="installSuccessMessage">*
+                                            *************************************************************************
+                                            *
+                                            * IMPORTANT:
+                                            *
+                                            * Missing lib dependencies successfully installed on [${settings.localRepository}]
+                                            * You should now re-run the build.
+                                            * This message will NOT appear again
+                                            *
+                                            *************************************************************************
+                                        </property>
+
+                                        <echo file="${project.basedir}/.maven/install.log">${installSuccessMessage}</echo>
+
+                                        <fail message="${installSuccessMessage}" >
+                                            <condition>
+                                                <istrue value="${displayMessage}"/>
+                                            </condition>
+                                        </fail>
+                                    </target>
+                                </configuration>
+                            </execution>
+                        </executions>
+                    </plugin>
+                </plugins>
+            </build>
+        </profile>
+
+        <profile>
+            <id>use-installed-libs</id>
+            <activation>
+                <file>
+                    <exists>${settings.localRepository}/dnl/utils/j-text-utils/${jTextUtilsVersion}/j-text-utils-${jTextUtilsVersion}.jar</exists>
+                </file>
+            </activation>
+            <dependencies>
+                <dependency>
+                    <groupId>dnl.utils</groupId>
+                    <artifactId>j-text-utils</artifactId>
+                    <version>${jTextUtilsVersion}</version>
+                </dependency>
+            </dependencies>
+        </profile>
+    </profiles>
+</project>
\ No newline at end of file
diff --git a/duniter4j-cmd/src/main/java/fr/duniter/cmd/Main.java b/duniter4j-cmd/src/main/java/fr/duniter/cmd/Main.java
new file mode 100644
index 0000000000000000000000000000000000000000..7208a3dc3163e375146c34163ccaeb030f4ef9d8
--- /dev/null
+++ b/duniter4j-cmd/src/main/java/fr/duniter/cmd/Main.java
@@ -0,0 +1,149 @@
+package fr.duniter.cmd;
+
+import com.beust.jcommander.JCommander;
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.ParameterException;
+import com.google.common.collect.Lists;
+import fr.duniter.cmd.actions.NetworkAction;
+import fr.duniter.cmd.actions.SentMoneyAction;
+import org.apache.commons.io.FileUtils;
+import org.duniter.core.client.config.Configuration;
+import org.duniter.core.client.service.ServiceLocator;
+import org.duniter.core.util.StringUtils;
+import org.nuiton.i18n.I18n;
+import org.nuiton.i18n.init.DefaultI18nInitializer;
+import org.nuiton.i18n.init.UserI18nInitializer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * Created by blavenie on 22/03/17.
+ */
+public class Main {
+
+    @Parameter(names = "-debug", description = "Debug mode", arity = 1)
+    private boolean debug = false;
+
+    @Parameter(names = "--help", help = true)
+    private boolean help;
+
+    @Parameter(names = "-config", description = "Configuration file path")
+    private String configFilename = "duniter-cmd.config";
+
+    public static void main(String ... args) {
+        Main main = new Main();
+        main.run(args);
+    }
+
+    protected void run(String ... args) {
+
+        Map<String, Runnable> actions = new HashMap<>();
+        actions.put("network", new NetworkAction());
+        actions.put("send", new SentMoneyAction());
+
+        // Parsing args
+        JCommander jc = new JCommander(this);
+        actions.entrySet().stream().forEach(entry -> jc.addCommand(entry.getKey(), entry.getValue()));
+        try {
+            jc.parse(args);
+        }
+        catch(ParameterException e) {
+            System.err.println(e.getMessage());
+            System.err.println("Try --help for usage");
+            //jc.usage();
+            System.exit(-1);
+        }
+
+        // Usage, if help or no command
+        String actionName = jc.getParsedCommand();
+        if (StringUtils.isBlank(actionName)) {
+            jc.usage();
+            // Return error code, if not help
+            if (!help) System.exit(-1);
+            return;
+        }
+
+        // Set log level
+        // TODO
+
+        // Init configuration
+        initConfiguration(configFilename);
+
+        // Init i18n
+        try {
+            initI18n();
+        } catch(IOException e) {
+            System.out.println("Unable to initialize translations");
+            System.exit(-1);
+        }
+
+        // Set a default account id, then load cache
+        ServiceLocator.instance().getDataContext().setAccountId(0);
+
+        // Initialize service locator
+        ServiceLocator.instance().init();
+
+        Runnable action = actions.get(actionName);
+        action.run();
+    }
+
+
+    protected String getI18nBundleName() {
+        return "duniter4j-core-client-i18n";
+    }
+
+    /* -- -- */
+
+    /**
+     * Convenience methods that could be override to initialize other configuration
+     *
+     * @param configFilename
+     * @param configArgs
+     */
+    protected void initConfiguration(String configFilename) {
+        String[] configArgs = getConfigArgs();
+        Configuration config = new Configuration(configFilename, configArgs);
+        Configuration.setInstance(config);
+    }
+
+    protected void initI18n() throws IOException {
+        Configuration config = Configuration.instance();
+
+        // --------------------------------------------------------------------//
+        // init i18n
+        // --------------------------------------------------------------------//
+        File i18nDirectory = new File(config.getDataDirectory(), "i18n");
+        if (i18nDirectory.exists()) {
+            // clean i18n cache
+            FileUtils.cleanDirectory(i18nDirectory);
+        }
+
+        FileUtils.forceMkdir(i18nDirectory);
+
+        if (debug) {
+            System.out.println("I18N directory: " + i18nDirectory);
+        }
+
+        Locale i18nLocale = config.getI18nLocale();
+
+        if (debug) {
+            System.out.println(String.format("Starts i18n with locale [%s] at [%s]",
+                    i18nLocale, i18nDirectory));
+        }
+        I18n.init(new UserI18nInitializer(
+                        i18nDirectory, new DefaultI18nInitializer(getI18nBundleName())),
+                i18nLocale);
+    }
+
+    protected String[] getConfigArgs() {
+        List<String> configArgs = Lists.newArrayList();
+        /*configArgs.addAll(Lists.newArrayList(
+                "--option", ConfigurationOption.BASEDIR.getKey(), getResourceDirectory().getAbsolutePath()));*/
+        return configArgs.toArray(new String[configArgs.size()]);
+    }
+
+}
diff --git a/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/NetworkAction.java b/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/NetworkAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..ac10b861e02cdbaf888f49ea549a234e77d4fd65
--- /dev/null
+++ b/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/NetworkAction.java
@@ -0,0 +1,80 @@
+package fr.duniter.cmd.actions;
+
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.Parameters;
+import dnl.utils.text.table.TextTable;
+import fr.duniter.cmd.actions.utils.Formatters;
+import org.duniter.core.client.model.local.Peer;
+import org.duniter.core.client.service.ServiceLocator;
+import org.duniter.core.client.service.local.NetworkService;
+import org.duniter.core.util.CollectionUtils;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+/**
+ * Created by blavenie on 22/03/17.
+ */
+@Parameters(commandDescription = "Display network peers")
+public class NetworkAction implements Runnable {
+
+    @Parameter(names = "-host", description = "Duniter host")
+    private String host = "g1.duniter.org";
+
+    @Parameter(names = "-port", description = "Duniter port")
+    private int port = 10901;
+
+    @Override
+    public void run() {
+        NetworkService service = ServiceLocator.instance().getNetworkService();
+        Peer mainPeer = Peer.newBuilder().setHost(host).setPort(port).build();
+
+        List<Peer> peers = service.getPeers(mainPeer);
+
+        if (CollectionUtils.isEmpty(peers)) {
+            System.out.println("No peers found");
+        }
+        else {
+
+            String[] columnNames = {
+                    "Uid",
+                    "Pubkey",
+                    "Address",
+                    "Status",
+                    "API",
+                    "Version",
+                    "Difficulty",
+                    "Block #"};
+
+            List<Object[]> data = peers.stream().map(peer -> {
+                    boolean isUp = peer.getStats().getStatus() == Peer.PeerStatus.UP;
+                    return new Object[] {
+                            Formatters.formatUid(peer.getStats().getUid()),
+                            Formatters.formatPubkey(peer.getPubkey()),
+                            peer.getHost() + ":" + peer.getPort(),
+                            peer.getStats().getStatus().name(),
+                            isUp && peer.isUseSsl() ? "SSL" : null,
+                            isUp ? peer.getStats().getVersion() : null,
+                            isUp ? peer.getStats().getHardshipLevel() : "Mirror",
+                            isUp ? peer.getStats().getBlockNumber() : null
+                    };
+                })
+                    .collect(Collectors.toList());
+
+            Object[][] rows = new Object[data.size()][];
+            int i = 0;
+            for (Object[] row : data) {
+                rows[i++] = row;
+            }
+
+
+            TextTable tt = new TextTable(columnNames, rows);
+            // this adds the numbering on the left
+            tt.setAddRowNumbering(true);
+            tt.printTable();
+        }
+
+    }
+
+
+}
diff --git a/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/SentMoneyAction.java b/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/SentMoneyAction.java
new file mode 100644
index 0000000000000000000000000000000000000000..9b64dcf8233f5d756b49c1e36c0996a1abc31444
--- /dev/null
+++ b/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/SentMoneyAction.java
@@ -0,0 +1,76 @@
+package fr.duniter.cmd.actions;
+
+import com.beust.jcommander.Parameter;
+import com.beust.jcommander.ParametersDelegate;
+import fr.duniter.cmd.actions.params.WalletParameters;
+import fr.duniter.cmd.actions.utils.Formatters;
+import org.duniter.core.client.config.Configuration;
+import org.duniter.core.client.model.bma.BlockchainParameters;
+import org.duniter.core.client.model.local.Currency;
+import org.duniter.core.client.model.local.Peer;
+import org.duniter.core.client.model.local.Wallet;
+import org.duniter.core.client.service.ServiceLocator;
+import org.duniter.core.client.service.bma.BlockchainRemoteService;
+import org.duniter.core.client.service.bma.TransactionRemoteService;
+import org.duniter.core.service.CryptoService;
+import org.duniter.core.util.crypto.CryptoUtils;
+import org.duniter.core.util.crypto.KeyPair;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Created by blavenie on 22/03/17.
+ */
+public class SentMoneyAction implements Runnable {
+
+    private static final Logger log = LoggerFactory.getLogger(SentMoneyAction.class);
+
+    @ParametersDelegate
+    private WalletParameters walletParams = new WalletParameters();
+
+    @Parameter(names = "--amount", description = "Amount", required = true)
+    public int amount;
+
+    @Parameter(names = "--dest", description = "Destination pubkey", required = true)
+    public String destPubkey;
+
+    @Parameter(names = "--comment", description = "TX Comment")
+    public String comment;
+
+    @Override
+    public void run() {
+
+        CryptoService cryptoService = ServiceLocator.instance().getCryptoService();
+        TransactionRemoteService txService = ServiceLocator.instance().getTransactionRemoteService();
+        Configuration config = Configuration.instance();
+
+        Peer peer = Peer.newBuilder().setHost(config.getNodeHost())
+                .setPort(config.getNodePort())
+                .build();
+
+        Currency currency = ServiceLocator.instance().getBlockchainRemoteService().getCurrencyFromPeer(peer);
+        ServiceLocator.instance().getCurrencyService().save(currency);
+        peer.setCurrencyId(currency.getId());
+        peer.setCurrency(currency.getCurrencyName());
+        ServiceLocator.instance().getPeerService().save(peer);
+
+        // Compute keypair and wallet
+        KeyPair keypair = cryptoService.getKeyPair(walletParams.salt, walletParams.password);
+        Wallet wallet = new Wallet(
+                currency.getCurrencyName(),
+                null,
+                keypair.getPubKey(),
+                keypair.getSecKey());
+        wallet.setCurrencyId(currency.getId());
+
+        System.out.println("Connected to wallet: " + wallet.getPubKeyHash());
+
+        txService.transfer(wallet, destPubkey, amount, comment);
+
+
+        System.out.println(String.format("Successfully sent [%d %s] to [%s]",
+                amount,
+                Formatters.currencySymbol(currency.getCurrencyName()),
+                Formatters.formatPubkey(destPubkey)));
+    }
+}
diff --git a/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/params/WalletParameters.java b/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/params/WalletParameters.java
new file mode 100644
index 0000000000000000000000000000000000000000..2694cafc0c0f304a6b6b709026f157f4c3d520d8
--- /dev/null
+++ b/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/params/WalletParameters.java
@@ -0,0 +1,14 @@
+package fr.duniter.cmd.actions.params;
+
+import com.beust.jcommander.Parameter;
+
+/**
+ * Created by blavenie on 22/03/17.
+ */
+public class WalletParameters {
+    @Parameter(names = "--salt", description = "Salt (to generate the keypair)", required = true)
+    public String salt;
+
+    @Parameter(names = "--passwd", description = "Password (to generate the keypair)", required = true)
+    public String password;
+}
diff --git a/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/utils/Formatters.java b/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/utils/Formatters.java
new file mode 100644
index 0000000000000000000000000000000000000000..0f8cdc9bdbbf0b2df6d3c8399b93e881e6f5262b
--- /dev/null
+++ b/duniter4j-cmd/src/main/java/fr/duniter/cmd/actions/utils/Formatters.java
@@ -0,0 +1,34 @@
+package fr.duniter.cmd.actions.utils;
+
+/**
+ * Created by blavenie on 24/03/17.
+ */
+public class Formatters {
+
+    public static String formatPubkey(String pubkey) {
+        if (pubkey != null && pubkey.length() > 8) {
+            return pubkey.substring(0, 8);
+        }
+        return pubkey;
+    }
+
+    public static String formatUid(String uid) {
+        if (uid != null && uid.length() > 20) {
+            return uid.substring(0, 19);
+        }
+        return uid;
+    }
+
+    public static String currencySymbol(String currencyName) {
+        String[] parts = currencyName.split("-_");
+        if (parts.length < 2) {
+            if (currencyName.length() <= 3) {
+                return currencyName.toUpperCase();
+            }
+            else {
+                return currencyName.toUpperCase().substring(0,1);
+            }
+        }
+        return currencySymbol(parts[0]) + currencySymbol(parts[1]);
+    }
+}
diff --git a/duniter4j-cmd/src/main/resources/META-INF/services/org.duniter.core.beans.Bean b/duniter4j-cmd/src/main/resources/META-INF/services/org.duniter.core.beans.Bean
new file mode 100644
index 0000000000000000000000000000000000000000..bbf9fb6b0c5cc69950b92beafedb92622ab88201
--- /dev/null
+++ b/duniter4j-cmd/src/main/resources/META-INF/services/org.duniter.core.beans.Bean
@@ -0,0 +1,13 @@
+org.duniter.core.client.service.bma.BlockchainRemoteServiceImpl
+org.duniter.core.client.service.bma.NetworkRemoteServiceImpl
+org.duniter.core.client.service.bma.WotRemoteServiceImpl
+org.duniter.core.client.service.bma.TransactionRemoteServiceImpl
+org.duniter.core.client.service.elasticsearch.CurrencyRegistryRemoteServiceImpl
+org.duniter.core.service.Ed25519CryptoServiceImpl
+org.duniter.core.client.service.HttpServiceImpl
+org.duniter.core.client.service.DataContext
+org.duniter.core.client.service.local.PeerServiceImpl
+org.duniter.core.client.service.local.CurrencyServiceImpl
+org.duniter.core.client.service.local.NetworkServiceImpl
+org.duniter.core.client.dao.mem.MemoryCurrencyDaoImpl
+org.duniter.core.client.dao.mem.MemoryPeerDaoImpl
\ No newline at end of file
diff --git a/duniter4j-cmd/src/main/resources/duniter4j-cmd.config b/duniter4j-cmd/src/main/resources/duniter4j-cmd.config
new file mode 100644
index 0000000000000000000000000000000000000000..d9f4f75b7e5ea3263c1a2c3b4ac7eecaba84c90e
--- /dev/null
+++ b/duniter4j-cmd/src/main/resources/duniter4j-cmd.config
@@ -0,0 +1,5 @@
+duniter4j.node.host=192.168.0.5
+duniter4j.node.port=10901
+
+duniter4j.node.elasticsearch.host=localhost
+duniter4j.node.elasticsearch.port=9200
diff --git a/duniter4j-cmd/src/main/resources/log4j.properties b/duniter4j-cmd/src/main/resources/log4j.properties
new file mode 100644
index 0000000000000000000000000000000000000000..f31940a1b8ca70d29ca0eb5840a6ec86174973f1
--- /dev/null
+++ b/duniter4j-cmd/src/main/resources/log4j.properties
@@ -0,0 +1,27 @@
+###
+# Global logging configuration
+log4j.rootLogger=ERROR, stdout, file
+
+# Console output
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %5p - %m%n
+
+# duniter4j levels
+log4j.logger.org.duniter=INFO
+log4j.logger.org.duniter.cmd=INFO
+#log4j.logger.org.duniter.core.client.service=DEBUG
+log4j.logger.org.duniter.core.client.service.local=DEBUG
+#log4j.logger.org.duniter.core.client.service.bma=DEBUG
+log4j.logger.org.duniter.core.beans=WARN
+#log4j.logger.org.duniter.core.client.service=TRACE
+
+log4j.appender.file=org.apache.log4j.RollingFileAppender
+log4j.appender.file.file=ucoin-client.log
+log4j.appender.file.MaxFileSize=10MB
+log4j.appender.file.MaxBackupIndex=4
+
+log4j.appender.file.layout=org.apache.log4j.PatternLayout
+log4j.appender.file.layout.ConversionPattern=%d{ISO8601} %5p %c - %m%n
+
+
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/config/Configuration.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/config/Configuration.java
index e6d06adb26087ec59debd9c4e1d4eb3db09c00b8..dfc6b61988c8a8a43cc36a7be56cc47dc60bd405 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/config/Configuration.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/config/Configuration.java
@@ -84,14 +84,14 @@ public class Configuration  {
         this.applicationConfig.setEncoding(Charsets.UTF_8.name());
         this.applicationConfig.setConfigFileName(file);
 
-        // get all config providers
+        // get allOfToList config providers
         Set<ApplicationConfigProvider> providers =
                 ApplicationConfigHelper.getProviders(null,
                         null,
                         null,
                         true);
 
-        // load all default options
+        // load allOfToList default options
         ApplicationConfigHelper.loadAllDefaultOption(applicationConfig,
                 providers);
 
@@ -106,7 +106,7 @@ public class Configuration  {
         // Override application version
         initVersion(applicationConfig);
 
-        // get all transient and final option keys
+        // get allOfToList transient and final option keys
         Set<String> optionToSkip =
                 ApplicationConfigHelper.getTransientOptionKeys(providers);
 
@@ -233,11 +233,7 @@ public class Configuration  {
     public String getNodeCurrency() {
         return applicationConfig.getOption(ConfigurationOption.NODE_CURRENCY.getKey());
     }
-    
-    public String getNodeProtocol() {
-        return applicationConfig.getOption(ConfigurationOption.NODE_PROTOCOL.getKey());
-    }
-    
+
     public String getNodeHost() {
         return applicationConfig.getOption(ConfigurationOption.NODE_HOST.getKey());
     }
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/config/ConfigurationOption.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/config/ConfigurationOption.java
index 41d80093af34d6b6ffbe0817c5ea5e8ca4562540..606253e0c73a2e84b73a628b95c572532d869f3a 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/config/ConfigurationOption.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/config/ConfigurationOption.java
@@ -143,14 +143,14 @@ public enum ConfigurationOption implements ConfigOptionDef {
     NODE_HOST(
             "duniter4j.node.host",
             n("duniter4j.config.option.node.host.description"),
-            "cgeek.fr",
+            "g1.duniter.org",
             String.class,
             false),
 
     NODE_PORT(
             "duniter4j.node.port",
             n("duniter4j.config.option.node.port.description"),
-            "9330",
+            "10901",
             Integer.class,
             false),
 
@@ -164,7 +164,7 @@ public enum ConfigurationOption implements ConfigOptionDef {
     NETWORK_TIMEOUT(
             "duniter4j.network.timeout",
             n("duniter4j.config.option.network.timeout.description"),
-            "100000", // = 10 s
+            "20000", // = 2 s
             Integer.class,
             false),
 
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/Constants.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/Constants.java
index 1df3da70caa447790e09943d22cce224f1a94be9..959794aeb5d26688a14c94ed24c2f814dcf31cbb 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/Constants.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/Constants.java
@@ -32,4 +32,13 @@ public interface Constants {
         String CURRENCY_NAME = "[A-Za-z0-9_-]";
         String PUBKEY = "[123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{43,44}";
     }
+
+    interface HttpStatus {
+        int SC_TOO_MANY_REQUESTS = 429;
+    }
+
+    interface Config {
+        int TOO_MANY_REQUEST_RETRY_TIME = 500; // 500 ms
+        int MAX_SAME_REQUEST_COUNT = 5; // 5 requests before to get 429 error
+    }
 }
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/EndpointProtocol.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/EndpointApi.java
similarity index 92%
rename from duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/EndpointProtocol.java
rename to duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/EndpointApi.java
index 1f4b2571737a00cb2863b0f7228ef21a1e50cf89..3c9965ff929413d775b27cd898a79f7094834f07 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/EndpointProtocol.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/EndpointApi.java
@@ -23,7 +23,10 @@ package org.duniter.core.client.model.bma;
  */
 
 
-public enum EndpointProtocol {
+public enum EndpointApi {
     BASIC_MERKLED_API,
+    BMAS,
+    ES_CORE_API,
+    ES_USER_API,
     UNDEFINED
 }
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/NetworkPeering.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/NetworkPeering.java
index 1d24f9c51679cfdc8a446f5f5626e35ffea45c54..560a70e02c7b778e3f2d3207e3179034ca075439 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/NetworkPeering.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/NetworkPeering.java
@@ -35,6 +35,7 @@ public class NetworkPeering implements Serializable {
     private String block;
     private String signature;
 
+
     private String raw;
 
     private String pubkey;
@@ -112,26 +113,26 @@ public class NetworkPeering implements Serializable {
     }
 
     public static class Endpoint implements Serializable {
-        public EndpointProtocol protocol;
-        public String url;
+        public EndpointApi api;
+        public String dns;
         public String ipv4;
         public String ipv6;
         public Integer port;
 
-        public EndpointProtocol getProtocol() {
-            return protocol;
+        public EndpointApi getApi() {
+            return api;
         }
 
-        public void setProtocol(EndpointProtocol protocol) {
-            this.protocol = protocol;
+        public void setApi(EndpointApi api) {
+            this.api = api;
         }
 
-        public String getUrl() {
-            return url;
+        public String getDns() {
+            return dns;
         }
 
-        public void setUrl(String url) {
-            this.url = url;
+        public void setDns(String dns) {
+            this.dns = dns;
         }
 
         public String getIpv4() {
@@ -160,8 +161,8 @@ public class NetworkPeering implements Serializable {
 
         @Override
         public String toString() {
-            String s = "protocol=" + protocol.name() + "\n" +
-                    "url=" + url + "\n" +
+            String s = "api=" + api.name() + "\n" +
+                    "dns=" + dns + "\n" +
                     "ipv4=" + ipv4 + "\n" +
                     "ipv6=" + ipv6 + "\n" +
                     "port=" + port + "\n";
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/NetworkPeers.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/NetworkPeers.java
index 90cbf2cf84aec6ef619f90a2d50f9bfa9ad16a86..c413492d9586f1980fd14cfc5b3d91e6d222c406 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/NetworkPeers.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/NetworkPeers.java
@@ -148,7 +148,9 @@ public class NetworkPeers implements Serializable {
                     "status=" + status + "\n" +
                     "block=" + block + "\n";
             for(NetworkPeering.Endpoint endpoint: endpoints) {
-                s += endpoint.toString() + "\n";
+                if (endpoint != null) {
+                    s += endpoint.toString() + "\n";
+                }
             }
             return s;
         }
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/Protocol.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/Protocol.java
index df822dd07a44e9c8f16863685275f7dac4686a81..04c77d8f45ecf18738bab899feb49a9694b24b1f 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/Protocol.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/Protocol.java
@@ -27,9 +27,9 @@ package org.duniter.core.client.model.bma;
  */
 public interface Protocol {
 
-    String VERSION = "2";
+    String VERSION = "10";
 
-    String TX_VERSION = "3";
+    String TX_VERSION = "10";
 
     String TYPE_IDENTITY = "Identity";
 
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/TxSource.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/TxSource.java
index 36e4901a5c9d62cfa718405c7d18885c2e2b1ab5..4c0a05e072f76d26619c495338979818a868e256 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/TxSource.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/TxSource.java
@@ -89,7 +89,7 @@ public class TxSource {
 		}
 
 		/**
-		 * Source type : <ul>
+		 * Source sortType : <ul>
 		 * <li><code>D</code> : Universal Dividend</li>
 		 * <li><code>T</code> : Transaction</li>
 		 * </ul>
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/gson/EndpointAdapter.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/gson/EndpointAdapter.java
index 2c8d35b912a8e58c8a2df43a80ea057db3e9cf20..35896bd932ce050e07d211f6dc854a7d80152d90 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/gson/EndpointAdapter.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/gson/EndpointAdapter.java
@@ -25,9 +25,9 @@ package org.duniter.core.client.model.bma.gson;
 import com.google.gson.TypeAdapter;
 import com.google.gson.stream.JsonReader;
 import com.google.gson.stream.JsonWriter;
-import org.duniter.core.client.model.bma.EndpointProtocol;
+import org.duniter.core.client.model.bma.EndpointApi;
 import org.duniter.core.client.model.bma.NetworkPeering;
-import org.apache.http.conn.util.InetAddressUtils;
+import org.duniter.core.util.http.InetAddressUtils;
 
 import java.io.IOException;
 import java.util.ArrayList;
@@ -51,19 +51,19 @@ public class EndpointAdapter extends TypeAdapter<NetworkPeering.Endpoint> {
                     endpoint.ipv4 = word;
                 } else if (InetAddressUtils.isIPv6Address(word)) {
                     endpoint.ipv6 = word;
-                } else if (word.startsWith("http")) {
-                    endpoint.url = word;
+                } else if (word.trim().length() > 0) {
+                    endpoint.dns = word;
                 } else {
                     try {
-                        endpoint.protocol = EndpointProtocol.valueOf(word);
+                        endpoint.api = EndpointApi.valueOf(word);
                     } catch (IllegalArgumentException e) {
                         // skip this part
                     }
                 }
             }
 
-            if (endpoint.protocol == null) {
-                endpoint.protocol = EndpointProtocol.UNDEFINED;
+            if (endpoint.api == null) {
+                endpoint.api = EndpointApi.UNDEFINED;
             }
 
             return endpoint;
@@ -74,8 +74,8 @@ public class EndpointAdapter extends TypeAdapter<NetworkPeering.Endpoint> {
                 writer.nullValue();
                 return;
             }
-            writer.value(endpoint.protocol.name() + " " +
-                    endpoint.url + " " +
+            writer.value(endpoint.api.name() + " " +
+                    endpoint.dns + " " +
                     endpoint.ipv4 + " " +
                     endpoint.ipv6 + " " +
                     endpoint.port);
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/gson/MultimapTypeAdapter.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/gson/MultimapTypeAdapter.java
index d8b5a9b2855dc7868f0f086aa64bbf9cafb8f1b3..fbd94e04174312d7050edbcbb67fe96b82d1f7f8 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/gson/MultimapTypeAdapter.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/gson/MultimapTypeAdapter.java
@@ -58,7 +58,7 @@ public class MultimapTypeAdapter implements JsonSerializer<Multimap>, JsonDeseri
         Preconditions.checkArgument(multimapType instanceof ParameterizedType);
         final ParameterizedType paramType = (ParameterizedType)multimapType;
         final Type[] typeArguments = paramType.getActualTypeArguments();
-        Preconditions.checkArgument(2 == typeArguments.length, "Type must contain exactly 2 type arguments.");
+        Preconditions.checkArgument(2 == typeArguments.length, "Type must contain exactly 2 sortType arguments.");
 
         final ParameterizedTypeImpl valueType = new ParameterizedTypeImpl(Collection.class, null, typeArguments[1]);
         final ParameterizedTypeImpl mapType = new ParameterizedTypeImpl(Map.class, null, typeArguments[0], valueType);
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/EndpointDeserializer.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/EndpointDeserializer.java
index 2681d5df51fa4855c21a34d6026e537e38927d26..7a877fe64949a229086429b08e7cd279ccc7bea3 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/EndpointDeserializer.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/bma/jackson/EndpointDeserializer.java
@@ -3,45 +3,124 @@ package org.duniter.core.client.model.bma.jackson;
 import com.fasterxml.jackson.core.JsonParser;
 import com.fasterxml.jackson.databind.DeserializationContext;
 import com.fasterxml.jackson.databind.JsonDeserializer;
-import org.apache.http.conn.util.InetAddressUtils;
-import org.duniter.core.client.model.bma.EndpointProtocol;
+import org.duniter.core.client.model.bma.EndpointApi;
 import org.duniter.core.client.model.bma.NetworkPeering;
+import org.duniter.core.util.StringUtils;
+import org.duniter.core.util.http.InetAddressUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
 
 /**
  * Created by blavenie on 07/12/16.
  */
 public class EndpointDeserializer extends JsonDeserializer<NetworkPeering.Endpoint> {
+
+    private static final Logger log = LoggerFactory.getLogger(EndpointDeserializer.class);
+
+    public static final String EP_END_REGEXP = "(?:[ ]+([a-z0-9-_]+[.][a-z0-9-_.]*))?(?:[ ]+([0-9.]+))?(?:[ ]+([0-9a-f:]+))?(?:[ ]+([0-9]+))$";
+    public static final String BMA_API_REGEXP = "^BASIC_MERKLED_API" + EP_END_REGEXP;
+    public static final String BMAS_API_REGEXP = "^BMAS" + EP_END_REGEXP;
+    public static final String OTHER_API_REGEXP = "^([A-Z_-]+)" + EP_END_REGEXP;
+
+    private Pattern bmaPattern;
+    private Pattern bmasPattern;
+    private Pattern otherApiPattern;
+
+    public EndpointDeserializer() {
+        bmaPattern = Pattern.compile(BMA_API_REGEXP);
+        bmasPattern = Pattern.compile(BMAS_API_REGEXP);
+        otherApiPattern = Pattern.compile(OTHER_API_REGEXP);
+    }
+
     @Override
     public NetworkPeering.Endpoint deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException {
 
         String ept = jp.getText();
-        ArrayList<String> parts = new ArrayList<>(Arrays.asList(ept.split(" ")));
+
         NetworkPeering.Endpoint endpoint = new NetworkPeering.Endpoint();
-        endpoint.port = Integer.parseInt(parts.remove(parts.size() - 1));
-        for (String word : parts) {
-            if (InetAddressUtils.isIPv4Address(word)) {
-                endpoint.ipv4 = word;
-            } else if (InetAddressUtils.isIPv6Address(word)) {
-                endpoint.ipv6 = word;
-            } else if (word.startsWith("http")) {
-                endpoint.url = word;
-            } else {
-                try {
-                    endpoint.protocol = EndpointProtocol.valueOf(word);
-                } catch (IllegalArgumentException e) {
-                    // skip this part
+
+        // BMA API
+        Matcher mather = bmaPattern.matcher(ept);
+        if (mather.matches()) {
+            endpoint.api = EndpointApi.BASIC_MERKLED_API;
+
+            for(int i=1; i<=mather.groupCount(); i++) {
+                String word = mather.group(i);
+
+                if (StringUtils.isNotBlank(word)) {
+                    if (InetAddressUtils.isIPv4Address(word)) {
+                        endpoint.ipv4 = word;
+                    } else if (InetAddressUtils.isIPv6Address(word)) {
+                        endpoint.ipv6 = word;
+                    } else if (i == mather.groupCount() && word.matches("\\d+")){
+                        endpoint.port = Integer.parseInt(word);
+                    } else {
+                        endpoint.dns = word;
+                    }
+                }
+            }
+
+            return endpoint;
+        }
+
+        // BMAS API
+        mather = bmasPattern.matcher(ept);
+        if (mather.matches()) {
+            endpoint.api = EndpointApi.BMAS;
+
+            for(int i=1; i<=mather.groupCount(); i++) {
+                String word = mather.group(i);
+
+                if (StringUtils.isNotBlank(word)) {
+                    if (InetAddressUtils.isIPv4Address(word)) {
+                        endpoint.ipv4 = word;
+                    } else if (InetAddressUtils.isIPv6Address(word)) {
+                        endpoint.ipv6 = word;
+                    } else if (i == mather.groupCount() && word.matches("\\d+")){
+                        endpoint.port = Integer.parseInt(word);
+                    } else {
+                        endpoint.dns = word;
+                    }
                 }
             }
+
+            return endpoint;
         }
 
-        if (endpoint.protocol == null) {
-            endpoint.protocol = EndpointProtocol.UNDEFINED;
+        // Other API
+        mather = otherApiPattern.matcher(ept);
+        if (mather.matches()) {
+            try {
+                endpoint.api = EndpointApi.valueOf(mather.group(1));
+            } catch(Exception e) {
+                log.warn("Unable to deserialize endpoint: unknown api [" + mather.group(1) + "]");
+                // not known API: skip
+                return null;
+            }
+
+            for(int i=2; i<=mather.groupCount(); i++) {
+                String word = mather.group(i);
+
+                if (StringUtils.isNotBlank(word)) {
+                    if (InetAddressUtils.isIPv4Address(word)) {
+                        endpoint.ipv4 = word;
+                    } else if (InetAddressUtils.isIPv6Address(word)) {
+                        endpoint.ipv6 = word;
+                    } else if (i == mather.groupCount() && word.matches("\\d+")){
+                        endpoint.port = Integer.parseInt(word);
+                    } else {
+                        endpoint.dns = word;
+                    }
+                }
+            }
+
+            return endpoint;
         }
 
-        return endpoint;
+        return null;
     }
 }
\ No newline at end of file
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/DeleteRecord.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/DeleteRecord.java
index c09fe0d125212863155ea662b67b071fa39676f1..d87291d7e20b990145f5dc11539bce7ecea63dbd 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/DeleteRecord.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/model/elasticsearch/DeleteRecord.java
@@ -28,7 +28,7 @@ package org.duniter.core.client.model.elasticsearch;
 public class DeleteRecord extends Record {
 
     public static final String PROPERTY_INDEX="index";
-    public static final String PROPERTY_TYPE="type";
+    public static final String PROPERTY_TYPE="sortType";
     public static final String PROPERTY_ID="id";
 
     private String index;
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 5b292b8731a4e8ef782fee97d7e2eb1bbc269d5f..02eb05ccfa62998f4a50656cf5ba93b28e2ba19b 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
@@ -23,107 +23,460 @@ package org.duniter.core.client.model.local;
  */
 
 
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import org.duniter.core.client.model.bma.EndpointApi;
+import org.duniter.core.client.model.bma.NetworkPeering;
+import org.duniter.core.util.Preconditions;
+import org.duniter.core.util.StringUtils;
+import org.duniter.core.util.http.InetAddressUtils;
+
 import java.io.Serializable;
+import java.util.StringJoiner;
 
 public class Peer implements LocalEntity, Serializable {
 
+
+
+    public static Builder newBuilder() {
+        return new Builder();
+    }
+
+    public static class Builder {
+
+        private String api;
+        private String dns;
+        private String ipv4;
+        private String ipv6;
+        private Integer port;
+        private Boolean useSsl;
+        private String pubkey;
+        private String hash;
+        private String currency;
+
+        public Builder() {
+
+        }
+
+        public Builder setApi(String api) {
+            this.api = api;
+            return this;
+        }
+
+        public Builder setDns(String dns) {
+            this.dns = dns;
+            return this;
+        }
+
+        public Builder setIpv4(String ipv4) {
+            this.ipv4 = ipv4;
+            return this;
+        }
+
+        public Builder setIpv6(String ipv6) {
+            this.ipv6 = ipv6;
+            return this;
+        }
+
+        public Builder setPort(int port) {
+            this.port = port;
+            return this;
+        }
+
+        public Builder setUseSsl(boolean useSsl) {
+            this.useSsl = useSsl;
+            return this;
+        }
+
+        public Builder setCurrency(String currency) {
+            this.currency = currency;
+            return this;
+        }
+
+        public Builder setPubkey(String pubkey) {
+            this.pubkey = pubkey;
+            return this;
+        }
+
+        public Builder setHash(String hash) {
+            this.hash = hash;
+            return this;
+        }
+
+        public Builder setHost(String host) {
+            Preconditions.checkNotNull(host);
+            if (InetAddressUtils.isIPv4Address(host)) {
+                this.ipv4 = host;
+            }
+            else if (InetAddressUtils.isIPv6Address(host)) {
+                this.ipv6 = host;
+            }
+            else {
+                this.dns = host;
+            }
+            return this;
+        }
+
+        public Builder setEndpoint(NetworkPeering.Endpoint source) {
+            Preconditions.checkNotNull(source);
+            if (source.api != null) {
+               setApi(source.api.name());
+            }
+            if (StringUtils.isNotBlank(source.dns)) {
+               setDns(source.dns);
+            }
+            if (StringUtils.isNotBlank(source.ipv4)) {
+               setIpv4(source.ipv4);
+            }
+            if (StringUtils.isNotBlank(source.ipv6)) {
+               setIpv6(source.ipv6);
+            }
+            if (StringUtils.isNotBlank(source.ipv6)) {
+               setHost(source.ipv6);
+            }
+            if (source.port != null) {
+               setPort(source.port);
+            }
+            return this;
+        }
+
+        public Peer build() {
+            int port = this.port != null ? this.port : 80;
+            boolean useSsl = this.useSsl != null ? this.useSsl :
+                    (port == 443 || this.api == EndpointApi.BMAS.name());
+            String api = this.api != null ? this.api : EndpointApi.BASIC_MERKLED_API.name();
+            Peer ep = new Peer(api, dns, ipv4, ipv6, port, useSsl);
+            if (StringUtils.isNotBlank(this.currency)) {
+                ep.setCurrency(this.currency);
+            }
+            if (StringUtils.isNotBlank(this.pubkey)) {
+                ep.setPubkey(this.pubkey);
+            }
+            if (StringUtils.isNotBlank(this.hash)) {
+                ep.setHash(this.hash);
+            }
+            return ep;
+        }
+
+    }
+
+
+    // Local entity attribute (only used for local DB)
     private Long id;
     private Long currencyId;
+
+    private String api;
+    private String dns;
+    private String ipv4;
+    private String ipv6;
+
+    private String url;
     private String host;
+    private String pubkey;
+
+    private String hash;
+    private String currency;
+
+    private Stats stats = new Stats();
+
     private int port;
     private boolean useSsl;
-    private String url; // computed
 
     public Peer() {
         // default constructor, need for de-serialization
     }
 
-    public Peer(String host, int port) {
-        this.host = host;
-        this.port = port;
-        this.useSsl = (port == 443);
-        this.url = computeUrl(this.host, this.port, this.useSsl);
+    /**
+     * @deprecated Use Builder instead
+     * @param host Can be a ipv4, ipv6 or a dns
+     * @param port any port, or null (default: 80)
+     */
+    @Deprecated
+    public Peer(String host, Integer port) {
+        this.api = EndpointApi.BASIC_MERKLED_API.name();
+        if (InetAddressUtils.isIPv4Address(host)) {
+            this.ipv4 = host;
+        }
+        if (InetAddressUtils.isIPv6Address(host)) {
+            this.ipv6 = host;
+        }
+        else {
+            this.dns = host;
+        }
+        this.port = port != null ? port : 80;
+        this.useSsl = (port == 443 || this.api == EndpointApi.BMAS.name());
+        init();
     }
 
-    public Peer(String host, int port, boolean useSsl) {
-        this.host = host;
+    public Peer(String api, String dns, String ipv4, String ipv6, int port, boolean useSsl) {
+        this.api = api;
+        this.dns = StringUtils.isNotBlank(dns) ? dns : null;
+        this.ipv4 = StringUtils.isNotBlank(ipv4) ? ipv4 : null;
+        this.ipv6 = StringUtils.isNotBlank(ipv6) ? ipv6 : null;
         this.port = port;
         this.useSsl = useSsl;
-        this.url = computeUrl(this.host, this.port, this.useSsl);
+        init();
     }
 
-    public String getHost() {
-        return host;
-    }
-
-    public int getPort() {
-        return port;
-    }
-
-    public String getUrl() {
-        return url;
+    protected void init() {
+        // If SSL: prefer DNS name (should be defined in SSL certificate)
+        // else (if define) use ipv4 (if NOT local IP)
+        // else (if define) use dns
+        // else (if define) use ipv6
+        this.host = ((port == 443 || useSsl) && dns != null) ? dns :
+                (ipv4 != null && InetAddressUtils.isNotLocalIPv4Address(ipv4) ? ipv4 :
+                    (dns != null ? dns :
+                        (ipv6 != null ? "[" + ipv6 + "]" : "")));
+        String protocol = (port == 443 || useSsl) ? "https" : "http";
+        this.url = protocol + "://" + this.host + (port != 80 ? (":" + port) : "");
     }
 
+    @JsonIgnore
     public Long 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 setPort(int port) {
-        this.port = port;
-        if (port == 443) {
-            this.useSsl = true;
-        }
-        this.url = computeUrl(this.host, this.port, this.useSsl);
+    public String getApi() {
+        return api;
     }
 
-    public void setHost(String host) {
-        this.host = host;
-        this.url = computeUrl(this.host, this.port, this.useSsl);
+    public String getDns() {
+        return dns;
+    }
+
+    public void setDns(String dns) {
+        this.dns = dns;
+        init();
+    }
+
+    public String getIpv4() {
+        return ipv4;
+    }
+
+    public void setIpv4(String ipv4) {
+        this.ipv4 = ipv4;
+        init();
+    }
+
+    public String getIpv6() {
+        return ipv6;
+    }
+
+    public void setIpv6(String ipv6) {
+        this.ipv6 = ipv6;
+        init();
+    }
+
+    public int getPort() {
+        return port;
+    }
+
+    public void setPort(int port) {
+        this.port = port;
+        init();
     }
 
     public boolean isUseSsl() {
-        return this.useSsl;
+        return useSsl;
     }
 
     public void setUseSsl(boolean useSsl) {
         this.useSsl = useSsl;
-        this.url = computeUrl(this.host, this.port, this.useSsl);
+        init();
     }
 
-    public String toString() {
-        return new StringBuilder().append(host)
-                .append(":")
-                .append(port)
-                .append(useSsl ? "[+SSL]" : "")
-                .toString();
+    public String getPubkey() {
+        return pubkey;
+    }
+
+    public void setPubkey(String pubkey) {
+        this.pubkey = pubkey;
+    }
+
+    @JsonIgnore
+    public String getHost() {
+        return this.host; // computed in init()
+    }
+
+    @JsonIgnore
+    public String getUrl() {
+        return this.url; // computed in init()
+    }
+
+    public String getHash() {
+        return hash;
+    }
+
+    public void setHash(String hash) {
+        this.hash = hash;
+    }
+
+    public String getCurrency() {
+        return currency;
+    }
+
+    public void setCurrency(String currency) {
+        this.currency = currency;
+    }
+
+    @JsonIgnore
+    public Stats getStats() {
+        return stats;
     }
 
-    @Override
-    public boolean equals(Object o) {
-        if (o == null) {
-            return false;
+    @JsonIgnore
+    public void setStats(Stats stats) {
+        this.stats = stats;
+    }
+
+    public String toString() {
+        StringJoiner joiner = new StringJoiner(" ");
+        if (api != null) {
+            joiner.add(api);
+        }
+        if (dns != null) {
+            joiner.add(dns);
+        }
+        if (ipv4 != null) {
+            joiner.add(ipv4);
         }
-        if (id != null && o instanceof Peer) {
-            return id.equals(((Peer)o).getId());
+        if (ipv6 != null) {
+            joiner.add(ipv6);
         }
-        return super.equals(o);
+        if (port != 80) {
+            joiner.add(String.valueOf(port));
+        }
+        return joiner.toString();
+    }
+
+    public enum PeerStatus {
+        UP,
+        DOWN,
+        ERROR
     }
 
-    /* -- Internal methods -- */
+    public static class Stats {
+        private String version;
+        private PeerStatus status = PeerStatus.UP; // default
+        private Integer blockNumber;
+        private String blockHash;
+        private String error;
+        private Long medianTime;
+        private Integer hardshipLevel;
+        private boolean isMainConsensus = false;
+        private boolean isForkConsensus = false;
+        private Double consensusPct = 0d;
+        private String uid;
+
+        public Stats() {
+
+        }
+
+        public PeerStatus getStatus() {
+            return status;
+        }
+
+        @JsonIgnore
+        public boolean isReacheable() {
+            return status != null && status == PeerStatus.UP;
+        }
+
+        public void setStatus(PeerStatus status) {
+            this.status = status;
+        }
+
+        public String getError() {
+            return error;
+        }
 
-    protected String computeUrl(String host, int port, boolean useSsl) {
-        return String.format("%s://%s:%s", (useSsl ? "https" : "http"), host, port);
+        public void setError(String error) {
+            this.error = error;
+        }
+
+        public String getVersion() {
+            return version;
+        }
+
+        public void setVersion(String version) {
+            this.version = version;
+        }
+
+        public Integer getBlockNumber() {
+            return blockNumber;
+        }
+
+        public void setBlockNumber(Integer blockNumber) {
+            this.blockNumber = blockNumber;
+        }
+
+        public String getBlockHash() {
+            return blockHash;
+        }
+
+        public void setBlockHash(String blockHash) {
+            this.blockHash = blockHash;
+        }
+
+        public Long getMedianTime() {
+            return medianTime;
+        }
+
+        public void setMedianTime(Long medianTime) {
+            this.medianTime = medianTime;
+        }
+
+        public boolean isMainConsensus() {
+            return isMainConsensus;
+        }
+
+        public void setMainConsensus(boolean mainConsensus) {
+            this.isMainConsensus = mainConsensus;
+        }
+
+        public boolean isForkConsensus() {
+            return isForkConsensus;
+        }
+
+        public void setForkConsensus(boolean forkConsensus) {
+            this.isForkConsensus = forkConsensus;
+        }
+
+        public Double getConsensusPct() {
+            return consensusPct;
+        }
+
+        public void setConsensusPct(Double consensusPct) {
+            this.consensusPct = consensusPct;
+        }
+
+        public Integer getHardshipLevel() {
+            return hardshipLevel;
+        }
+
+        public void setHardshipLevel(Integer hardshipLevel) {
+            this.hardshipLevel = hardshipLevel;
+        }
+
+        public String getUid() {
+            return uid;
+        }
+
+        public void setUid(String uid) {
+            this.uid = uid;
+        }
     }
 }
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/HttpServiceImpl.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/HttpServiceImpl.java
index ac46d141a7a8d8a9bdfe0dc5c9c91124bb1cb8f7..eaa8ce61df003f3f22a3b760b88d2a7c1f8e01d9 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/HttpServiceImpl.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/HttpServiceImpl.java
@@ -24,7 +24,6 @@ package org.duniter.core.client.service;
 
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.google.common.base.Joiner;
-import org.apache.commons.io.IOUtils;
 import org.apache.http.HttpResponse;
 import org.apache.http.HttpStatus;
 import org.apache.http.client.HttpClient;
@@ -38,13 +37,13 @@ import org.apache.http.impl.client.HttpClients;
 import org.apache.http.util.EntityUtils;
 import org.duniter.core.beans.InitializingBean;
 import org.duniter.core.client.config.Configuration;
+import org.duniter.core.client.model.bma.Constants;
 import org.duniter.core.client.model.bma.Error;
 import org.duniter.core.client.model.bma.jackson.JacksonUtils;
 import org.duniter.core.client.model.local.Peer;
 import org.duniter.core.client.service.exception.*;
 import org.duniter.core.exception.TechnicalException;
 import org.duniter.core.util.StringUtils;
-import org.duniter.core.util.cache.Cache;
 import org.duniter.core.util.cache.SimpleCache;
 import org.nuiton.i18n.I18n;
 import org.slf4j.Logger;
@@ -93,6 +92,7 @@ public class HttpServiceImpl implements HttpService, Closeable, InitializingBean
     protected void initCaches() {
         Configuration config = Configuration.instance();
         int cacheTimeInMillis = config.getNetworkCacheTimeInMillis();
+        int defaultTimeout = config.getNetworkTimeout();
 
         requestConfigCache = new SimpleCache<Integer, RequestConfig>(cacheTimeInMillis) {
             @Override
@@ -162,7 +162,7 @@ public class HttpServiceImpl implements HttpService, Closeable, InitializingBean
     }
 
     public <T> T executeRequest(Peer peer, String absolutePath, Class<? extends T> resultClass)  {
-        HttpGet httpGet = new HttpGet(getPath(peer, absolutePath));
+        HttpGet httpGet = new HttpGet(peer.getUrl() + absolutePath);
         return executeRequest(httpClientCache.get(0), httpGet, resultClass);
     }
 
@@ -228,12 +228,17 @@ public class HttpServiceImpl implements HttpService, Closeable, InitializingBean
 
     @SuppressWarnings("unchecked")
     protected <T> T executeRequest(HttpClient httpClient, HttpUriRequest request, Class<? extends T> resultClass, Class<?> errorClass)  {
+        return executeRequest(httpClient, request, resultClass, errorClass, 5);
+    }
+
+    protected <T> T executeRequest(HttpClient httpClient, HttpUriRequest request, Class<? extends T> resultClass, Class<?> errorClass, int retryCount)  {
         T result = null;
 
         if (debug) {
             log.debug("Executing request : " + request.getRequestLine());
         }
 
+        boolean retry = false;
         HttpResponse response = null;
         try {
             response = httpClient.execute(request);
@@ -271,6 +276,11 @@ public class HttpServiceImpl implements HttpService, Closeable, InitializingBean
                     catch(IOException e) {
                         throw new HttpBadRequestException(I18n.t("duniter4j.client.status", response.getStatusLine().toString()));
                     }
+
+                case HttpStatus.SC_SERVICE_UNAVAILABLE:
+                case Constants.HttpStatus.SC_TOO_MANY_REQUESTS:
+                    retry = true;
+                    break;
                 default:
                     throw new TechnicalException(I18n.t("duniter4j.client.status", request.toString(), response.getStatusLine().toString()));
             }
@@ -296,6 +306,23 @@ public class HttpServiceImpl implements HttpService, Closeable, InitializingBean
             }
         }
 
+        // HTTP requests limit exceed, retry when possible
+        if (retry) {
+            if (retryCount > 0) {
+                log.debug(String.format("Service unavailable: waiting [%s ms] before retrying...", Constants.Config.TOO_MANY_REQUEST_RETRY_TIME));
+                try {
+                    Thread.sleep(Constants.Config.TOO_MANY_REQUEST_RETRY_TIME);
+                } catch (InterruptedException e) {
+                    throw new TechnicalException(I18n.t("duniter4j.client.status", request.toString(), response.getStatusLine().toString()));
+                }
+                // iterate
+                return executeRequest(httpClient, request, resultClass, errorClass, retryCount - 1);
+            }
+            else {
+                throw new TechnicalException(I18n.t("duniter4j.client.status", request.toString(), response.getStatusLine().toString()));
+            }
+        }
+
         return result;
     }
 
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/ServiceLocator.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/ServiceLocator.java
index e0f14a353c8b514c68bada786d197d278d4bb241..38107551c7cf74de53406be06771c33864ee5861 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/ServiceLocator.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/ServiceLocator.java
@@ -25,9 +25,13 @@ package org.duniter.core.client.service;
 
 import org.duniter.core.beans.Bean;
 import org.duniter.core.beans.BeanFactory;
-import org.duniter.core.client.service.bma.*;
+import org.duniter.core.client.service.bma.BlockchainRemoteService;
+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.elasticsearch.CurrencyRegistryRemoteService;
 import org.duniter.core.client.service.local.CurrencyService;
+import org.duniter.core.client.service.local.NetworkService;
 import org.duniter.core.client.service.local.PeerService;
 import org.duniter.core.service.CryptoService;
 import org.duniter.core.service.MailService;
@@ -140,10 +144,14 @@ public class ServiceLocator implements Closeable {
         return getBean(CurrencyRegistryRemoteService.class);
     }
 
-    public MailService getMaiLService() {
+    public MailService getMailService() {
         return getBean(MailService.class);
     }
 
+    public NetworkService getNetworkService() {
+        return getBean(NetworkService.class);
+    }
+
     public <S extends Bean> S getBean(Class<S> clazz) {
         if (beanFactory == null) {
             initBeanFactory();
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 8fb4bf2bb97f8bc3cf4aac02549ff368fadeef83..8e92845eb8810efaeea6b24bc6570f56061d5db0 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
@@ -76,7 +76,7 @@ public interface BlockchainRemoteService extends Service {
 
     /**
      * Retrieve the dividend of a block, by id (from 0 to current).
-     * Usefull method to avoid to deserialize all the block
+     * Usefull method to avoid to deserialize allOfToList the block
      *
      * @param currencyId
      * @param number
@@ -222,8 +222,24 @@ public interface BlockchainRemoteService extends Service {
      */
     Map<Integer, Long> getUDs(long currencyId, long startOffset);
 
+    /**
+     * Listening new block event
+     * @param currencyId
+     * @param listener
+     * @return
+     */
     WebsocketClientEndpoint addBlockListener(long currencyId, WebsocketClientEndpoint.MessageListener listener);
 
     WebsocketClientEndpoint addBlockListener(Peer peer, WebsocketClientEndpoint.MessageListener listener);
 
+    /**
+     * Listening new peer event
+     * @param currencyId
+     * @param listener
+     * @return
+     */
+    WebsocketClientEndpoint addPeerListener(long currencyId, WebsocketClientEndpoint.MessageListener listener);
+
+    WebsocketClientEndpoint addPeerListener(Peer peer, WebsocketClientEndpoint.MessageListener listener);
+
 }
\ No newline at end of file
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 d431342e9ac914f8a0c32ff271fac385428b52d8..25c8b7a16c4cd5abab17afa43401eec92a69a157 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,6 +22,7 @@ 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;
@@ -76,6 +77,11 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement
 
     public static final String URL_MEMBERSHIP_SEARCH = URL_BASE + "/memberships/%s";
 
+    public static final String URL_WS_BLOCK = "/ws/block";
+
+    public static final String URL_WS_PEER = "/ws/peer";
+
+    private ObjectMapper objectMapper;
 
     private NetworkRemoteService networkRemoteService;
 
@@ -88,7 +94,7 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement
     // Cache on blockchain parameters
     private Cache<Long, BlockchainParameters> mParametersCache;
 
-    private Map<URI, WebsocketClientEndpoint> blockWsEndPoints = new HashMap<>();
+    private Map<URI, WebsocketClientEndpoint> wsEndPoints = new HashMap<>();
 
     public BlockchainRemoteServiceImpl() {
         super();
@@ -108,11 +114,11 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement
     public void close() throws IOException {
         super.close();
 
-        if (blockWsEndPoints.size() != 0) {
-            for (WebsocketClientEndpoint clientEndPoint: blockWsEndPoints.values()) {
+        if (wsEndPoints.size() != 0) {
+            for (WebsocketClientEndpoint clientEndPoint: wsEndPoints.values()) {
                 clientEndPoint.close();
             }
-            blockWsEndPoints.clear();
+            wsEndPoints.clear();
         }
     }
 
@@ -561,8 +567,7 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement
 
     @Override
     public WebsocketClientEndpoint addBlockListener(long currencyId, WebsocketClientEndpoint.MessageListener listener) {
-        Peer peer = peerService.getActivePeerByCurrencyId(currencyId);
-        return addBlockListener(peer, listener);
+        return addBlockListener(peerService.getActivePeerByCurrencyId(currencyId), listener);
     }
 
     @Override
@@ -570,8 +575,27 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement
         Preconditions.checkNotNull(peer);
         Preconditions.checkNotNull(listener);
 
-        // Get the websocket endpoint
-        WebsocketClientEndpoint wsClientEndPoint = getWebsocketClientEndpoint(peer, "/ws/block");
+        // Get (or create) the websocket endpoint
+        WebsocketClientEndpoint wsClientEndPoint = getWebsocketClientEndpoint(peer, URL_WS_BLOCK);
+
+        // add listener
+        wsClientEndPoint.registerListener(listener);
+
+        return wsClientEndPoint;
+    }
+
+    @Override
+    public WebsocketClientEndpoint addPeerListener(long currencyId, WebsocketClientEndpoint.MessageListener listener) {
+        return addBlockListener(peerService.getActivePeerByCurrencyId(currencyId), listener);
+    }
+
+    @Override
+    public WebsocketClientEndpoint addPeerListener(Peer peer, WebsocketClientEndpoint.MessageListener listener) {
+        Preconditions.checkNotNull(peer);
+        Preconditions.checkNotNull(listener);
+
+        // Get (or create) the websocket endpoint
+        WebsocketClientEndpoint wsClientEndPoint = getWebsocketClientEndpoint(peer, URL_WS_PEER);
 
         // add listener
         wsClientEndPoint.registerListener(listener);
@@ -807,17 +831,17 @@ public class BlockchainRemoteServiceImpl extends BaseRemoteServiceImpl implement
                     path));
 
             // Get the websocket, or open new one if not exists
-            WebsocketClientEndpoint wsClientEndPoint = blockWsEndPoints.get(wsBlockURI);
+            WebsocketClientEndpoint wsClientEndPoint = wsEndPoints.get(wsBlockURI);
             if (wsClientEndPoint == null || wsClientEndPoint.isClosed()) {
-                log.info(String.format("Starting to listen block from [%s]...", wsBlockURI.toString()));
+                log.info(String.format("Starting to listen on [%s]...", wsBlockURI.toString()));
                 wsClientEndPoint = new WebsocketClientEndpoint(wsBlockURI);
-                blockWsEndPoints.put(wsBlockURI, wsClientEndPoint);
+                wsEndPoints.put(wsBlockURI, wsClientEndPoint);
             }
 
             return wsClientEndPoint;
 
         } catch (URISyntaxException | ServiceConfigurationError ex) {
-            throw new TechnicalException("could not create URI need for web socket on block: " + ex.getMessage());
+            throw new TechnicalException(String.format("Could not create URI need for web socket [%s]: %s", path, ex.getMessage()));
         }
 
     }
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 9e5da699dc8f689ef28850d97af1c4ea5e67531e..262dd501a4848d698d5a1be067a8bc2468cf0a1a 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
@@ -23,9 +23,11 @@ package org.duniter.core.client.service.bma;
  */
 
 import org.duniter.core.beans.Service;
-import org.duniter.core.client.model.bma.EndpointProtocol;
+import org.duniter.core.client.model.bma.EndpointApi;
 import org.duniter.core.client.model.bma.NetworkPeering;
+import org.duniter.core.client.model.bma.NetworkPeers;
 import org.duniter.core.client.model.local.Peer;
+import org.duniter.core.util.websocket.WebsocketClientEndpoint;
 
 import java.util.List;
 
@@ -38,5 +40,11 @@ public interface NetworkRemoteService extends Service {
 
     List<Peer> getPeers(Peer peer);
 
-    List<Peer> findPeers(Peer peer, String status, EndpointProtocol endpointProtocol, Integer currentBlockNumber, String currentBlockHash);
+    List<String> getPeersLeaves(Peer peer);
+
+    NetworkPeers.Peer getPeerLeaf(Peer peer, String leaf);
+
+    List<Peer> findPeers(Peer peer, String status, EndpointApi endpointApi, Integer currentBlockNumber, String currentBlockHash);
+
+    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 48902b024a07fb7de8b2295e2a93cb51885b4a09..3815be7e913a023c3c5c0372d678f69cb4fa7717 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
@@ -22,22 +22,31 @@ package org.duniter.core.client.service.bma;
  * #L%
  */
 
-import java.util.ArrayList;
-import java.util.List;
-
-import org.duniter.core.client.model.bma.EndpointProtocol;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.*;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.duniter.core.client.model.bma.EndpointApi;
 import org.duniter.core.client.model.bma.NetworkPeering;
 import org.duniter.core.client.model.bma.NetworkPeers;
+import org.duniter.core.client.model.bma.jackson.JacksonUtils;
 import org.duniter.core.client.model.local.Peer;
-import org.duniter.core.util.ObjectUtils;
+import org.duniter.core.exception.TechnicalException;
 import org.duniter.core.util.Preconditions;
 import org.duniter.core.util.StringUtils;
+import org.duniter.core.util.websocket.WebsocketClientEndpoint;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * Created by eis on 05/02/15.
  */
 public class NetworkRemoteServiceImpl extends BaseRemoteServiceImpl implements NetworkRemoteService{
 
+    private static final Logger log = LoggerFactory.getLogger(NetworkRemoteServiceImpl.class);
 
     public static final String URL_BASE = "/network";
 
@@ -47,10 +56,20 @@ public class NetworkRemoteServiceImpl extends BaseRemoteServiceImpl implements N
 
     public static final String URL_PEERING_PEERS = URL_PEERING + "/peers";
 
-    public static final String URL_PEERING_PEERS_LEAF = URL_PEERING + "/peers?leaf=";
+    public static final String URL_PEERING_PEERS_LEAVES = URL_PEERING_PEERS + "?leaves=true";
+
+    public static final String URL_PEERING_PEERS_LEAF = URL_PEERING_PEERS + "?leaf=";
+
+    public static final String URL_WS_PEER = "/ws/peer";
+
+    protected ObjectMapper objectMapper;
+
+    private Map<URI, WebsocketClientEndpoint> wsEndPoints = new HashMap<>();
 
     public NetworkRemoteServiceImpl() {
         super();
+
+        objectMapper = JacksonUtils.newObjectMapper();
     }
 
     public NetworkPeering getPeering(Peer peer) {
@@ -58,13 +77,60 @@ public class NetworkRemoteServiceImpl extends BaseRemoteServiceImpl implements N
         return result;
     }
 
+    @Override
+    public void close() throws IOException {
+        super.close();
+
+        if (wsEndPoints.size() != 0) {
+            for (WebsocketClientEndpoint clientEndPoint: wsEndPoints.values()) {
+                clientEndPoint.close();
+            }
+            wsEndPoints.clear();
+        }
+    }
+
     @Override
     public List<Peer> getPeers(Peer peer) {
         return findPeers(peer, null, null, null, null);
     }
 
     @Override
-    public List<Peer> findPeers(Peer peer, String status, EndpointProtocol endpointProtocol, Integer currentBlockNumber, String currentBlockHash) {
+    public List<String> getPeersLeaves(Peer peer) {
+        Preconditions.checkNotNull(peer);
+
+        List<String> result = new ArrayList<>();
+        JsonNode jsonNode= httpService.executeRequest(peer, URL_PEERING_PEERS_LEAVES, JsonNode.class);
+        jsonNode.get("leaves").forEach(jsonNode1 -> {
+            result.add(jsonNode1.asText());
+        });
+        return result;
+    }
+
+    @Override
+    public NetworkPeers.Peer getPeerLeaf(Peer peer, String leaf) {
+        Preconditions.checkNotNull(peer);
+        JsonNode jsonNode = httpService.executeRequest(peer, URL_PEERING_PEERS_LEAF + leaf, JsonNode.class);
+        NetworkPeers.Peer result = null;
+
+        try {
+
+            if (jsonNode.has("leaf")) {
+                jsonNode = jsonNode.get("leaf");
+                if (jsonNode.has("value")) {
+                    jsonNode = jsonNode.get("value");
+                    String json = objectMapper.writeValueAsString(jsonNode);
+                    result = objectMapper.readValue(json, NetworkPeers.Peer.class);
+                }
+            }
+        } catch(IOException e) {
+            throw new TechnicalException(e);
+        }
+
+        return result;
+    }
+
+    @Override
+    public List<Peer> findPeers(Peer peer, String status, EndpointApi endpointApi, Integer currentBlockNumber, String currentBlockHash) {
         Preconditions.checkNotNull(peer);
 
         List<Peer> result = new ArrayList<Peer>();
@@ -80,13 +146,15 @@ public class NetworkRemoteServiceImpl extends BaseRemoteServiceImpl implements N
 
                 for (NetworkPeering.Endpoint endpoint : remotePeer.endpoints) {
 
-                    match = endpointProtocol == null || endpointProtocol == endpoint.protocol;
+                    match = endpointApi == null || endpointApi == endpoint.api;
 
-                    if (match) {
-                        Peer childPeer = toPeer(endpoint);
-                        if (childPeer != null) {
-                            result.add(childPeer);
-                        }
+                    if (match && endpoint != null) {
+                        Peer childPeer = Peer.newBuilder()
+                                .setCurrency(remotePeer.getCurrency())
+                                .setPubkey(remotePeer.getPubkey())
+                                .setEndpoint(endpoint)
+                                .build();
+                        result.add(childPeer);
                     }
 
                 }
@@ -96,25 +164,22 @@ public class NetworkRemoteServiceImpl extends BaseRemoteServiceImpl implements N
         return result;
     }
 
-    /* -- Internal methods -- */
+    @Override
+    public WebsocketClientEndpoint addPeerListener(Peer peer, WebsocketClientEndpoint.MessageListener listener, boolean autoReconnect) {
+        Preconditions.checkNotNull(peer);
+        Preconditions.checkNotNull(listener);
 
-    protected Peer toPeer(NetworkPeering.Endpoint source) {
-        Peer target = new Peer();
-        if (StringUtils.isNotBlank(source.ipv4)) {
-            target.setHost(source.ipv4);
-        } else if (StringUtils.isNotBlank(source.ipv6)) {
-            target.setHost(source.ipv6);
-        } else if (StringUtils.isNotBlank(source.url)) {
-            target.setHost(source.url);
-        } else {
-            target = null;
-        }
-        if (target != null && source.port != null) {
-            target.setPort(source.port);
-        }
-        return target;
+        // Get (or create) the websocket endpoint
+        WebsocketClientEndpoint wsClientEndPoint = getWebsocketClientEndpoint(peer, URL_WS_PEER, autoReconnect);
+
+        // add listener
+        wsClientEndPoint.registerListener(listener);
+
+        return wsClientEndPoint;
     }
 
+    /* -- Internal methods -- */
+
     protected Integer parseBlockNumber(NetworkPeers.Peer remotePeer) {
         Preconditions.checkNotNull(remotePeer);
 
@@ -148,4 +213,29 @@ public class NetworkRemoteServiceImpl extends BaseRemoteServiceImpl implements N
         String hash = remotePeer.block.substring(index+1);
         return hash;
     }
+
+    public WebsocketClientEndpoint getWebsocketClientEndpoint(Peer peer, String path, boolean autoReconnect) {
+
+        try {
+            URI wsBlockURI = new URI(String.format("%s://%s:%s%s",
+                    peer.isUseSsl() ? "wss" : "ws",
+                    peer.getHost(),
+                    peer.getPort(),
+                    path));
+
+            // Get the websocket, or open new one if not exists
+            WebsocketClientEndpoint wsClientEndPoint = wsEndPoints.get(wsBlockURI);
+            if (wsClientEndPoint == null || wsClientEndPoint.isClosed()) {
+                log.info(String.format("Starting to listen on [%s]...", wsBlockURI.toString()));
+                wsClientEndPoint = new WebsocketClientEndpoint(wsBlockURI, autoReconnect);
+                wsEndPoints.put(wsBlockURI, wsClientEndPoint);
+            }
+
+            return wsClientEndPoint;
+
+        } catch (URISyntaxException | ServiceConfigurationError ex) {
+            throw new TechnicalException(String.format("Could not create URI need for web socket [%s]: %s", path, ex.getMessage()));
+        }
+
+    }
 }
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 b927f4e5e19d50ee7daf21e8cde4ebf17744116f..a6ad175ccf63ced59fd8ba9c7ecd1169c665a873 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
@@ -308,7 +308,9 @@ public class TransactionRemoteServiceImpl extends BaseRemoteServiceImpl implemen
 		}
 
 		// Comment
-		sb.append("Comment: ").append(comments).append('\n');
+		sb.append("Comment: ");
+		if (comments != null) sb.append(comments);
+		sb.append('\n');
 
 		return sb.toString();
 	}
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 09fbe9c22895cde0847a613f9b7ba48b704b28b6..0b8aca4c0fb407f1f8cca179b9503fab95618ff2 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
@@ -32,6 +32,7 @@ import org.duniter.core.client.model.local.Wallet;
 
 import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 public interface WotRemoteService extends Service {
@@ -64,6 +65,10 @@ public interface WotRemoteService extends Service {
 
     String getSignedIdentity(String currency, byte[] pubKey, byte[] secKey, String uid, String blockUid);
 
+    Map<String, String> getMembersUids(long currencyId);
+
+    Map<String, String> getMembersUids(Peer peer);
+
     void sendIdentity(long currencyId, byte[] pubKey, byte[] secKey, String uid, String blockUid);
 
     void sendIdentity(Peer peer, String currency, byte[] pubKey, byte[] secKey, String uid, String blockUid);
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 3e42c34609db280f3699b2496d543935b8dd1169..863e6e07aa8de33b579d75095e158a52cee4c1b7 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
@@ -22,6 +22,7 @@ package org.duniter.core.client.service.bma;
  * #L%
  */
 
+import com.fasterxml.jackson.databind.JsonNode;
 import org.duniter.core.client.model.ModelUtils;
 import org.duniter.core.client.model.bma.*;
 import org.duniter.core.client.model.local.Certification;
@@ -54,6 +55,8 @@ public class WotRemoteServiceImpl extends BaseRemoteServiceImpl implements WotRe
 
     public static final String URL_ADD = URL_BASE + "/add";
 
+    public static final String URL_MEMBERS = URL_BASE + "/members";
+
     public static final String URL_LOOKUP = URL_BASE + "/lookup/%s";
 
     public static final String URL_REQUIREMENT = URL_BASE+"/requirements/%s";
@@ -119,6 +122,35 @@ public class WotRemoteServiceImpl extends BaseRemoteServiceImpl implements WotRe
 
     }
 
+    public Map<String, String> getMembersUids(long currencyId) {
+        // get /wot/members
+        JsonNode json = executeRequest(currencyId, URL_MEMBERS, JsonNode.class);
+
+        if (json == null  || !json.has("results")) return null;
+
+        Map<String, String> result = new HashMap<>();
+
+        json.get("results").forEach(entry -> {
+            result.put(entry.get("pubkey").asText(), entry.get("uid").asText());
+        });
+        return result;
+    }
+
+    public Map<String, String> getMembersUids(Peer peer) {
+        // get /wot/members
+        JsonNode json = executeRequest(peer, URL_MEMBERS, JsonNode.class);
+
+        if (json == null || !json.has("results")) return null;
+
+        Map<String, String> result = new HashMap<>();
+
+        json.get("results").forEach(entry -> {
+            result.put(entry.get("pubkey").asText(), entry.get("uid").asText());
+        });
+        return result;
+    }
+
+
     public void getRequirments(long currencyId, String pubKey) {
         if (log.isDebugEnabled()) {
             log.debug(String.format("Try to find user requirements on [%s]", pubKey));
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/elasticsearch/CurrencyRegistryRemoteServiceImpl.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/elasticsearch/CurrencyRegistryRemoteServiceImpl.java
index b2ab891fd7ac2c1f008cffc35ae68a1d555d9733..47c81eef9350f3f06556a64007ca7f4aa1f240cb 100644
--- a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/elasticsearch/CurrencyRegistryRemoteServiceImpl.java
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/elasticsearch/CurrencyRegistryRemoteServiceImpl.java
@@ -58,7 +58,7 @@ public class CurrencyRegistryRemoteServiceImpl extends BaseRemoteServiceImpl imp
     public void afterPropertiesSet() {
         super.afterPropertiesSet();
         config = Configuration.instance();
-        peer = new Peer(config.getNodeElasticSearchHost(), config.getNodeElasticSearchPort());
+        peer = Peer.newBuilder().setHost(config.getNodeElasticSearchHost()).setPort(config.getNodeElasticSearchPort()).build();
     }
 
     @Override
@@ -97,7 +97,7 @@ public class CurrencyRegistryRemoteServiceImpl extends BaseRemoteServiceImpl imp
     @Override
     public List<String> getAllCurrencyNames() {
         if (log.isDebugEnabled()) {
-            log.debug("Getting all currency names...");
+            log.debug("Getting allOfToList currency names...");
         }
 
         // get currency
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 3dbdd24659484c7f877680459bdcc7fc52af800a..f9cbe365dfb7a0cd06de41f09eae007717013fc9 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
@@ -67,7 +67,7 @@ public interface CurrencyService extends Service {
     int getCurrencyCount();
 
     /**
-     * Fill all cache need for currencies
+     * Fill allOfToList cache need for currencies
      * @param context
      */
     void loadCache(long accountId);
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 7ffc39c3de9148c8927682d789f282f5bcc8c771..91b9be1f13544a194b66bc144936cdfee35c57e6 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
@@ -167,7 +167,7 @@ public class CurrencyServiceImpl implements CurrencyService, InitializingBean {
 
 
     /**
-     * Fill all cache need for currencies
+     * Fill allOfToList cache need for currencies
      * @param accountId
      */
     public void loadCache(long accountId) {
diff --git a/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/NetworkService.java b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/NetworkService.java
new file mode 100644
index 0000000000000000000000000000000000000000..501b5a87f3c11e88eb4631dc6e84e49dd3a541b7
--- /dev/null
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/NetworkService.java
@@ -0,0 +1,57 @@
+package org.duniter.core.client.service.local;
+
+import org.duniter.core.beans.Service;
+import org.duniter.core.client.model.local.Peer;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.function.Predicate;
+
+/**
+ * Created by blavenie on 20/03/17.
+ */
+public interface NetworkService extends Service {
+
+    class Sort {
+        public SortType sortType;
+        public boolean sortAsc;
+    }
+
+    class Filter {
+        public FilterType filterType;
+        public Peer.PeerStatus filterStatus;
+        public Boolean filterSsl;
+        public List<String> filterEndpoints;
+    }
+
+
+    enum SortType {
+        UID,
+        PUBKEY,
+        API,
+        HARDSHIP,
+        BLOCK_NUMBER
+    }
+
+    enum FilterType {
+        MEMBER, // Only members peers
+        MIRROR // Only mirror peers
+    }
+
+    List<Peer> getPeers(Peer mainPeer);
+
+    List<Peer> getPeers(Peer mainPeer, Filter filter, Sort sort);
+
+    CompletableFuture<List<CompletableFuture<Peer>>> asyncGetPeers(Peer mainPeer, ExecutorService pool) throws ExecutionException, InterruptedException;
+
+    List<Peer> fillPeerStatsConsensus(final List<Peer> peers);
+
+    Predicate<Peer> peerFilter(Filter filter);
+
+    Comparator<Peer> peerComparator(Sort sort);
+
+
+}
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
new file mode 100644
index 0000000000000000000000000000000000000000..6d0d6679c5176639f82adaa3fbc32538501818e3
--- /dev/null
+++ b/duniter4j-core-client/src/main/java/org/duniter/core/client/service/local/NetworkServiceImpl.java
@@ -0,0 +1,444 @@
+package org.duniter.core.client.service.local;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.google.common.collect.ImmutableList;
+import org.duniter.core.client.model.bma.Constants;
+import org.duniter.core.client.model.bma.EndpointApi;
+import org.duniter.core.client.model.bma.NetworkPeering;
+import org.duniter.core.client.model.bma.NetworkPeers;
+import org.duniter.core.client.model.local.Peer;
+import org.duniter.core.client.service.ServiceLocator;
+import org.duniter.core.client.service.bma.BaseRemoteServiceImpl;
+import org.duniter.core.client.service.bma.NetworkRemoteService;
+import org.duniter.core.client.service.bma.WotRemoteService;
+import org.duniter.core.client.service.exception.HttpConnectException;
+import org.duniter.core.client.service.exception.HttpNotFoundException;
+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.core.util.concurrent.CompletableFutures;
+import org.duniter.core.util.websocket.WebsocketClientEndpoint;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+import java.util.concurrent.*;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+/**
+ * Created by blavenie on 20/03/17.
+ */
+public class NetworkServiceImpl extends BaseRemoteServiceImpl implements NetworkService {
+
+    private static final Logger log = LoggerFactory.getLogger(NetworkServiceImpl.class);
+
+    private final static String BMA_URL_STATUS = "/node/summary";
+    private final static String BMA_URL_BLOCKCHAIN_CURRENT = "/blockchain/current";
+    private final static String BMA_URL_BLOCKCHAIN_HARDSHIP = "/blockchain/hardship/";
+
+    private NetworkRemoteService networkRemoteService;
+    private CryptoService cryptoService;
+    private WotRemoteService wotRemoteService;
+
+    public NetworkServiceImpl() {
+    }
+
+    public NetworkServiceImpl(NetworkRemoteService networkRemoteService,
+                              WotRemoteService wotRemoteService,
+                              CryptoService cryptoService) {
+        this();
+        this.networkRemoteService = networkRemoteService;
+        this.wotRemoteService = wotRemoteService;
+        this.cryptoService = cryptoService;
+    }
+
+    @Override
+    public void afterPropertiesSet() {
+        super.afterPropertiesSet();
+        this.networkRemoteService = ServiceLocator.instance().getNetworkRemoteService();
+        this.wotRemoteService = ServiceLocator.instance().getWotRemoteService();
+        this.cryptoService = ServiceLocator.instance().getCryptoService();
+    }
+
+    @Override
+    public List<Peer> getPeers(Peer firstPeer) {
+
+        // Default filter
+        Filter filterDef = new Filter();
+        filterDef.filterType = null;
+        filterDef.filterStatus = Peer.PeerStatus.UP;
+        filterDef.filterEndpoints = ImmutableList.of(EndpointApi.BASIC_MERKLED_API.name(), EndpointApi.BMAS.name());
+
+        // Default sort
+        Sort sortDef = new Sort();
+        sortDef.sortType = null;
+
+        return getPeers(firstPeer, filterDef, sortDef);
+    }
+
+    @Override
+    public List<Peer> getPeers(Peer firstPeer, Filter filter, Sort sort) {
+
+        try {
+            return asyncGetPeers(firstPeer, null)
+                .thenCompose(CompletableFutures::allOfToList)
+                .thenApply(this::fillPeerStatsConsensus)
+                .thenApply(peers -> peers.stream()
+                        // filter, then sort
+                        .filter(peerFilter(filter))
+                        .sorted(peerComparator(sort))
+                        .collect(Collectors.toList()))
+                .thenApply(this::logPeers)
+                .get();
+        } catch (InterruptedException | ExecutionException e) {
+            throw new TechnicalException("Error while loading peers: " + e.getMessage(), e);
+        }
+    }
+
+    @Override
+    public Predicate<Peer> peerFilter(final Filter filter) {
+        return peer -> applyPeerFilter(peer, filter);
+    }
+
+    @Override
+    public Comparator<Peer> peerComparator(final Sort sort) {
+        return Comparator.comparing(peer -> computePeerStatsScore(peer, sort), (score1, score2) -> score2.compareTo(score1));
+    }
+
+    @Override
+    public CompletableFuture<List<CompletableFuture<Peer>>> asyncGetPeers(Peer mainPeer, ExecutorService executor) throws ExecutionException, InterruptedException {
+        Preconditions.checkNotNull(mainPeer);
+
+        log.debug("Loading network peers...");
+
+        final ExecutorService pool = (executor != null) ? executor : ForkJoinPool.commonPool();
+
+        CompletableFuture<List<Peer>> peersFuture = CompletableFuture.supplyAsync(() -> loadPeerLeafs(mainPeer), pool);
+        CompletableFuture<Map<String, String>> memberUidsFuture = CompletableFuture.supplyAsync(() -> wotRemoteService.getMembersUids(mainPeer), pool);
+
+        return CompletableFuture.allOf(
+                new CompletableFuture[] {peersFuture, memberUidsFuture})
+                .thenApply(v -> {
+                    final Map<String, String> memberUids = memberUidsFuture.join();
+                    return peersFuture.join().stream().map(peer ->
+                        CompletableFuture.supplyAsync(() -> getVersion(peer), pool)
+                            .thenApply(this::getCurrentBlock)
+                            .exceptionally(throwable -> {
+                                peer.getStats().setStatus(Peer.PeerStatus.DOWN);
+                                if(!(throwable instanceof HttpConnectException)) {
+                                    Throwable cause = throwable.getCause() != null ? throwable.getCause() : throwable;
+                                    peer.getStats().setError(cause.getMessage());
+                                    if (log.isDebugEnabled()) {
+                                        if (log.isTraceEnabled()) {
+                                            log.debug(String.format("[%s] is DOWN: %s", peer, cause.getMessage()), cause);
+                                        }
+                                        else log.debug(String.format("[%s] is DOWN: %s", peer, cause.getMessage()));
+                                    }
+                                }
+                                else if (log.isTraceEnabled()) log.debug(String.format("[%s] is DOWN", peer));
+                                return peer;
+                            })
+                            .thenApply(apeer -> {
+                                String uid = StringUtils.isNotBlank(peer.getPubkey()) ? memberUids.get(peer.getPubkey()) : null;
+                                peer.getStats().setUid(uid);
+                                if (peer.getStats().isReacheable() && StringUtils.isNotBlank(uid)) {
+                                    getHardship(peer);
+                                }
+                                return apeer;
+                            })
+                            .exceptionally(throwable -> {
+                                peer.getStats().setHardshipLevel(0);
+                                return peer;
+                            })
+                        ).collect(Collectors.toList());
+                });
+    }
+
+    public List<Peer> fillPeerStatsConsensus(final List<Peer> peers) {
+
+        final Map<String,Long> peerCountByBuid = peers.stream()
+                .filter(peer -> peer.getStats().getStatus() == Peer.PeerStatus.UP)
+                .map(this::buid)
+                .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
+
+        // Compute main consensus buid
+        Optional<Map.Entry<String, Long>> maxPeerCountEntry = peerCountByBuid.entrySet().stream()
+                .sorted(Comparator.comparing(Map.Entry::getValue, (l1, l2) -> l2.compareTo(l1)))
+                .findFirst();
+
+        final String mainBuid = maxPeerCountEntry.isPresent() ? maxPeerCountEntry.get().getKey() : null;;
+
+        // Compute total of UP peers
+        final Long peersUpTotal = peerCountByBuid.values().stream().mapToLong(Long::longValue).sum();
+
+        // Compute pct by buid
+        final Map<String, Double> buidsPct = peerCountByBuid.keySet().stream()
+                .collect(Collectors.toMap(
+                        buid -> buid,
+                        buid -> (peerCountByBuid.get(buid).doubleValue() * 100 / peersUpTotal)));
+
+        // Set consensus stats
+        peers.forEach(peer -> {
+                    Peer.Stats stats = peer.getStats();
+                    String buid = buid(stats);
+
+                    // Set consensus stats on each peers
+                    if (buid != null) {
+                        boolean isMainConsensus = buid.equals(mainBuid);
+                        stats.setMainConsensus(isMainConsensus);
+
+                        boolean isForkConsensus = !isMainConsensus && peerCountByBuid.get(buid) > 1;
+                        stats.setForkConsensus(isForkConsensus);
+
+                        stats.setConsensusPct(isMainConsensus || isForkConsensus ? buidsPct.get(buid) : 0d);
+                    }
+                });
+
+        return peers;
+    }
+
+    /* -- protected methods -- */
+
+    protected List<Peer> loadPeerLeafs(Peer peer) {
+        List<String> leaves = networkRemoteService.getPeersLeaves(peer);
+
+        if (CollectionUtils.isEmpty(leaves)) return new ArrayList<>(); // should never occur
+
+        // If less than 100 node, get it in ONE call
+        if (leaves.size() < 100) {
+            // TODO uncomment on prod
+            //List<Peer> peers = networkService.getPeers(peer);
+            //return ImmutableList.of(peers.get(0), peers.get(1), peers.get(2), peers.get(3));
+
+            //return networkService.getPeers(peer);
+        }
+
+        // Get it by multiple call on /network/peering?leaf=
+        List<Peer> result = new ArrayList<>();
+        if (CollectionUtils.isNotEmpty(leaves)) {
+            int offset = 0;
+            int count = Constants.Config.MAX_SAME_REQUEST_COUNT;
+            while (offset < leaves.size()) {
+                if (offset + count > leaves.size()) count = leaves.size() - offset;
+                loadPeerLeafs(peer, result, leaves, offset, count);
+                offset += count;
+                try {
+                    Thread.sleep(1000); // wait 1 s
+                } catch (InterruptedException e) {
+                    // stop
+                    offset = leaves.size();
+                }
+            }
+        }
+
+        return result;
+    }
+
+    protected void loadPeerLeafs(Peer requestedPeer, List<Peer> result, List<String> leaves, int offset, int count) {
+
+        for (int i = offset; i< offset + count; i++) {
+            String leaf = leaves.get(i);
+            try {
+                NetworkPeers.Peer peer = networkRemoteService.getPeerLeaf(requestedPeer, leaf);
+
+                if (CollectionUtils.isNotEmpty(peer.getEndpoints())) {
+                    for (NetworkPeering.Endpoint ep: peer.getEndpoints()) {
+                        if (ep != null && ep.getApi() != null) {
+                            Peer peerEp = Peer.newBuilder()
+                                    .setCurrency(peer.getCurrency())
+                                    .setHash(leaf)
+                                    .setPubkey(peer.getPubkey())
+                                    .setEndpoint(ep)
+                                    .build();
+                            result.add(peerEp);
+                        }
+                    }
+                }
+
+
+            } catch(HttpNotFoundException e) {
+                log.warn("Peer not found for leaf=" + leaf);
+                // skip
+            }
+        }
+    }
+
+
+    protected boolean applyPeerFilter(Peer peer, Filter filter) {
+
+        Peer.Stats stats = peer.getStats();
+
+        // Filter member or mirror
+        if (filter.filterType != null && (
+                (filter.filterType == FilterType.MEMBER && StringUtils.isBlank(stats.getUid()))
+                        || (filter.filterType == FilterType.MIRROR && StringUtils.isNotBlank(stats.getUid()))
+        )) {
+            return false;
+        }
+
+        // Filter on endpoints
+        if (CollectionUtils.isNotEmpty(filter.filterEndpoints)
+                && (StringUtils.isBlank(peer.getApi())
+                    || !filter.filterEndpoints.contains(peer.getApi()))) {
+            return false;
+        }
+
+        // Filter on status
+        if (filter.filterStatus != null && filter.filterStatus != stats.getStatus()) {
+            return false;
+        }
+
+        // Filter on SSL
+        if (filter.filterSsl != null && filter.filterSsl.booleanValue() != peer.isUseSsl()) {
+            return false;
+        }
+
+        return true;
+    }
+
+    protected Peer getVersion(final Peer peer) {
+        JsonNode json = executeRequest(peer, BMA_URL_STATUS, JsonNode.class);
+        // TODO update peer
+        json = json.get("duniter");
+        if (json.isMissingNode()) throw new TechnicalException(String.format("Invalid format of [%s] response", BMA_URL_STATUS));
+        json = json.get("version");
+        if (json.isMissingNode()) throw new TechnicalException(String.format("No version attribute found in [%s] response", BMA_URL_STATUS));
+        String version = json.asText();
+        peer.getStats().setVersion(version);
+        return peer;
+    }
+
+    protected Peer getCurrentBlock(final Peer peer) {
+        JsonNode json = executeRequest(peer, BMA_URL_BLOCKCHAIN_CURRENT , JsonNode.class);
+
+        Integer number = json.has("number") ? json.get("number").asInt() : null;
+        peer.getStats().setBlockNumber(number);
+
+        String hash = json.has("hash") ? json.get("hash").asText() : null;
+        peer.getStats().setBlockHash(hash);
+
+        Long medianTime = json.has("medianTime") ? json.get("medianTime").asLong() : null;
+        peer.getStats().setMedianTime(medianTime);
+
+        if (log.isTraceEnabled()) {
+            log.trace(String.format("[%s] current block [%s-%s]", peer.toString(), number, hash));
+        }
+
+        return peer;
+    }
+
+    protected Peer getHardship(final Peer peer) {
+        if (StringUtils.isBlank(peer.getPubkey())) return peer;
+
+        JsonNode json = executeRequest(peer, BMA_URL_BLOCKCHAIN_HARDSHIP + peer.getPubkey(), JsonNode.class);
+        Integer level = json.has("level") ? json.get("level").asInt() : null;
+        peer.getStats().setHardshipLevel(level);
+        return peer;
+    }
+
+    protected String computeUniqueId(Peer peer) {
+        return cryptoService.hash(
+                new StringJoiner("|")
+                .add(peer.getPubkey())
+                .add(peer.getDns())
+                .add(peer.getIpv4())
+                .add(peer.getIpv6())
+                .add(String.valueOf(peer.getPort()))
+                .add(Boolean.toString(peer.isUseSsl()))
+                .toString());
+    }
+
+    protected JsonNode get(final Peer peer, String path) {
+        return executeRequest(peer, path, JsonNode.class);
+    }
+
+    /**
+     * Log allOfToList peers found
+     */
+    protected List<Peer> logPeers(final List<Peer> peers) {
+        if (!log.isDebugEnabled()) return peers;
+
+        if (CollectionUtils.isEmpty(peers)) {
+            log.debug("No peers found.");
+        }
+        else {
+            log.debug(String.format("Found %s peers", peers.size()));
+            if (log.isTraceEnabled()) {
+
+                peers.forEach(peerFound -> {
+                    if (peerFound.getStats().getStatus() == Peer.PeerStatus.DOWN) {
+                        String error = peerFound.getStats().getError();
+                        log.trace(String.format("Found peer [%s] [%s] %s",
+                                peerFound.toString(),
+                                peerFound.getStats().getStatus().name(),
+                                error != null ? error : ""));
+                    } else {
+                        log.trace(String.format("Found peer [%s] [%s] [v%s] block [%s]", peerFound.toString(),
+                                peerFound.getStats().getStatus().name(),
+                                peerFound.getStats().getVersion(),
+                                peerFound.getStats().getBlockNumber()
+                        ));
+
+                    }
+                });
+            }
+        }
+        return peers;
+    }
+
+    protected double computePeerStatsScore(Peer peer, Sort sort) {
+        double score = 0;
+        Peer.Stats stats = peer.getStats();
+        if (sort != null && sort.sortType != null) {
+            long specScore = 0;
+            specScore += (sort.sortType == SortType.UID ? computeScoreAlphaValue(stats.getUid(), 3, sort.sortAsc) : 0);
+            specScore += (sort.sortType == SortType.PUBKEY ? computeScoreAlphaValue(peer.getPubkey(), 3, sort.sortAsc) : 0);
+            specScore += (sort.sortType == SortType.API ?
+                    (peer.isUseSsl() ? (sort.sortAsc ? 1 : -1) :
+                            (hasEndPointAPI(peer, EndpointApi.ES_USER_API) ? (sort.sortAsc ? 0.5 : -0.5) : 0)) : 0);
+            specScore += (sort.sortType == SortType.HARDSHIP ? (stats.getHardshipLevel() != null ? (sort.sortAsc ? (10000-stats.getHardshipLevel()) : stats.getHardshipLevel()): 0) : 0);
+            specScore += (sort.sortType == SortType.BLOCK_NUMBER ? (stats.getBlockNumber() != null ? (sort.sortAsc ? (1000000000 - stats.getBlockNumber()) : stats.getBlockNumber()) : 0) : 0);
+            score += (10000000000l * specScore);
+        }
+        score += (1000000000 * (stats.getStatus() == Peer.PeerStatus.UP ? 1 : 0));
+        score += (100000000  * (stats.isMainConsensus() ? 1 : 0));
+        score += (1000000    * (stats.isForkConsensus() ? stats.getConsensusPct() : 0));
+
+        score += (100     * (stats.getHardshipLevel() != null ? (10000-stats.getHardshipLevel()) : 0));
+        score += /* 1     */(peer.getPubkey() != null ? computeScoreAlphaValue(peer.getPubkey(), 2, true) : 0);
+
+        return score;
+    }
+
+    protected int computeScoreAlphaValue(String value, int nbChars, boolean asc) {
+        if (StringUtils.isBlank(value)) return 0;
+        int score = 0;
+        value = value.toLowerCase();
+        if (nbChars > value.length()) {
+            nbChars = value.length();
+        }
+        score += (int)value.charAt(0);
+        for (int i=1; i < nbChars; i++) {
+            score += Math.pow(0.001, i) * value.charAt(i);
+        }
+        return asc ? (1000 - score) : score;
+    }
+
+    protected boolean hasEndPointAPI(Peer peer, EndpointApi api) {
+        return peer.getApi() != null && peer.getApi().equalsIgnoreCase(api.name());
+    }
+
+    protected String buid(Peer peer) {
+        return buid(peer.getStats());
+    }
+
+    protected String buid(Peer.Stats stats) {
+        return stats.getStatus() == Peer.PeerStatus.UP
+                ? stats.getBlockNumber() + "-" + stats.getBlockHash()
+                : null;
+    }
+}
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 d8f3692418f5bb3e62f97b15bc3f983acc723898..f724f1576d1f9f65ac86da21037fbabeb5b45027 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
@@ -150,7 +150,7 @@ public class PeerServiceImpl implements PeerService, InitializingBean {
     }
 
     /**
-     * Fill all cache need for currencies
+     * Fill allOfToList cache need for currencies
      * @param accountId
      */
     public void loadCache(long accountId) {
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 28d37dbd7cfc0f9d60788df39c13dd291ccc6321..1029dadb1f1e56926ef3f159918356cf17304957 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
@@ -155,7 +155,10 @@ public class TestResource extends org.duniter.core.test.TestResource {
         // Set a default account id, then load cache
         ServiceLocator.instance().getDataContext().setAccountId(0);
 
-        Peer peer = new Peer(config.getNodeHost(), config.getNodePort());
+        Peer peer = Peer.newBuilder()
+                .setHost(config.getNodeHost())
+                .setPort(config.getNodePort())
+                .build();
         peer.setCurrencyId(fixtures.getDefaultCurrencyId());
 
         ServiceLocator.instance().getPeerService().save(peer);
diff --git a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/HttpServiceTest.java b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/HttpServiceTest.java
index 0f96bc8bf125e2a5c361b60bad554e195e955e20..62eb7714b3e6f450e93fbf7c2a7c232772d17727 100644
--- a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/HttpServiceTest.java
+++ b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/HttpServiceTest.java
@@ -25,19 +25,13 @@ package org.duniter.core.client.service;
 
 import org.duniter.core.client.TestResource;
 import org.duniter.core.client.config.Configuration;
-import org.duniter.core.client.model.bma.EndpointProtocol;
-import org.duniter.core.client.model.bma.NetworkPeering;
 import org.duniter.core.client.model.local.Peer;
-import org.duniter.core.client.service.bma.NetworkRemoteService;
-import org.junit.Assert;
 import org.junit.Before;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.List;
-
 public class HttpServiceTest {
 
 	private static final Logger log = LoggerFactory.getLogger(HttpServiceTest.class);
@@ -64,10 +58,9 @@ public class HttpServiceTest {
 	/* -- internal methods */
 
     protected Peer createTestPeer() {
-        Peer peer = new Peer(
-                Configuration.instance().getNodeHost(),
-                Configuration.instance().getNodePort());
-
-        return peer;
+		return Peer.newBuilder()
+				.setHost(Configuration.instance().getNodeHost())
+				.setPort(Configuration.instance().getNodePort())
+				.build();
     }
 }
diff --git a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/BlockchainRemoteServiceTest.java b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/BlockchainRemoteServiceTest.java
index fb88104790407a94ceaaefa12e14b31b11828653..33ee0035fd0ca25d5d2ea0b87c946949590222cd 100644
--- a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/BlockchainRemoteServiceTest.java
+++ b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/BlockchainRemoteServiceTest.java
@@ -114,7 +114,7 @@ public class BlockchainRemoteServiceTest {
         Assert.assertNotNull(result);
         Assert.assertEquals(10, result.length);
 
-        // Make sure all json are valid blocks
+        // Make sure allOfToList json are valid blocks
         ObjectMapper objectMapper = JacksonUtils.newObjectMapper();
 
         int number = 0;
@@ -192,11 +192,10 @@ public class BlockchainRemoteServiceTest {
     /* -- Internal methods -- */
 
     protected Peer createTestPeer() {
-        Peer peer = new Peer(
-                Configuration.instance().getNodeHost(),
-                Configuration.instance().getNodePort());
-
-        return peer;
+        return Peer.newBuilder()
+                .setHost(Configuration.instance().getNodeHost())
+                .setPort(Configuration.instance().getNodePort())
+                .build();
     }
 
     protected Wallet createTestWallet() {
diff --git a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/NetworkRemoteServiceTest.java b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/NetworkRemoteServiceTest.java
index 5544aee2482de27386ee170b4a20a7d4dd27c80a..ccd274e860c2431071a16f67f34e2445664f2fb2 100644
--- a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/NetworkRemoteServiceTest.java
+++ b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/bma/NetworkRemoteServiceTest.java
@@ -25,7 +25,7 @@ package org.duniter.core.client.service.bma;
 
 import org.duniter.core.client.TestResource;
 import org.duniter.core.client.config.Configuration;
-import org.duniter.core.client.model.bma.EndpointProtocol;
+import org.duniter.core.client.model.bma.EndpointApi;
 import org.duniter.core.client.model.bma.NetworkPeering;
 import org.duniter.core.client.model.local.Peer;
 import org.duniter.core.client.service.ServiceLocator;
@@ -69,7 +69,7 @@ public class NetworkRemoteServiceTest {
 	@Test
 	public void findPeers() throws Exception {
 
-		List<Peer> result = service.findPeers(peer, null, EndpointProtocol.BASIC_MERKLED_API, null, null);
+		List<Peer> result = service.findPeers(peer, null, EndpointApi.BASIC_MERKLED_API, null, null);
 
 		Assert.assertNotNull(result);
 		Assert.assertTrue(result.size() > 0);
@@ -87,10 +87,9 @@ public class NetworkRemoteServiceTest {
 	/* -- internal methods */
 
     protected Peer createTestPeer() {
-        Peer peer = new Peer(
-                Configuration.instance().getNodeHost(),
-                Configuration.instance().getNodePort());
-
-        return peer;
+		return Peer.newBuilder()
+				.setHost(Configuration.instance().getNodeHost())
+				.setPort(Configuration.instance().getNodePort())
+				.build();
     }
 }
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 8d8950c612d0229f0f3c6742e5345b174d9b5576..3118e7f373661f1c9060abbd5182999c69510331 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
@@ -90,10 +90,9 @@ public class TransactionRemoteServiceTest {
 	}
 
     protected Peer createTestPeer() {
-        Peer peer = new Peer(
-                Configuration.instance().getNodeHost(),
-                Configuration.instance().getNodePort());
-
-        return peer;
+		return Peer.newBuilder()
+				.setHost(Configuration.instance().getNodeHost())
+				.setPort(Configuration.instance().getNodePort())
+				.build();
     }
 }
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 5cbbd36e453fa608a099cfc802b0119c521e7f5c..ca52ee154ad72030aa3174afebdb51e5e9ae9325 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
@@ -165,10 +165,9 @@ public class WotRemoteServiceTest {
 	}
 
 	protected Peer createTestPeer() {
-		Peer peer = new Peer(
-				Configuration.instance().getNodeHost(),
-				Configuration.instance().getNodePort());
-
-		return peer;
+		return Peer.newBuilder()
+				.setHost(Configuration.instance().getNodeHost())
+				.setPort(Configuration.instance().getNodePort())
+				.build();
 	}
 }
diff --git a/duniter4j-core-client/src/test/java/org/duniter/core/client/service/local/NetworkServiceTest.java b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/local/NetworkServiceTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..8106d95f86b39254e6114fcaefaaf35c4baa6d8d
--- /dev/null
+++ b/duniter4j-core-client/src/test/java/org/duniter/core/client/service/local/NetworkServiceTest.java
@@ -0,0 +1,72 @@
+package org.duniter.core.client.service.local;
+
+/*
+ * #%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.TestResource;
+import org.duniter.core.client.config.Configuration;
+import org.duniter.core.client.model.local.Peer;
+import org.duniter.core.client.service.ServiceLocator;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+public class NetworkServiceTest {
+
+	private static final Logger log = LoggerFactory.getLogger(NetworkServiceTest.class);
+
+	@ClassRule
+	public static final TestResource resource = TestResource.create();
+	
+	private NetworkService service;
+	private Peer peer;
+	
+	@Before
+	public void setUp() {
+		peer = createTestPeer();
+		service = ServiceLocator.instance().getNetworkService();
+	}
+
+	@Test
+	public void start() throws Exception {
+
+		List<Peer> peers = service.getPeers(peer);
+
+		Assert.assertNotNull(peers);
+		Assert.assertTrue(peers.size() > 0);
+	}
+
+	/* -- internal methods */
+
+    protected Peer createTestPeer() {
+        return Peer.newBuilder()
+				.setHost(Configuration.instance().getNodeHost())
+				.setPort(Configuration.instance().getNodePort())
+				.build();
+    }
+}
diff --git a/duniter4j-core-client/src/test/resources/META-INF/services/org.duniter.core.beans.Bean b/duniter4j-core-client/src/test/resources/META-INF/services/org.duniter.core.beans.Bean
index e4fdb2bbd7e5e975380f1b3256c23970d322ba73..bbf9fb6b0c5cc69950b92beafedb92622ab88201 100644
--- a/duniter4j-core-client/src/test/resources/META-INF/services/org.duniter.core.beans.Bean
+++ b/duniter4j-core-client/src/test/resources/META-INF/services/org.duniter.core.beans.Bean
@@ -8,5 +8,6 @@ org.duniter.core.client.service.HttpServiceImpl
 org.duniter.core.client.service.DataContext
 org.duniter.core.client.service.local.PeerServiceImpl
 org.duniter.core.client.service.local.CurrencyServiceImpl
+org.duniter.core.client.service.local.NetworkServiceImpl
 org.duniter.core.client.dao.mem.MemoryCurrencyDaoImpl
 org.duniter.core.client.dao.mem.MemoryPeerDaoImpl
\ No newline at end of file
diff --git a/duniter4j-core-client/src/test/resources/duniter4j-core-client-test.properties b/duniter4j-core-client/src/test/resources/duniter4j-core-client-test.properties
index 9e0174256782c42e59993c031833112967a47301..d9f4f75b7e5ea3263c1a2c3b4ac7eecaba84c90e 100644
--- a/duniter4j-core-client/src/test/resources/duniter4j-core-client-test.properties
+++ b/duniter4j-core-client/src/test/resources/duniter4j-core-client-test.properties
@@ -1,5 +1,5 @@
-duniter4j.node.host=gtest.duniter.org
-duniter4j.node.port=10900
+duniter4j.node.host=192.168.0.5
+duniter4j.node.port=10901
 
 duniter4j.node.elasticsearch.host=localhost
 duniter4j.node.elasticsearch.port=9200
diff --git a/duniter4j-core-client/src/test/resources/log4j.properties b/duniter4j-core-client/src/test/resources/log4j.properties
index a3e37387864bcfee1cb503b4b88d5d0f781cafe3..e1c60841fcdf60dd05ccd622f278ae8d34a91a7d 100644
--- a/duniter4j-core-client/src/test/resources/log4j.properties
+++ b/duniter4j-core-client/src/test/resources/log4j.properties
@@ -7,12 +7,7 @@ log4j.appender.stdout=org.apache.log4j.ConsoleAppender
 log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
 log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %5p (%c:%L) - %m%n
 
-# ucoin levels
-log4j.logger.org.duniter=INFO
-log4j.logger.org.duniter.core.client.service=DEBUG
-log4j.logger.org.duniter.core.client.service.bma=DEBUG
-log4j.logger.org.duniter.core.beans=WARN
-
+# File output
 log4j.appender.file=org.apache.log4j.RollingFileAppender
 log4j.appender.file.file=ucoin-client.log
 log4j.appender.file.MaxFileSize=10MB
@@ -21,4 +16,15 @@ log4j.appender.file.MaxBackupIndex=4
 log4j.appender.file.layout=org.apache.log4j.PatternLayout
 log4j.appender.file.layout.ConversionPattern=%d{ISO8601} %5p %c - %m%n
 
+# duniter4j levels
+log4j.logger.org.duniter=INFO
+log4j.logger.org.duniter.core.client.service=DEBUG
+log4j.logger.org.duniter.core.client.service.bma=DEBUG
+log4j.logger.org.duniter.core.beans=WARN
+log4j.logger.org.duniter.core.client.service=TRACE
+
+# Framework levels
+log4j.logger.org.apache.http=WARN
+
+
 
diff --git a/duniter4j-core-shared/pom.xml b/duniter4j-core-shared/pom.xml
index 7844bdeae84993aeec9267c06214ad834b05237b..f826f638bebf7a4043a5a65f45775f4d288a7037 100644
--- a/duniter4j-core-shared/pom.xml
+++ b/duniter4j-core-shared/pom.xml
@@ -41,6 +41,16 @@
       <artifactId>scrypt</artifactId>
     </dependency>
 
+    <!-- http -->
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpclient</artifactId>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.httpcomponents</groupId>
+      <artifactId>httpcore</artifactId>
+    </dependency>
+
     <dependency>
       <groupId>javax.websocket</groupId>
       <artifactId>javax.websocket-api</artifactId>
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 4d1b4d458bc2cd7b40e3b7b7ada17242da1353a8..4567cea4a8f677485eb0249ba5a7ed16f1293b80 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
@@ -120,7 +120,6 @@ public class BeanFactory implements Closeable{
         throw new TechnicalException(String.format("Unable to create bean with type [%s]: not configured for the service loader [%s]", clazz.getName(), Bean.class.getCanonicalName()));
     }
 
-
     @Override
     public void close() throws IOException {
         for(Object bean: beansCache.values()) {
diff --git a/duniter4j-core-shared/src/main/java/org/duniter/core/util/concurrent/CompletableFutures.java b/duniter4j-core-shared/src/main/java/org/duniter/core/util/concurrent/CompletableFutures.java
new file mode 100644
index 0000000000000000000000000000000000000000..b0b78f91ae42fd04bdd2234d8968d971c0cede18
--- /dev/null
+++ b/duniter4j-core-shared/src/main/java/org/duniter/core/util/concurrent/CompletableFutures.java
@@ -0,0 +1,38 @@
+package org.duniter.core.util.concurrent;
+
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+/**
+ * Helper class on CompletableFuture and concurrent classes
+ * Created by blavenie on 24/03/17.
+ */
+public class CompletableFutures {
+
+    private CompletableFutures() {
+    }
+
+    public static <T> CompletableFuture<List<T>> allOfToList(List<CompletableFuture<T>> futures) {
+        CompletableFuture<Void> allDoneFuture =
+                CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
+        return allDoneFuture.thenApply(v ->
+                futures.stream()
+                        .map(future -> future.join())
+                        .filter(peer -> peer != null) // skip
+                        .collect(Collectors.toList())
+        );
+    }
+
+    public static <T> CompletableFuture<List<T>> allOfToList(List<CompletableFuture<T>> futures, Predicate<? super T> filter) {
+        CompletableFuture<Void> allDoneFuture =
+                CompletableFuture.allOf(futures.toArray(new CompletableFuture[futures.size()]));
+        return allDoneFuture.thenApply(v ->
+                futures.stream()
+                        .map(future -> future.join())
+                        .filter(filter)
+                        .collect(Collectors.toList())
+        );
+    }
+}
diff --git a/duniter4j-core-shared/src/main/java/org/duniter/core/util/http/InetAddressUtils.java b/duniter4j-core-shared/src/main/java/org/duniter/core/util/http/InetAddressUtils.java
new file mode 100644
index 0000000000000000000000000000000000000000..b88050ef68d830a8cbda7a38c41177a87b36a6fc
--- /dev/null
+++ b/duniter4j-core-shared/src/main/java/org/duniter/core/util/http/InetAddressUtils.java
@@ -0,0 +1,27 @@
+package org.duniter.core.util.http;
+
+import java.util.regex.Pattern;
+
+/**
+ * Created by blavenie on 24/03/17.
+ */
+public class InetAddressUtils {
+
+    public static final Pattern LOCAL_IP_ADDRESS_PATTERN = Pattern.compile("^127[.]0[.]0.|192[.]168[.]|10[.]0[.]0[.]|172[.]16[.]");
+
+    private InetAddressUtils() {
+    }
+
+    public static boolean isNotLocalIPv4Address(String input) {
+        return org.apache.http.conn.util.InetAddressUtils.isIPv4Address(input) &&
+                !LOCAL_IP_ADDRESS_PATTERN.matcher(input).matches();
+    }
+
+    public static boolean isIPv4Address(String input) {
+        return org.apache.http.conn.util.InetAddressUtils.isIPv4Address(input);
+    }
+
+    public static boolean isIPv6Address(String input) {
+        return org.apache.http.conn.util.InetAddressUtils.isIPv6Address(input);
+    }
+}
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 6a58a0688110cb3731dbefb0fd3b6d53abb3d55e..2f127d8dae2b8f279b75fabcad23c8bec343163a 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
@@ -136,6 +136,17 @@ public class WebsocketClientEndpoint implements Closeable {
         }
     }
 
+    /**
+     * unregister message listener
+     *
+     * @param listener
+     */
+    public void unregisterListener(MessageListener listener) {
+        synchronized (messageListeners) {
+            this.messageListeners.remove(listener);
+        }
+    }
+
     /**
      * register connection listener
      *
@@ -147,6 +158,18 @@ public class WebsocketClientEndpoint implements Closeable {
         }
     }
 
+    /**
+     * unregister connection listener
+     *
+     * @param listener
+     */
+    public void unregisterListener(ConnectionListener listener) {
+        synchronized (connectionListeners) {
+            this.connectionListeners.remove(listener);
+        }
+    }
+
+
     /**
      * Send a message.
      *
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 b8d4bde5a57834e778f2ddb9caa12c1148c4a26f..89d277399098291704539d207cd6b39aa1825c55 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,6 +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.NetworkService;
 import org.duniter.elasticsearch.threadpool.ThreadPool;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.cluster.health.ClusterHealthStatus;
@@ -135,6 +136,11 @@ public class PluginInit extends AbstractLifecycleComponent<PluginInit> {
             injector.getInstance(BlockchainService.class)
                     .indexLastBlocks(peer)
                     .listenAndIndexNewBlock(peer);
+
+            // Index peers (and listen if new peer appear)
+            injector.getInstance(NetworkService.class)
+                    .indexLastPeers(peer)/*
+                    .listenAndIndexNewPeer(peer)*/;
         }
     }
 }
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginSettings.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginSettings.java
index 50cb48e4bd5290f549d2eec62b2db6ed2d04e57f..e95270d390d0a7cb0ee960d11e5113b5efb94578 100644
--- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginSettings.java
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/PluginSettings.java
@@ -105,7 +105,6 @@ public class PluginSettings extends AbstractLifecycleComponent<PluginSettings> {
         applicationConfig.setDefaultOption(ConfigurationOption.BASEDIR.getKey(), baseDir);
         applicationConfig.setDefaultOption(ConfigurationOption.NODE_HOST.getKey(), getNodeBmaHost());
         applicationConfig.setDefaultOption(ConfigurationOption.NODE_PORT.getKey(), String.valueOf(getNodeBmaPort()));
-        applicationConfig.setDefaultOption(ConfigurationOption.NODE_PROTOCOL.getKey(), getNodeBmaUseSsl() ? "https" : "http");
         applicationConfig.setDefaultOption(ConfigurationOption.NETWORK_TIMEOUT.getKey(), String.valueOf(getNetworkTimeout()));
 
         try {
@@ -168,7 +167,7 @@ public class PluginSettings extends AbstractLifecycleComponent<PluginSettings> {
     }
 
     public boolean getNodeBmaUseSsl() {
-        return settings.getAsBoolean("duniter.useSsl", getNodeBmaPort() == 443);
+        return settings.getAsBoolean("duniter.useSsl", null);
     }
 
     public boolean isIndexBulkEnable() {
@@ -227,7 +226,7 @@ public class PluginSettings extends AbstractLifecycleComponent<PluginSettings> {
             return null;
         }
 
-        Peer peer = new Peer(getNodeBmaHost(), getNodeBmaPort(), getNodeBmaUseSsl());
+        Peer peer = Peer.newBuilder().setHost(getNodeBmaHost()).setPort(getNodeBmaPort()).setUseSsl(getNodeBmaUseSsl()).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 ce82dfc3435d8b97c1dc264cfa80364a4493525e..8f3192de1985f3dffc53c41e4fdb9f6b492372dd 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
@@ -30,7 +30,7 @@ import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Lists;
 import org.duniter.core.client.model.bma.BlockchainBlock;
 import org.duniter.core.client.model.bma.BlockchainParameters;
-import org.duniter.core.client.model.bma.EndpointProtocol;
+import org.duniter.core.client.model.bma.EndpointApi;
 import org.duniter.core.client.model.local.Peer;
 import org.duniter.core.util.json.JsonAttributeParser;
 import org.duniter.core.client.model.bma.jackson.JacksonUtils;
@@ -178,7 +178,7 @@ public class BlockchainService extends AbstractService {
             // Check if index exists
             createIndexIfNotExists(currencyName, true/*wait cluster health*/);
 
-            // Then index all blocks
+            // Then index allOfToList blocks
             BlockchainBlock peerCurrentBlock = blockchainRemoteService.getCurrentBlock(peer);
 
             if (peerCurrentBlock != null) {
@@ -243,7 +243,7 @@ public class BlockchainService extends AbstractService {
                         progressionModel.setStatus(ProgressionModel.Status.SUCCESS);
                     }
                     else {
-                        logger.warn(String.format("[%s] [%s] Could not indexed all blocks. Missing %s blocks.", currencyName, peer, missingBlocks.size()));
+                        logger.warn(String.format("[%s] [%s] Could not indexed allOfToList blocks. Missing %s blocks.", currencyName, peer, missingBlocks.size()));
                         progressionModel.setStatus(ProgressionModel.Status.FAILED);
                     }
                 }
@@ -294,7 +294,7 @@ public class BlockchainService extends AbstractService {
                 .build();
         createIndexRequestBuilder.setSettings(indexSettings);
         createIndexRequestBuilder.addMapping(BLOCK_TYPE, createBlockTypeMapping());
-        createIndexRequestBuilder.addMapping(PEER_TYPE, createPeerTypeMapping());
+        createIndexRequestBuilder.addMapping(PEER_TYPE, NetworkService.createPeerTypeMapping());
         createIndexRequestBuilder.execute().actionGet();
     }
 
@@ -689,34 +689,6 @@ public class BlockchainService extends AbstractService {
         }
     }
 
-    public XContentBuilder createPeerTypeMapping() {
-        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()
-
-
-                    .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) {
 
@@ -954,9 +926,9 @@ public class BlockchainService extends AbstractService {
         // Select other peers, in filtering on the same blockchain version
 
         // TODO : a activer quand les peers seront bien mis à jour (UP/DOWN, block, hash...)
-        //List<Peer> otherPeers = networkRemoteService.findPeers(peer, "UP", EndpointProtocol.BASIC_MERKLED_API,
+        //List<Peer> otherPeers = networkRemoteService.findPeers(peer, "UP", EndpointApi.BASIC_MERKLED_API,
         //        currentBlock.getNumber(), currentBlock.getHash());
-        List<Peer> otherPeers = networkRemoteService.findPeers(peer, null, EndpointProtocol.BASIC_MERKLED_API,
+        List<Peer> otherPeers = networkRemoteService.findPeers(peer, null, EndpointApi.BASIC_MERKLED_API,
                 null, null);
 
         for(Peer childPeer: otherPeers) {
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/NetworkService.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/NetworkService.java
new file mode 100644
index 0000000000000000000000000000000000000000..209abd269a9356cc65ba89a5cc2419a95861306e
--- /dev/null
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/service/NetworkService.java
@@ -0,0 +1,571 @@
+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.client.service.local.NetworkServiceImpl;
+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 NetworkService extends AbstractService {
+
+    public static final String PEER_TYPE = "peer";
+
+    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 NetworkService(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 NetworkService indexLastPeers(Peer peer) {
+        indexLastPeers(peer, nullProgressionModel);
+        return this;
+    }
+
+    public NetworkService indexLastPeers(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 indexLastPeers: " + e.getMessage(), e);
+            progressionModel.setStatus(ProgressionModel.Status.FAILED);
+        }
+
+        return this;
+    }
+
+
+    public NetworkService 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.getHash(), "peer attribute 'hash' 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");
+
+        Peer existingPeer = getPeerByHash(peer.getCurrency(), peer.getHash());
+
+        // Currency not exists, or has changed, so create it
+        if (existingPeer == null) {
+            if (logger.isTraceEnabled()) {
+                logger.trace(String.format("Insert new peer [%s]", peer));
+            }
+
+            // Index new peer
+            indexPeer(peer, wait);
+        }
+
+        // Update existing peer
+        else {
+            logger.trace(String.format("Update peer [%s]", peer));
+            updatePeer(peer, wait);
+        }
+        return peer;
+    }
+
+    public void indexPeer(Peer peer, 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(), PEER_TYPE)
+                    .setId(peer.getHash())
+                    .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, 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(), PEER_TYPE, peer.getHash())
+                    .setDoc(json);
+
+            // Execute indexBlocksFromNode
+            ActionFuture<UpdateResponse> futureResponse = updateRequest
+                    .setRefresh(true)
+                    .execute();
+
+            if (wait) {
+                futureResponse.actionGet();
+            }
+        }
+        catch(JsonProcessingException e) {
+            throw new TechnicalException(e);
+        }
+    }
+
+    /**
+     *
+     * @param currencyName
+     * @param number the peer hash
+     * @param json block as JSON
+     */
+    public NetworkService indexPeerFromJson(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, PEER_TYPE)
+                .setId(String.valueOf(number))
+                .setRefresh(refresh)
+                .setSource(json);
+
+        // Execute indexBlocksFromNode
+        if (!wait) {
+            indexRequest.execute();
+        }
+        else {
+            indexRequest.execute().actionGet();
+        }
+
+        return this;
+    }
+
+    /**
+     * 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 NetworkService 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, PEER_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(PEER_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);
+    }
+
+    /* -- Internal methods -- */
+
+    public static XContentBuilder createPeerTypeMapping() {
+        try {
+            XContentBuilder mapping = XContentFactory.jsonBuilder()
+                    .startObject()
+                    .startObject(PEER_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 hash) {
+
+        // Prepare request
+        SearchRequestBuilder searchRequest = client
+                .prepareSearch(currencyName)
+                .setTypes(PEER_TYPE)
+                .setSearchType(SearchType.DFS_QUERY_THEN_FETCH);
+
+        // If more than a word, search on terms match
+        searchRequest.setQuery(QueryBuilders.matchQuery("_id", hash));
+
+        // Execute query
+        try {
+            SearchResponse searchResponse = searchRequest.execute().actionGet();
+            List<Peer> 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 peer #%s for [%s]", hash, currencyName), e);
+        }
+
+    }
+
+    protected List<Peer> toBlocks(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;
+    }
+
+
+    protected void reportIndexPeersProgress(ProgressionModel progressionModel, String currencyName, Peer peer, int offset, int total) {
+        int pct = offset * 100 / total;
+        progressionModel.setCurrent(pct);
+
+        progressionModel.setMessage(I18n.t("duniter4j.es.networkService.indexPeers.progress", currencyName, peer, offset, pct));
+        if (logger.isInfoEnabled()) {
+            logger.info(I18n.t("duniter4j.es.networkService.indexPeers.progress", currencyName, peer, offset, pct));
+        }
+
+    }
+
+}
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 23609da14728cbbdb23236312bbf14b34f5c0bb2..b264f91d209a4c23edb2f867aa50260b5879c327 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
@@ -33,17 +33,15 @@ 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.client.service.local.CurrencyServiceImpl;
-import org.duniter.core.client.service.local.PeerService;
-import org.duniter.core.client.service.local.PeerServiceImpl;
 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.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;
@@ -95,6 +93,7 @@ public class ServiceLocator
                 .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)
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 fd377ed5be4acb49d245acff3519979cbc9470d0..68d7ccbb7f533da43ce6deed98c7f4c65ae874e3 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
@@ -52,8 +52,9 @@ public class ServiceModule extends AbstractModule implements Module {
         bind(PluginInit.class).asEagerSingleton();
         bind(ChangeService.class).asEagerSingleton();
 
-        // indexation services
+        // blockchain indexation services
         bind(BlockchainService.class).asEagerSingleton();
+        bind(NetworkService.class).asEagerSingleton();
 
         // Duniter Client API beans
         bindWithLocator(BlockchainRemoteService.class);
diff --git a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/threadpool/ThreadPool.java b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/threadpool/ThreadPool.java
index 4818cbf50e5e81c7031c56738f450dbf70e4790d..3025a909921d79b086aae5ab22a37467ba2beef9 100644
--- a/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/threadpool/ThreadPool.java
+++ b/duniter4j-es-core/src/main/java/org/duniter/elasticsearch/threadpool/ThreadPool.java
@@ -98,7 +98,7 @@ public class ThreadPool extends AbstractLifecycleComponent<ThreadPool> {
     public void doClose() {}
 
     /**
-     * Schedules an rest when node is started (all services and modules ready)
+     * Schedules an rest when node is started (allOfToList services and modules ready)
      *
      * @param job the rest to execute when node started
      * @return a ScheduledFuture who's get will return when the task is complete and throw an exception if it is canceled
@@ -233,4 +233,7 @@ public class ThreadPool extends AbstractLifecycleComponent<ThreadPool> {
         return canContinue;
     }
 
+    public ScheduledExecutorService scheduler() {
+        return delegate.scheduler();
+    }
 }
diff --git a/duniter4j-es-core/src/main/resources/META-INF/services/org.duniter.core.beans.Bean b/duniter4j-es-core/src/main/resources/META-INF/services/org.duniter.core.beans.Bean
index 0f39d76137c59998de28bf005b0fb022bd52251e..1cf6cb3c777043f71eecb37be3c997efedf6cae7 100644
--- a/duniter4j-es-core/src/main/resources/META-INF/services/org.duniter.core.beans.Bean
+++ b/duniter4j-es-core/src/main/resources/META-INF/services/org.duniter.core.beans.Bean
@@ -9,6 +9,7 @@ org.duniter.core.client.service.HttpServiceImpl
 org.duniter.core.client.service.DataContext
 org.duniter.core.client.service.local.PeerServiceImpl
 org.duniter.core.client.service.local.CurrencyServiceImpl
+org.duniter.core.client.service.local.NetworkServiceImpl
 org.duniter.core.client.dao.mem.MemoryCurrencyDaoImpl
 org.duniter.core.client.dao.mem.MemoryPeerDaoImpl
 
diff --git a/duniter4j-es-core/src/main/resources/i18n/duniter4j-es-core_en_GB.properties b/duniter4j-es-core/src/main/resources/i18n/duniter4j-es-core_en_GB.properties
index a6067b359c9b1b3288467e75f3b34faaa2ce093d..f7bd9849ff9e2736d36bd984c22e02aabd7ae8b3 100644
--- a/duniter4j-es-core/src/main/resources/i18n/duniter4j-es-core_en_GB.properties
+++ b/duniter4j-es-core/src/main/resources/i18n/duniter4j-es-core_en_GB.properties
@@ -34,6 +34,11 @@ duniter4j.config.option.tasks.queueCapacity.description=
 duniter4j.config.option.temp.directory.description=
 duniter4j.config.option.version.description=
 duniter4j.config.parse.error=
+duniter4j.es.networkService.indexPeer=
+duniter4j.es.networkService.indexPeers.progress=
+duniter4j.es.networkService.indexPeers.remoteParametersError=
+duniter4j.es.networkService.indexPeers.succeed=
+duniter4j.es.networkService.indexPeers.task=
 duniter4j.executor.task.waitingExecution=
 duniter4j.job.stopped=
 duniter4j.job.stopping=
diff --git a/duniter4j-es-core/src/main/resources/i18n/duniter4j-es-core_fr_FR.properties b/duniter4j-es-core/src/main/resources/i18n/duniter4j-es-core_fr_FR.properties
index 7e5877795b675d15bc9d49e6fd0be56f4cb548c5..f8468a743c945cf81976dfdda02a1fd0a1171493 100644
--- a/duniter4j-es-core/src/main/resources/i18n/duniter4j-es-core_fr_FR.properties
+++ b/duniter4j-es-core/src/main/resources/i18n/duniter4j-es-core_fr_FR.properties
@@ -34,6 +34,11 @@ duniter4j.config.option.tasks.queueCapacity.description=
 duniter4j.config.option.temp.directory.description=
 duniter4j.config.option.version.description=
 duniter4j.config.parse.error=
+duniter4j.es.networkService.indexPeer=[%s] Indexing peer [%s]...
+duniter4j.es.networkService.indexPeers.progress=[%s] [%s] Indexing peers (%s%%)...
+duniter4j.es.networkService.indexPeers.remoteParametersError=[%s] Error when calling [/blockchain/parameters]\: %s
+duniter4j.es.networkService.indexPeers.succeed=[%s] [%s] All peers indexed\: found [%s] in [%s ms]
+duniter4j.es.networkService.indexPeers.task=[%s] [%s] Indexing peers...
 duniter4j.executor.task.waitingExecution=
 duniter4j.job.stopped=
 duniter4j.job.stopping=
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/service/CitiesRegistryService.java
index dadcc55a39214c4e77bd548a5d38971c925841ad..b4b30dfb38565b4d839cdfad89567f409cb43f54 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/service/CitiesRegistryService.java
@@ -105,7 +105,7 @@ public class CitiesRegistryService extends AbstractService {
 
     public void initCities() {
         if (log.isDebugEnabled()) {
-            log.debug("Initializing all registry cities");
+            log.debug("Initializing allOfToList registry cities");
         }
 
         //File bulkFile = createCitiesBulkFile2();
diff --git a/pom.xml b/pom.xml
index 3896935deef855cc2f3e33b77e429572635d59b5..c5c5a9db4363cd31a6d667af45de2e10577fb8df 100644
--- a/pom.xml
+++ b/pom.xml
@@ -104,6 +104,7 @@
     <module>duniter4j-es-user</module>
     <module>duniter4j-es-gchange</module>
     <module>duniter4j-es-assembly</module>
+      <module>duniter4j-cmd</module>
   </modules>
 
   <scm>
@@ -408,7 +409,7 @@
                 <!-- This is need to override the option version, in configuration classes -->
                 <addDefaultImplementationEntries>true</addDefaultImplementationEntries>
                 <addDefaultSpecificationEntries>true</addDefaultSpecificationEntries>
-                <!-- Main class, configured in sub-modules -->
+                <!-- fr.duniter.cmd.Main class, configured in sub-modules -->
                 <mainClass>${maven.jar.main.class}</mainClass>
               </manifest>
               <manifestEntries>