From 645fd9205b830ae5e6066d986dd513b29e59c408 Mon Sep 17 00:00:00 2001
From: blavenie <benoit.lavenier@e-is.pro>
Date: Mon, 14 Jan 2019 09:33:16 +0100
Subject: [PATCH] [enh] Add websocket implementation on /ws using Netty (= same
 port as default HTTP requests) [enh] Add some BMA REST endpoints :
 /wot/members, blockchain/block/:number, blockchain/current,
 blockchain/patameters [enh] Subscription: Allow to change the button label,
 in email content

---
 .../src/main/assembly/config/favicon.ico      | Bin 0 -> 16958 bytes
 .../src/test/es-home/config/elasticsearch.yml |   5 +-
 .../src/test/es-home/config/logging.yml       |   2 +-
 .../src/test/misc/block_members.sh            |  33 +++
 .../org/duniter/elasticsearch/Plugin.java     |  35 ++-
 .../duniter/elasticsearch/PluginSettings.java |  18 +-
 .../duniter/elasticsearch/dao/BlockDao.java   |  12 +-
 .../elasticsearch/dao/CurrencyExtendDao.java  |   1 +
 .../elasticsearch/dao/impl/BlockDaoImpl.java  | 139 +++++++++++-
 .../dao/impl/CurrencyDaoImpl.java             |  26 ++-
 .../http/WebSocketServerModule.java           |  42 ++++
 .../http/netty/HttpRequestHandler.java        |  46 ++++
 .../http/netty/NettyHttpServerTransport.java  | 104 +++++++++
 .../http/netty/NettyWebSocketServer.java      |  30 +++
 .../http/netty/WebSocketRequestHandler.java   | 106 ++++++++++
 .../websocket/NettyBaseWebSocketEndpoint.java |  27 +++
 .../websocket/NettyWebSocketSession.java      |  60 ++++++
 .../netty/websocket/WebSocketEndpoint.java    |  18 ++
 .../tyrus/TyrusWebSocketServer.java}          |  22 +-
 .../elasticsearch/rest/RestModule.java        |  16 +-
 .../RestBlockchainBlockGetAction.java         |  48 ++---
 .../RestBlockchainBlocksGetAction.java        | 100 +++++++++
 .../RestBlockchainWithNewcomersAction.java    |  80 +++++++
 .../RestBlockchainWithUdAction.java           |  85 ++++++++
 ...=> RestNetworkPeeringPeersPostAction.java} |  12 +-
 .../rest/security/RestSecurityFilter.java     |   2 +-
 .../rest/wot/RestWotLookupGetAction.java      |  87 ++++++++
 .../rest/wot/RestWotMembersGetAction.java     |  92 ++++++++
 .../service/BlockchainService.java            |  60 +++---
 .../service/CurrencyService.java              |  12 ++
 .../elasticsearch/service/NetworkService.java |  18 +-
 .../elasticsearch/service/ServiceModule.java  |   3 +
 .../elasticsearch/service/WotService.java     | 120 +++++++++++
 .../service/changes/ChangeEvent.java          |  21 ++
 .../service/changes/ChangeService.java        |   2 +-
 .../elasticsearch/threadpool/ThreadPool.java  |   4 +-
 .../websocket/WebSocketModule.java            |  32 +--
 .../netty/NettyWebSocketBlockHandler.java     | 200 ++++++++++++++++++
 .../netty/NettyWebSocketChangesHandler.java   | 154 ++++++++++++++
 .../netty/NettyWebSocketPeerHandler.java      | 165 +++++++++++++++
 .../tyrus/WebSocketBlockEndPoint.java         | 168 +++++++++++++++
 .../{ => tyrus}/WebSocketChangesEndPoint.java |  21 +-
 .../subscription/PluginSettings.java          |   6 +-
 .../service/SubscriptionService.java          |  18 +-
 ...ium-plus-pod-subscription_en_GB.properties |   6 +-
 ...ium-plus-pod-subscription_fr_FR.properties |  10 +-
 .../templates/html_email_content.st           |   8 +-
 .../subscription/templates/text_email.st      |  10 +-
 .../websocket/WebsocketUserEventEndPoint.java |   4 +-
 pom.xml                                       |   2 +-
 50 files changed, 2130 insertions(+), 162 deletions(-)
 create mode 100644 cesium-plus-pod-assembly/src/main/assembly/config/favicon.ico
 create mode 100755 cesium-plus-pod-assembly/src/test/misc/block_members.sh
 create mode 100644 cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/WebSocketServerModule.java
 create mode 100644 cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/netty/HttpRequestHandler.java
 create mode 100644 cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/netty/NettyHttpServerTransport.java
 create mode 100644 cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/netty/NettyWebSocketServer.java
 create mode 100644 cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/netty/WebSocketRequestHandler.java
 create mode 100644 cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/netty/websocket/NettyBaseWebSocketEndpoint.java
 create mode 100644 cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/netty/websocket/NettyWebSocketSession.java
 create mode 100644 cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/netty/websocket/WebSocketEndpoint.java
 rename cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/{websocket/WebSocketServer.java => http/tyrus/TyrusWebSocketServer.java} (92%)
 create mode 100644 cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/blockchain/RestBlockchainBlocksGetAction.java
 create mode 100644 cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/blockchain/RestBlockchainWithNewcomersAction.java
 create mode 100644 cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/blockchain/RestBlockchainWithUdAction.java
 rename cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/network/{RestNetworkPeeringPostAction.java => RestNetworkPeeringPeersPostAction.java} (90%)
 create mode 100644 cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/wot/RestWotLookupGetAction.java
 create mode 100644 cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/wot/RestWotMembersGetAction.java
 create mode 100644 cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/service/WotService.java
 create mode 100644 cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/netty/NettyWebSocketBlockHandler.java
 create mode 100644 cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/netty/NettyWebSocketChangesHandler.java
 create mode 100644 cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/netty/NettyWebSocketPeerHandler.java
 create mode 100644 cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/tyrus/WebSocketBlockEndPoint.java
 rename cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/{ => tyrus}/WebSocketChangesEndPoint.java (86%)

diff --git a/cesium-plus-pod-assembly/src/main/assembly/config/favicon.ico b/cesium-plus-pod-assembly/src/main/assembly/config/favicon.ico
new file mode 100644
index 0000000000000000000000000000000000000000..641664a33fb26fdd0f60b9f41ae1d9ccd110c77a
GIT binary patch
literal 16958
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
G|NjdduBt`=

literal 0
HcmV?d00001

diff --git a/cesium-plus-pod-assembly/src/test/es-home/config/elasticsearch.yml b/cesium-plus-pod-assembly/src/test/es-home/config/elasticsearch.yml
index 124763c1..b0c24a34 100644
--- a/cesium-plus-pod-assembly/src/test/es-home/config/elasticsearch.yml
+++ b/cesium-plus-pod-assembly/src/test/es-home/config/elasticsearch.yml
@@ -17,7 +17,7 @@
 # cluster.name: my-application
 cluster.name: cesium-plus-cluster-DEV
 cluster.remote.host: localhost
-cluster.remote.port: 9201
+cluster.remote.port: 9200
 #
 # ------------------------------------ Node ------------------------------------
 #
@@ -125,6 +125,7 @@ duniter.string.analyzer: french
 # Enabling blockchain synchronization (default: false)
 #
 duniter.blockchain.enable: true
+duniter.blockchain.peer.enable: false
 duniter.blockchain.event.user.enable: false
 duniter.blockchain.event.admin.enable: false
 #
@@ -139,6 +140,8 @@ duniter.blockchain.event.admin.enable: false
 duniter.host: g1.duniter.fr
 duniter.port: 443
 duniter.useSsl: true
+
+duniter.network.timeout: 5000
 #
 # Compute statistics on indices (each hour) ? (default: true)
 #
diff --git a/cesium-plus-pod-assembly/src/test/es-home/config/logging.yml b/cesium-plus-pod-assembly/src/test/es-home/config/logging.yml
index 033d8a15..a281deaa 100644
--- a/cesium-plus-pod-assembly/src/test/es-home/config/logging.yml
+++ b/cesium-plus-pod-assembly/src/test/es-home/config/logging.yml
@@ -35,7 +35,7 @@ logger:
   #org.duniter.elasticsearch: DEBUG
   #org.duniter.elasticsearch.service: DEBUG
   #org.duniter.elasticsearch.user.service: DEBUG
-  #org.duniter.elasticsearch.subscription.service: DEBUG
+  #org.duniter.elasticsearch.subscription.service: INFO
 
   org.nuiton.i18n: ERROR
   org.nuiton.config: ERROR
diff --git a/cesium-plus-pod-assembly/src/test/misc/block_members.sh b/cesium-plus-pod-assembly/src/test/misc/block_members.sh
new file mode 100755
index 00000000..2e12d208
--- /dev/null
+++ b/cesium-plus-pod-assembly/src/test/misc/block_members.sh
@@ -0,0 +1,33 @@
+#!/bin/sh
+
+curl -XPOST 'https://g1.data.le-sou.org/g1/block/_search?pretty' -d '
+   {
+     "size": 1000,
+     "query": {
+          filtered: {
+            filter: {
+
+              bool: {
+                must: [
+                  {
+                    exists: {
+                      field: "joiners"
+                    }
+                  },
+                  {
+                    range: {
+                        medianTime: {
+                            gt: 1506837759
+                        }
+                    }
+                  }
+                ]
+              }
+            }
+          }
+        },
+        _source: ["joiners", "number"],
+          sort: {
+            "number" : "asc"
+          }
+   }'
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/Plugin.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/Plugin.java
index bd86c829..71beba0f 100644
--- a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/Plugin.java
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/Plugin.java
@@ -30,12 +30,16 @@ import org.duniter.elasticsearch.security.SecurityModule;
 import org.duniter.elasticsearch.service.ServiceModule;
 import org.duniter.elasticsearch.threadpool.ThreadPool;
 import org.duniter.elasticsearch.websocket.WebSocketModule;
+import org.duniter.elasticsearch.http.netty.NettyHttpServerTransport;
 import org.elasticsearch.common.component.LifecycleComponent;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.inject.Module;
 import org.elasticsearch.common.logging.ESLogger;
 import org.elasticsearch.common.logging.Loggers;
 import org.elasticsearch.common.settings.Settings;
+import org.duniter.elasticsearch.http.WebSocketServerModule;
+import org.elasticsearch.http.HttpServerModule;
+import org.elasticsearch.script.ScriptModule;
 
 import java.util.Collection;
 
@@ -44,10 +48,13 @@ public class Plugin extends org.elasticsearch.plugins.Plugin {
     private ESLogger logger;
 
     private boolean enable;
+    private boolean enableWs;
 
     @Inject public Plugin(Settings settings) {
-        this.enable = settings.getAsBoolean("duniter.enable", true);
         this.logger = Loggers.getLogger("duniter.core", settings, new String[0]);
+
+        this.enable = settings.getAsBoolean("duniter.enable", true);
+        this.enableWs = settings.getAsBoolean("duniter.ws.enable", this.enable);
     }
 
     @Override
@@ -60,38 +67,44 @@ public class Plugin extends org.elasticsearch.plugins.Plugin {
         return "Duniter Core Plugin";
     }
 
-    @Inject
-    public void onModule(org.elasticsearch.script.ScriptModule scriptModule) {
+    public void onModule(ScriptModule scriptModule) {
         // TODO: in ES v5+, see example here :
         // https://github.com/imotov/elasticsearch-native-script-example/blob/60a390f77f2fb25cb89d76de5071c52207a57b5f/src/main/java/org/elasticsearch/examples/nativescript/plugin/NativeScriptExamplesPlugin.java
         scriptModule.registerScript("txcount", BlockchainTxCountScriptFactory.class);
+
+    }
+
+    public void onModule(HttpServerModule httpServerModule) {
+        if (this.enableWs) httpServerModule.setHttpServerTransport(NettyHttpServerTransport.class, "cesium-plus-core");
     }
 
     @Override
     public Collection<Module> nodeModules() {
-        Collection<Module> modules = Lists.newArrayList();
         if (!enable) {
             logger.warn(description() + " has been disabled.");
-            return modules;
+            return Lists.newArrayList();
         }
-        modules.add(new SecurityModule());
 
-        modules.add(new WebSocketModule());
+        Collection<Module> modules = Lists.newArrayList();
+        modules.add(new SecurityModule());
         modules.add(new RestModule());
 
+        // Websocket
+        if (this.enableWs) {
+            modules.add(new WebSocketServerModule());
+            modules.add(new WebSocketModule());
+        }
+
 
         modules.add(new DaoModule());
         modules.add(new ServiceModule());
-        //modules.add(new ScriptModule());
         return modules;
     }
 
     @Override
     public Collection<Class<? extends LifecycleComponent>> nodeServices() {
+        if (!enable) return Lists.newArrayList();
         Collection<Class<? extends LifecycleComponent>> components = Lists.newArrayList();
-        if (!enable) {
-            return components;
-        }
         components.add(PluginSettings.class);
         components.add(ThreadPool.class);
         components.add(PluginInit.class);
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/PluginSettings.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/PluginSettings.java
index c03d2d20..a7239bde 100644
--- a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/PluginSettings.java
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/PluginSettings.java
@@ -23,6 +23,7 @@ package org.duniter.elasticsearch;
  */
 
 
+import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import org.apache.commons.io.FileUtils;
 import org.duniter.core.client.config.Configuration;
@@ -151,6 +152,9 @@ public class PluginSettings extends AbstractLifecycleComponent<PluginSettings> {
         }
 
         initVersion(applicationConfig);
+
+        // Init Http client logging
+        System.setProperty("org.apache.commons.logging.Log", "org.apache.commons.logging.impl.Log4JLogger");
     }
 
     @Override
@@ -298,7 +302,17 @@ public class PluginSettings extends AbstractLifecycleComponent<PluginSettings> {
         String[] includeApis = settings.getAsArray("duniter.p2p.peer.indexedApis");
         // By default: getPeeringPublishedApis + getPeeringTargetedApis
         if (CollectionUtils.isEmpty(includeApis)) {
-            return CollectionUtils.union(getPeeringTargetedApis(), getPeeringPublishedApis());
+            return CollectionUtils.union(
+                    ImmutableList.of(
+                            EndpointApi.BASIC_MERKLED_API,
+                            EndpointApi.BMAS,
+                            EndpointApi.WS2P
+                    ),
+                    CollectionUtils.union(
+                        getPeeringTargetedApis(),
+                        getPeeringPublishedApis()
+                    )
+            );
         }
 
         return Arrays.stream(includeApis).map(EndpointApi::valueOf).collect(Collectors.toList());
@@ -316,7 +330,7 @@ public class PluginSettings extends AbstractLifecycleComponent<PluginSettings> {
     }
 
     /**
-     * Targeted API where to send the peer document.
+     * Targeted API where to sendBlock the peer document.
      * This API should accept a POST request to '/network/peering' (like Duniter node, but can also be a pod)
      * @return
      */
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/dao/BlockDao.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/dao/BlockDao.java
index 9e4dca4c..9770ad5d 100644
--- a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/dao/BlockDao.java
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/dao/BlockDao.java
@@ -24,10 +24,12 @@ package org.duniter.elasticsearch.dao;
 
 import org.duniter.core.beans.Bean;
 import org.duniter.core.client.model.bma.BlockchainBlock;
+import org.duniter.core.client.model.bma.BlockchainParameters;
+import org.elasticsearch.common.bytes.BytesReference;
 
 import java.util.Collection;
 import java.util.List;
-import java.util.Set;
+import java.util.Map;
 
 /**
  * Created by blavenie on 03/04/17.
@@ -65,9 +67,17 @@ public interface BlockDao extends Bean, TypeDao<BlockDao> {
 
     BlockchainBlock getBlockById(String currencyName, String id);
 
+    BytesReference getBlockByIdAsBytes(String currencyName, String id);
+
+    long[] getBlockNumberWithUd(String currencyName);
+
+    long[] getBlockNumberWithNewcomers(String currencyName);
+
     void deleteRange(final String currencyName, final int fromNumber, final int toNumber);
 
     List<BlockchainBlock> getBlocksByIds(String currencyName, Collection<String> ids);
 
     void deleteById(final String currencyName, String id);
+
+    Map<String, String> getMembers(BlockchainParameters parameters);
 }
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/dao/CurrencyExtendDao.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/dao/CurrencyExtendDao.java
index de4aae0a..08482695 100644
--- a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/dao/CurrencyExtendDao.java
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/dao/CurrencyExtendDao.java
@@ -32,4 +32,5 @@ public interface CurrencyExtendDao extends CurrencyDao, IndexTypeDao<CurrencyExt
     String RECORD_TYPE = "record";
 
 
+    String getDefaultCurrencyName();
 }
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/dao/impl/BlockDaoImpl.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/dao/impl/BlockDaoImpl.java
index c3bf1835..3cd69c29 100644
--- a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/dao/impl/BlockDaoImpl.java
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/dao/impl/BlockDaoImpl.java
@@ -25,22 +25,35 @@ package org.duniter.elasticsearch.dao.impl;
 
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Streams;
 import org.duniter.core.client.model.bma.BlockchainBlock;
+import org.duniter.core.client.model.bma.BlockchainParameters;
+import org.duniter.core.client.model.local.Peer;
 import org.duniter.core.exception.TechnicalException;
+import org.duniter.core.util.CollectionUtils;
 import org.duniter.core.util.Preconditions;
 import org.duniter.core.util.StringUtils;
 import org.duniter.core.util.json.JsonSyntaxException;
 import org.duniter.elasticsearch.dao.AbstractDao;
 import org.duniter.elasticsearch.dao.BlockDao;
+import org.duniter.elasticsearch.model.SynchroExecution;
 import org.elasticsearch.action.bulk.BulkRequestBuilder;
+import org.elasticsearch.action.get.GetRequestBuilder;
+import org.elasticsearch.action.get.GetResponse;
 import org.elasticsearch.action.index.IndexRequestBuilder;
+import org.elasticsearch.action.search.SearchRequest;
 import org.elasticsearch.action.search.SearchRequestBuilder;
 import org.elasticsearch.action.search.SearchResponse;
 import org.elasticsearch.action.search.SearchType;
 import org.elasticsearch.action.update.UpdateRequestBuilder;
+import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentFactory;
+import org.elasticsearch.index.query.BoolQueryBuilder;
+import org.elasticsearch.index.query.QueryBuilder;
 import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.search.SearchHit;
 import org.elasticsearch.search.SearchHitField;
 import org.elasticsearch.search.aggregations.AggregationBuilders;
 import org.elasticsearch.search.aggregations.metrics.max.Max;
@@ -48,10 +61,10 @@ import org.elasticsearch.search.highlight.HighlightField;
 import org.elasticsearch.search.sort.SortOrder;
 
 import java.io.IOException;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 /**
  * Created by Benoit on 30/03/2015.
@@ -249,6 +262,101 @@ public class BlockDaoImpl extends AbstractDao implements BlockDao {
         return client.getSourceById(currencyName, TYPE, id, BlockchainBlock.class);
     }
 
+    public BytesReference getBlockByIdAsBytes(String currencyName, String id) {
+        GetResponse response = client.prepareGet(currencyName, TYPE, id).setFetchSource(true).execute().actionGet();
+        if (response.isExists()) {
+            return client.prepareGet(currencyName, TYPE, id).setFetchSource(true).execute().actionGet().getSourceAsBytesRef();
+        }
+        return null;
+    }
+
+
+    public long[] getBlockNumberWithUd(String currencyName) {
+        return getBlockNumbersFromQuery(currencyName,
+                QueryBuilders.boolQuery()
+                    .filter(QueryBuilders.existsQuery(BlockchainBlock.PROPERTY_DIVIDEND)));
+    }
+
+    @Override
+    public long[] getBlockNumberWithNewcomers(String currencyName) {
+        return getBlockNumbersFromQuery(currencyName,
+                QueryBuilders.boolQuery()
+                        .filter(QueryBuilders.existsQuery(BlockchainBlock.PROPERTY_IDENTITIES)));
+    }
+
+    @Override
+    public Map<String, String> getMembers(BlockchainParameters parameters) {
+        Preconditions.checkNotNull(parameters);
+
+        Number medianTime = client.getMandatoryTypedFieldById(parameters.getCurrency(), TYPE, "current", BlockchainBlock.PROPERTY_MEDIAN_TIME);
+        long startMedianTime = medianTime.longValue() - parameters.getMsValidity() - (parameters.getAvgGenTime() / 2);
+
+        QueryBuilder withEvents = QueryBuilders.boolQuery()
+                .minimumNumberShouldMatch(1)
+                .should(QueryBuilders.existsQuery(BlockchainBlock.PROPERTY_JOINERS))
+                .should(QueryBuilders.existsQuery(BlockchainBlock.PROPERTY_ACTIVES));
+
+        QueryBuilder timeQuery = QueryBuilders.rangeQuery(BlockchainBlock.PROPERTY_MEDIAN_TIME)
+                .gte(startMedianTime);
+
+        long total = -1;
+        int from = 0;
+        int size = pluginSettings.getIndexBulkSize();
+        Map<String, String> results = Maps.newHashMap();
+        do {
+            SearchRequestBuilder req = client.prepareSearch(parameters.getCurrency())
+                    .setTypes(BlockDao.TYPE)
+                    .setFrom(from)
+                    .setSize(size)
+                    .addFields(BlockchainBlock.PROPERTY_JOINERS,
+                            BlockchainBlock.PROPERTY_ACTIVES,
+                            BlockchainBlock.PROPERTY_EXCLUDED,
+                            BlockchainBlock.PROPERTY_LEAVERS,
+                            BlockchainBlock.PROPERTY_REVOKED)
+                    .setQuery(QueryBuilders.constantScoreQuery(QueryBuilders.boolQuery().must(withEvents).must(timeQuery)))
+                    .addSort(BlockchainBlock.PROPERTY_NUMBER, SortOrder.ASC)
+                    .setFetchSource(false);
+
+            SearchResponse response = req.execute().actionGet();
+            if (total == -1) total = response.getHits().getTotalHits();
+
+            if (total > 0) {
+                for (SearchHit hit: response.getHits().getHits()) {
+                    Map<String, SearchHitField> fields = hit.getFields();
+                    // membership IN
+                    updateMembershipsMap(results, fields.get(BlockchainBlock.PROPERTY_JOINERS), true);
+                    updateMembershipsMap(results, fields.get(BlockchainBlock.PROPERTY_ACTIVES), true);
+                    // membership OUT
+                    updateMembershipsMap(results, fields.get(BlockchainBlock.PROPERTY_EXCLUDED), false);
+                    updateMembershipsMap(results, fields.get(BlockchainBlock.PROPERTY_LEAVERS), false);
+                    updateMembershipsMap(results, fields.get(BlockchainBlock.PROPERTY_REVOKED), false);
+                }
+            }
+
+            from += size;
+        } while(from<total);
+
+        if (logger.isDebugEnabled()) logger.debug("Wot members found: " + results);
+        return results;
+    }
+
+    private void updateMembershipsMap(Map<String, String> result, SearchHitField field, boolean membershipIn) {
+        List<Object> values = field != null ? field.values() : null;
+        if (CollectionUtils.isEmpty(values)) return;
+        for (Object value: values) {
+            String[] parts = value.toString().split(":");
+            String pubkey = parts[0];
+            if (membershipIn) {
+                String uid = parts[parts.length -1 ];
+                result.put(pubkey, uid);
+            }
+            else {
+                result.remove(pubkey);
+            }
+        }
+
+    }
+
     /**
      * Delete blocks from a start number (using bulk)
      * @param currencyName
@@ -411,4 +519,27 @@ public class BlockDaoImpl extends AbstractDao implements BlockDao {
 
         return result;
     }
+
+    protected long[] getBlockNumbersFromQuery(String currencyName, QueryBuilder query) {
+        int size = pluginSettings.getIndexBulkSize();
+        int offset = 0;
+        long total = -1;
+        List<String> ids = Lists.newArrayList();
+        do {
+            SearchRequestBuilder request = client.prepareSearch(currencyName)
+                    .setTypes(TYPE)
+                    .setFrom(offset)
+                    .setSize(size)
+                    .addSort(BlockchainBlock.PROPERTY_NUMBER, SortOrder.ASC)
+                    .setQuery(query)
+                    .setFetchSource(false);
+            SearchResponse response = request.execute().actionGet();
+            ids.addAll(toListIds(response));
+
+            if (total == -1) total = response.getHits().getTotalHits();
+            offset += size;
+        } while (offset < total);
+
+        return ids.stream().mapToLong(Long::parseLong).toArray();
+    }
 }
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/dao/impl/CurrencyDaoImpl.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/dao/impl/CurrencyDaoImpl.java
index be4033f9..61f73b7b 100644
--- a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/dao/impl/CurrencyDaoImpl.java
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/dao/impl/CurrencyDaoImpl.java
@@ -25,7 +25,10 @@ package org.duniter.elasticsearch.dao.impl;
 import com.fasterxml.jackson.core.JsonProcessingException;
 import com.google.common.collect.Lists;
 import org.duniter.core.client.model.local.Currency;
+import org.duniter.core.client.service.ServiceLocator;
+import org.duniter.core.client.util.KnownCurrencies;
 import org.duniter.core.exception.TechnicalException;
+import org.duniter.core.util.CollectionUtils;
 import org.duniter.core.util.Preconditions;
 import org.duniter.core.util.StringUtils;
 import org.duniter.elasticsearch.dao.AbstractIndexTypeDao;
@@ -33,7 +36,6 @@ import org.duniter.elasticsearch.dao.CurrencyExtendDao;
 import org.elasticsearch.action.admin.indices.create.CreateIndexRequestBuilder;
 import org.elasticsearch.action.index.IndexRequestBuilder;
 import org.elasticsearch.action.search.SearchRequestBuilder;
-import org.elasticsearch.action.search.SearchType;
 import org.elasticsearch.action.update.UpdateRequestBuilder;
 import org.elasticsearch.common.xcontent.XContentBuilder;
 import org.elasticsearch.common.xcontent.XContentFactory;
@@ -48,6 +50,7 @@ import java.util.Map;
 public class CurrencyDaoImpl extends AbstractIndexTypeDao<CurrencyExtendDao> implements CurrencyExtendDao {
 
     protected static final String REGEX_WORD_SEPARATOR = "[-\\t@# _]+";
+    private String defaultCurrency;
 
     public CurrencyDaoImpl(){
         super(INDEX, RECORD_TYPE);
@@ -213,6 +216,27 @@ public class CurrencyDaoImpl extends AbstractIndexTypeDao<CurrencyExtendDao> imp
         }
     }
 
+    /**
+     * Return the default currency
+     * @return
+     */
+    public String getDefaultCurrencyName() {
+
+        if (defaultCurrency != null) return defaultCurrency;
+
+        boolean enableBlockchainIndexation = pluginSettings.enableBlockchainIndexation() && existsIndex();
+        try {
+            List<String> currencyIds = enableBlockchainIndexation ? getCurrencyIds() : null;
+            if (CollectionUtils.isNotEmpty(currencyIds)) {
+                defaultCurrency = currencyIds.get(0);
+                return defaultCurrency;
+            }
+        } catch(Throwable t) {
+            // Continue (index not read yet?)
+        }
+        return KnownCurrencies.G1;
+    }
+
     /* -- internal methods -- */
 
     @Override
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/WebSocketServerModule.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/WebSocketServerModule.java
new file mode 100644
index 00000000..614c55f3
--- /dev/null
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/WebSocketServerModule.java
@@ -0,0 +1,42 @@
+package org.duniter.elasticsearch.http;
+
+/*
+ * #%L
+ * Duniter4j :: ElasticSearch Plugin
+ * %%
+ * Copyright (C) 2014 - 2016 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+
+import org.duniter.elasticsearch.http.netty.NettyWebSocketServer;
+import org.duniter.elasticsearch.http.tyrus.TyrusWebSocketServer;
+import org.elasticsearch.common.inject.AbstractModule;
+
+public class WebSocketServerModule extends AbstractModule {
+    @Override
+    protected void configure() {
+
+        // Netty transport: add websocket support
+        bind(NettyWebSocketServer.class).asEagerSingleton();
+
+        // Tyrus Web socket Server
+        bind(TyrusWebSocketServer.class).asEagerSingleton();
+
+    }
+
+}
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/netty/HttpRequestHandler.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/netty/HttpRequestHandler.java
new file mode 100644
index 00000000..31c12d52
--- /dev/null
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/netty/HttpRequestHandler.java
@@ -0,0 +1,46 @@
+package org.duniter.elasticsearch.http.netty;
+
+import org.duniter.elasticsearch.http.netty.websocket.WebSocketEndpoint;
+import org.elasticsearch.http.netty.NettyHttpChannel;
+import org.elasticsearch.http.netty.NettyHttpRequest;
+import org.jboss.netty.channel.*;
+import org.jboss.netty.handler.codec.http.HttpHeaders;
+import org.jboss.netty.handler.codec.http.HttpRequest;
+
+@ChannelHandler.Sharable
+public class HttpRequestHandler extends org.elasticsearch.http.netty.HttpRequestHandler {
+
+    private final NettyHttpServerTransport serverTransport;
+    private final boolean detailedErrorsEnabled;
+
+    public HttpRequestHandler(NettyHttpServerTransport transport, boolean detailedErrorsEnabled) {
+        super(transport, detailedErrorsEnabled);
+        this.serverTransport = transport;
+        this.detailedErrorsEnabled = detailedErrorsEnabled;
+    }
+
+    @Override
+    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
+        if (e.getMessage() instanceof HttpRequest) {
+            HttpRequest httpRequest = (HttpRequest) e.getMessage();
+            HttpHeaders headers = httpRequest.headers();
+
+            // If web socket path, and connection request
+            if (httpRequest.getUri().startsWith(WebSocketEndpoint.WEBSOCKET_PATH + "/") &&
+                    HttpHeaders.Names.UPGRADE.equalsIgnoreCase(headers.get(org.apache.http.HttpHeaders.CONNECTION)) &&
+                    HttpHeaders.Values.WEBSOCKET.equalsIgnoreCase(headers.get(org.apache.http.HttpHeaders.UPGRADE))) {
+
+                // Convert request and channel
+                NettyHttpRequest request = new NettyHttpRequest(httpRequest, ctx.getChannel());
+                NettyHttpChannel channel = new NettyHttpChannel(this.serverTransport, request, null, this.detailedErrorsEnabled);
+
+                serverTransport.dispathWebsocketRequest(request, channel);
+                ctx.sendUpstream(e);
+                return;
+            }
+        }
+        super.messageReceived(ctx, e);
+    }
+
+
+}
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/netty/NettyHttpServerTransport.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/netty/NettyHttpServerTransport.java
new file mode 100644
index 00000000..3c3eddd1
--- /dev/null
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/netty/NettyHttpServerTransport.java
@@ -0,0 +1,104 @@
+package org.duniter.elasticsearch.http.netty;
+
+
+import org.duniter.elasticsearch.http.netty.websocket.NettyWebSocketSession;
+import org.duniter.elasticsearch.http.netty.websocket.WebSocketEndpoint;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.network.NetworkService;
+import org.elasticsearch.common.path.PathTrie;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.util.BigArrays;
+import org.elasticsearch.http.netty.NettyHttpChannel;
+import org.elasticsearch.http.netty.NettyHttpRequest;
+import org.elasticsearch.rest.BytesRestResponse;
+import org.elasticsearch.rest.RestChannel;
+import org.elasticsearch.rest.RestRequest;
+import org.elasticsearch.rest.RestStatus;
+import org.elasticsearch.rest.support.RestUtils;
+import org.jboss.netty.channel.*;
+import org.jboss.netty.handler.codec.http.HttpHeaders;
+import org.jboss.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
+import org.jboss.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
+
+public class NettyHttpServerTransport extends  org.elasticsearch.http.netty.NettyHttpServerTransport {
+
+    private final PathTrie<Class<? extends WebSocketEndpoint>> websocketEndpoints;
+
+    @Inject
+    public NettyHttpServerTransport(Settings settings,
+                                    NetworkService networkService,
+                                    BigArrays bigArrays) {
+        super(settings, networkService, bigArrays);
+        this.websocketEndpoints = new PathTrie(RestUtils.REST_DECODER);
+    }
+
+    @Override
+    public ChannelPipelineFactory configureServerChannelPipelineFactory() {
+        return new HttpChannelPipelineFactory(this, this.detailedErrorsEnabled);
+    }
+
+    protected static class HttpChannelPipelineFactory extends org.elasticsearch.http.netty.NettyHttpServerTransport.HttpChannelPipelineFactory {
+
+        protected final HttpRequestHandler handler;
+
+        public HttpChannelPipelineFactory(NettyHttpServerTransport transport, boolean detailedErrorsEnabled) {
+            super(transport, detailedErrorsEnabled);
+            this.handler = new HttpRequestHandler(transport, detailedErrorsEnabled);
+        }
+
+        public ChannelPipeline getPipeline() throws Exception {
+            ChannelPipeline pipeline = super.getPipeline();
+
+            // Replace default HttpRequestHandler by a custom handler with WebSocket support
+            pipeline.replace("handler", "handler", handler);
+
+            return pipeline;
+        }
+    }
+
+
+    public <T extends WebSocketEndpoint> void addEndpoint(String path, Class<T> handler) {
+        websocketEndpoints.insert(path, handler);
+    }
+
+    @Override
+    protected void dispatchRequest(RestRequest request, RestChannel channel) {
+        super.dispatchRequest(request, channel);
+    }
+
+    public void dispathWebsocketRequest(NettyHttpRequest request, NettyHttpChannel channel) {
+
+        WebSocketEndpoint wsEndpoint = createWebsocketEndpoint(request);
+        if (wsEndpoint != null) {
+
+            WebSocketRequestHandler channelHandler = new WebSocketRequestHandler(wsEndpoint);
+
+            // Replacing the new handler to the existing pipeline to handle
+            request.getChannel().getPipeline().replace("handler", "websocketHandler", channelHandler);
+
+            // Execute the handshake
+            channelHandler.handleHandshake(request);
+
+        } else if (request.method() == RestRequest.Method.OPTIONS) {
+            channel.sendResponse(new BytesRestResponse(RestStatus.OK));
+        } else {
+            channel.sendResponse(new BytesRestResponse(RestStatus.BAD_REQUEST, "Websocket to URI [" + request.uri() + "] not authorized"));
+        }
+    }
+
+
+    /* -- protected method -- */
+
+    protected <T extends WebSocketEndpoint> T createWebsocketEndpoint(RestRequest request) {
+        String path = request.rawPath();
+        Class<? extends WebSocketEndpoint> clazz = websocketEndpoints != null ? websocketEndpoints.retrieve(path, request.params()) : null;
+        try {
+            return (T)clazz.newInstance();
+        }
+        catch(Exception e) {
+            logger.error(String.format("Could not create websocket endpoint instance, from class %s: %s", clazz.getName(), e.getMessage()), e);
+            return null;
+        }
+    }
+
+}
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/netty/NettyWebSocketServer.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/netty/NettyWebSocketServer.java
new file mode 100644
index 00000000..11a6144c
--- /dev/null
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/netty/NettyWebSocketServer.java
@@ -0,0 +1,30 @@
+package org.duniter.elasticsearch.http.netty;
+
+import org.duniter.elasticsearch.http.netty.websocket.WebSocketEndpoint;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.logging.ESLogger;
+import org.elasticsearch.common.logging.Loggers;
+import org.elasticsearch.http.HttpServerTransport;
+
+public class NettyWebSocketServer {
+
+    private final ESLogger logger;
+    private HttpServerTransport serverTransport;
+
+    @Inject
+    public  NettyWebSocketServer(HttpServerTransport serverTransport) {
+        logger = Loggers.getLogger("duniter.ws");
+        this.serverTransport = serverTransport;
+    }
+
+    public <T extends WebSocketEndpoint> void addEndpoint(String path, Class<T> handler) {
+        if (serverTransport instanceof NettyHttpServerTransport) {
+            NettyHttpServerTransport transport = (NettyHttpServerTransport)serverTransport;
+            transport.addEndpoint(path, handler);
+        }
+        else {
+            logger.warn("Ignoring websocket endpoint {" + handler.getName()+ "}: server transport is not compatible");
+        }
+    }
+
+}
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/netty/WebSocketRequestHandler.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/netty/WebSocketRequestHandler.java
new file mode 100644
index 00000000..31cec32c
--- /dev/null
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/netty/WebSocketRequestHandler.java
@@ -0,0 +1,106 @@
+package org.duniter.elasticsearch.http.netty;
+
+import org.duniter.elasticsearch.http.netty.websocket.NettyWebSocketSession;
+import org.duniter.elasticsearch.http.netty.websocket.WebSocketEndpoint;
+import org.elasticsearch.common.bytes.ChannelBufferBytesReference;
+import org.elasticsearch.http.netty.NettyHttpRequest;
+import org.jboss.netty.channel.*;
+import org.jboss.netty.handler.codec.http.HttpHeaders;
+import org.jboss.netty.handler.codec.http.websocketx.*;
+
+import javax.websocket.CloseReason;
+
+@ChannelHandler.Sharable
+public class WebSocketRequestHandler extends SimpleChannelHandler {
+
+    private final WebSocketEndpoint endpoint;
+    private NettyWebSocketSession session;
+
+    public WebSocketRequestHandler(WebSocketEndpoint endpoint) {
+        super();
+        this.endpoint = endpoint;
+    }
+
+    /* Do the handshaking for WebSocket request */
+    public ChannelFuture handleHandshake(final NettyHttpRequest request) {
+        WebSocketServerHandshakerFactory wsFactory =
+                new WebSocketServerHandshakerFactory(getWebSocketURL(request), null, true);
+        WebSocketServerHandshaker handshaker = wsFactory.newHandshaker(request.request());
+        if (handshaker == null) {
+            return wsFactory.sendUnsupportedWebSocketVersionResponse(request.getChannel());
+        }
+
+        ChannelFuture future = handshaker.handshake(request.getChannel(), request.request());
+        future.addListener(new ChannelFutureListener() {
+            public void operationComplete(ChannelFuture future) {
+                // Session is open
+                session = new NettyWebSocketSession(future.getChannel(), request.params());
+                endpoint.onOpen(session);
+            }
+        });
+        return future;
+    }
+
+    @Override
+    public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
+        if (endpoint == null) return; // not open
+
+        Object msg = e.getMessage();
+        if (msg instanceof NettyWebSocketSession) {
+            endpoint.onOpen((NettyWebSocketSession)msg);
+        }
+
+        else if (msg instanceof WebSocketFrame) {
+
+            // Received binary
+            if (msg instanceof BinaryWebSocketFrame) {
+                BinaryWebSocketFrame frame = (BinaryWebSocketFrame)msg;
+                endpoint.onMessage(new ChannelBufferBytesReference(frame.getBinaryData()));
+            }
+
+            // Received text
+            else if (msg instanceof TextWebSocketFrame) {
+                TextWebSocketFrame frame = (TextWebSocketFrame) msg;
+                endpoint.onMessage(frame.getText());
+            }
+
+            // Ping event
+            else if (msg instanceof PingWebSocketFrame) {
+               // TODO
+            }
+
+            // Pong event
+            else if (msg instanceof PongWebSocketFrame) {
+                // TODO
+            }
+
+            // Close
+            else if (msg instanceof CloseWebSocketFrame) {
+                ctx.getChannel().close();
+                CloseWebSocketFrame frame = (CloseWebSocketFrame)msg;
+                endpoint.onClose(new CloseReason(getCloseCode(frame), frame.getReasonText()));
+            }
+
+            // Unknown event
+            else {
+                System.out.println("Unsupported WebSocketFrame");
+            }
+        }
+    }
+
+    protected String getWebSocketURL(NettyHttpRequest req) {
+        return "ws://" + req.request().headers().get(HttpHeaders.Names.HOST) + req.rawPath() ;
+    }
+
+    protected CloseReason.CloseCode getCloseCode(CloseWebSocketFrame frame) {
+
+        int statusCode = frame.getStatusCode();
+        if (statusCode == -1) return CloseReason.CloseCodes.NO_STATUS_CODE;
+        try {
+            return CloseReason.CloseCodes.getCloseCode(statusCode);
+        }
+        catch(IllegalArgumentException e) {
+            return CloseReason.CloseCodes.NO_STATUS_CODE;
+        }
+    }
+}
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/netty/websocket/NettyBaseWebSocketEndpoint.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/netty/websocket/NettyBaseWebSocketEndpoint.java
new file mode 100644
index 00000000..5c079323
--- /dev/null
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/netty/websocket/NettyBaseWebSocketEndpoint.java
@@ -0,0 +1,27 @@
+package org.duniter.elasticsearch.http.netty.websocket;
+
+import org.elasticsearch.common.bytes.BytesReference;
+
+import javax.websocket.CloseReason;
+
+public class NettyBaseWebSocketEndpoint implements WebSocketEndpoint {
+
+    @Override
+    public void onOpen(NettyWebSocketSession session) {
+
+    }
+
+    @Override
+    public void onMessage(String message) {
+
+    }
+
+    @Override
+    public void onMessage(BytesReference bytes) {
+
+    }
+
+    public void onClose(CloseReason reason) {
+    }
+
+}
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/netty/websocket/NettyWebSocketSession.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/netty/websocket/NettyWebSocketSession.java
new file mode 100644
index 00000000..914d9722
--- /dev/null
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/netty/websocket/NettyWebSocketSession.java
@@ -0,0 +1,60 @@
+package org.duniter.elasticsearch.http.netty.websocket;
+
+import org.elasticsearch.common.bytes.BytesReference;
+import org.elasticsearch.http.netty.NettyHttpRequest;
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelFuture;
+import org.jboss.netty.channel.ChannelFutureListener;
+import org.jboss.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
+import org.jboss.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
+import org.jboss.netty.handler.codec.http.websocketx.TextWebSocketFrame;
+
+import javax.websocket.CloseReason;
+import java.util.Map;
+
+public class NettyWebSocketSession {
+
+    private Channel channel;
+    private Map<String, String> pathParameters;
+
+    public NettyWebSocketSession(Channel channel, Map<String, String> pathParameters) {
+        this.channel = channel;
+        this.pathParameters = pathParameters;
+    }
+
+    public void close(CloseReason closeReason) {
+
+        CloseWebSocketFrame frame = new CloseWebSocketFrame(closeReason.getCloseCode().getCode(), closeReason.getReasonPhrase());
+        ChannelFuture future = channel.write(frame);
+
+        future.addListener(ChannelFutureListener.CLOSE);
+    }
+
+    public void sendText(String text) {
+        channel.write(new TextWebSocketFrame(text));
+    }
+
+    public void sendBinary(ChannelBuffer buffer) {
+        BinaryWebSocketFrame frame = new BinaryWebSocketFrame();
+        frame.setBinaryData(buffer);
+        channel.write(frame);
+    }
+
+    public void sendBinary(BytesReference bytes) {
+        sendBinary(bytes.toChannelBuffer());
+    }
+
+    public Map<String, String> getPathParameters() {
+        return pathParameters;
+    }
+
+    public void setPathParameters( Map<String, String> pathParameters) {
+        this.pathParameters = pathParameters;
+    }
+
+    public String getId() {
+        return String.valueOf(this.hashCode());
+    }
+
+}
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/netty/websocket/WebSocketEndpoint.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/netty/websocket/WebSocketEndpoint.java
new file mode 100644
index 00000000..9c1b8c2d
--- /dev/null
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/netty/websocket/WebSocketEndpoint.java
@@ -0,0 +1,18 @@
+package org.duniter.elasticsearch.http.netty.websocket;
+
+import org.elasticsearch.common.bytes.BytesReference;
+
+import javax.websocket.CloseReason;
+
+public interface WebSocketEndpoint {
+
+    String WEBSOCKET_PATH = "/ws";
+
+    void onOpen(NettyWebSocketSession session);
+
+    void onMessage(String message);
+
+    void onMessage(BytesReference bytes);
+
+    void onClose(CloseReason reason);
+}
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/WebSocketServer.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/tyrus/TyrusWebSocketServer.java
similarity index 92%
rename from cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/WebSocketServer.java
rename to cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/tyrus/TyrusWebSocketServer.java
index 4abb50fd..778cdebd 100644
--- a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/WebSocketServer.java
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/http/tyrus/TyrusWebSocketServer.java
@@ -1,4 +1,4 @@
-package org.duniter.elasticsearch.websocket;
+package org.duniter.elasticsearch.http.tyrus;
 
 /*
  * #%L
@@ -45,6 +45,9 @@ import org.duniter.elasticsearch.threadpool.ThreadPool;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.logging.ESLogger;
 import org.elasticsearch.common.logging.Loggers;
+import org.elasticsearch.http.HttpServer;
+import org.elasticsearch.rest.RestChannel;
+import org.elasticsearch.rest.RestRequest;
 import org.glassfish.tyrus.server.Server;
 
 import java.net.BindException;
@@ -54,7 +57,7 @@ import java.security.PrivilegedExceptionAction;
 import java.util.ArrayList;
 import java.util.List;
 
-public class WebSocketServer {
+public class TyrusWebSocketServer {
 
 
     public static final String WS_PATH = "/ws";
@@ -64,10 +67,13 @@ public class WebSocketServer {
     private List<Class<?>> endPoints = new ArrayList<>();
 
     @Inject
-    public WebSocketServer(final PluginSettings pluginSettings, ThreadPool threadPool) {
+    public TyrusWebSocketServer(final PluginSettings pluginSettings,
+                                ThreadPool threadPool) {
         logger = Loggers.getLogger("duniter.ws", pluginSettings.getSettings(), new String[0]);
+
         // If WS enable
         if (pluginSettings.getWebSocketEnable()) {
+
             // When node started
             threadPool.scheduleOnStarted(() -> {
                 // startScheduling WS server
@@ -76,6 +82,8 @@ public class WebSocketServer {
                         getEndPoints());
             });
         }
+
+
     }
 
     public void addEndPoint(Class<?> endPoint) {
@@ -104,15 +112,15 @@ public class WebSocketServer {
 
             final Server server = new Server(host, port, WS_PATH, null, endPoints) ;
             try {
-                AccessController.doPrivileged(new PrivilegedExceptionAction<Server>() {
+                AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
                     @Override
-                    public Server run() throws Exception {
+                    public Void run() throws Exception {
                             // Tyrus tries to load the server code using reflection. In Elasticsearch 2.x Java
                             // security manager is used which breaks the reflection code as it can't find the class.
                             // This is a workaround for that
                             Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
                             server.start();
-                            return server;
+                            return null;
                     }
                 });
                 started = true;
@@ -130,6 +138,8 @@ public class WebSocketServer {
 
         }
 
+
+
         if (started) {
             logger.info(String.format("Websocket server started {%s:%s%s}", host, port, WS_PATH));
         }
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/RestModule.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/RestModule.java
index 193fe0b3..19e935e1 100644
--- a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/RestModule.java
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/RestModule.java
@@ -23,15 +23,16 @@ package org.duniter.elasticsearch.rest;
  */
 
 import org.duniter.elasticsearch.rest.attachment.RestImageAttachmentAction;
-import org.duniter.elasticsearch.rest.blockchain.RestBlockchainBlockGetAction;
-import org.duniter.elasticsearch.rest.blockchain.RestBlockchainParametersGetAction;
+import org.duniter.elasticsearch.rest.blockchain.*;
 import org.duniter.elasticsearch.rest.network.RestNetworkPeeringGetAction;
-import org.duniter.elasticsearch.rest.network.RestNetworkPeeringPostAction;
+import org.duniter.elasticsearch.rest.network.RestNetworkPeeringPeersPostAction;
 import org.duniter.elasticsearch.rest.node.RestNodeSummaryGetAction;
 import org.duniter.elasticsearch.rest.security.RestSecurityAuthAction;
 import org.duniter.elasticsearch.rest.security.RestSecurityController;
 import org.duniter.elasticsearch.rest.security.RestSecurityFilter;
 import org.duniter.elasticsearch.rest.security.RestSecurityGetChallengeAction;
+import org.duniter.elasticsearch.rest.wot.RestWotLookupGetAction;
+import org.duniter.elasticsearch.rest.wot.RestWotMembersGetAction;
 import org.elasticsearch.common.inject.AbstractModule;
 import org.elasticsearch.common.inject.Module;
 
@@ -56,11 +57,18 @@ public class RestModule extends AbstractModule implements Module {
 
         // Network
         bind(RestNetworkPeeringGetAction.class).asEagerSingleton();
-        bind(RestNetworkPeeringPostAction.class).asEagerSingleton();
+        bind(RestNetworkPeeringPeersPostAction.class).asEagerSingleton();
 
         // Blockchain
         bind(RestBlockchainParametersGetAction.class).asEagerSingleton();
         bind(RestBlockchainBlockGetAction.class).asEagerSingleton();
+        bind(RestBlockchainWithUdAction.class).asEagerSingleton();
+        bind(RestBlockchainWithNewcomersAction.class).asEagerSingleton();
+        bind(RestBlockchainBlocksGetAction.class).asEagerSingleton();
+
+        // Wot
+        bind(RestWotLookupGetAction.class).asEagerSingleton();
+        bind(RestWotMembersGetAction.class).asEagerSingleton();
 
     }
 }
\ No newline at end of file
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/blockchain/RestBlockchainBlockGetAction.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/blockchain/RestBlockchainBlockGetAction.java
index e8c4c434..e37fbe28 100644
--- a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/blockchain/RestBlockchainBlockGetAction.java
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/blockchain/RestBlockchainBlockGetAction.java
@@ -22,19 +22,14 @@ package org.duniter.elasticsearch.rest.blockchain;
  * #L%
  */
 
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.apache.http.entity.ContentType;
-import org.duniter.core.client.config.Configuration;
-import org.duniter.core.client.model.bma.BlockchainBlock;
-import org.duniter.core.client.model.bma.jackson.JacksonUtils;
 import org.duniter.core.exception.TechnicalException;
 import org.duniter.core.util.StringUtils;
-import org.duniter.elasticsearch.PluginSettings;
-import org.duniter.elasticsearch.rest.AbstractRestPostIndexAction;
+import org.duniter.elasticsearch.dao.BlockDao;
+import org.duniter.elasticsearch.rest.RestXContentBuilder;
 import org.duniter.elasticsearch.rest.XContentRestResponse;
 import org.duniter.elasticsearch.rest.security.RestSecurityController;
-import org.duniter.elasticsearch.service.BlockchainService;
 import org.duniter.elasticsearch.service.CurrencyService;
+import org.elasticsearch.action.get.GetResponse;
 import org.elasticsearch.client.Client;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.settings.Settings;
@@ -50,11 +45,12 @@ import java.io.IOException;
  */
 public class RestBlockchainBlockGetAction extends BaseRestHandler {
 
-    private BlockchainService blockchainService;
+    private Client client;
+    private CurrencyService currencyService;
 
     @Inject
     public RestBlockchainBlockGetAction(Settings settings, RestController controller, Client client, RestSecurityController securityController,
-                                        BlockchainService blockchainService) {
+                                        CurrencyService currencyService) {
         super(settings, controller, client);
 
         securityController.allow(RestRequest.Method.GET, "(/[^/]+)?/blockchain/block/[0-9]+");
@@ -65,29 +61,21 @@ public class RestBlockchainBlockGetAction extends BaseRestHandler {
         controller.registerHandler(RestRequest.Method.GET, "/{index}/blockchain/block/{number}", this);
         controller.registerHandler(RestRequest.Method.GET, "/{index}/blockchain/current", this);
 
-        this.blockchainService = blockchainService;
+        this.client = client;
+        this.currencyService = currencyService;
     }
 
     @Override
-    protected void handleRequest(RestRequest request, RestChannel channel, Client client) throws Exception {
-        String currency = request.param("index");
-        int number = request.paramAsInt("number", -1);
-        boolean isCurrent = (number == -1);
-
-        BlockchainBlock block;
-        if (isCurrent) {
-            block = blockchainService.getCurrentBlock(currency);
-        }
-        else {
-            block = blockchainService.getBlockById(currency, number);
-        }
+    protected void handleRequest(RestRequest request, RestChannel channel, Client client) {
+        String currency = currencyService.safeGetCurrency(request.param("index"));
+        String number = request.param("number");
+        boolean isCurrent = StringUtils.isBlank(number);
 
         try {
-            channel.sendResponse(new BytesRestResponse(RestStatus.OK,
-                    ContentType.APPLICATION_JSON.toString(),
-                    getObjectMapper()
-                            .writerWithDefaultPrettyPrinter()
-                            .writeValueAsString(block)));
+            GetResponse response = client.prepareGet(currency, BlockDao.TYPE, isCurrent ? "current" : number)
+                    .execute().actionGet();
+            XContentBuilder builder = RestXContentBuilder.restContentBuilder(request).rawValue(response.getSourceAsBytesRef());
+            channel.sendResponse(new XContentRestResponse(request, RestStatus.OK, builder));
         }
         catch(IOException ioe) {
             if (isCurrent)
@@ -96,8 +84,4 @@ public class RestBlockchainBlockGetAction extends BaseRestHandler {
                 throw new TechnicalException(String.format("Error while generating JSON for [/blockchain/block/%s]: %s", number, ioe.getMessage()), ioe);
         }
     }
-
-    protected ObjectMapper getObjectMapper() {
-        return JacksonUtils.getThreadObjectMapper();
-    }
 }
\ No newline at end of file
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/blockchain/RestBlockchainBlocksGetAction.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/blockchain/RestBlockchainBlocksGetAction.java
new file mode 100644
index 00000000..ec3019ef
--- /dev/null
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/blockchain/RestBlockchainBlocksGetAction.java
@@ -0,0 +1,100 @@
+package org.duniter.elasticsearch.rest.blockchain;
+
+/*
+ * #%L
+ * duniter4j-elasticsearch-plugin
+ * %%
+ * Copyright (C) 2014 - 2016 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+import org.duniter.core.client.model.bma.BlockchainBlock;
+import org.duniter.core.exception.TechnicalException;
+import org.duniter.core.util.StringUtils;
+import org.duniter.elasticsearch.dao.BlockDao;
+import org.duniter.elasticsearch.rest.RestXContentBuilder;
+import org.duniter.elasticsearch.rest.XContentRestResponse;
+import org.duniter.elasticsearch.rest.security.RestSecurityController;
+import org.duniter.elasticsearch.service.CurrencyService;
+import org.elasticsearch.action.get.GetResponse;
+import org.elasticsearch.action.search.SearchRequest;
+import org.elasticsearch.action.search.SearchRequestBuilder;
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.client.Client;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.index.query.QueryBuilders;
+import org.elasticsearch.rest.*;
+import org.elasticsearch.search.SearchHit;
+import org.elasticsearch.search.sort.SortOrder;
+
+import java.io.IOException;
+
+/**
+ * A rest to post a request to process a new currency/peer.
+ *
+ */
+public class RestBlockchainBlocksGetAction extends BaseRestHandler {
+
+    private Client client;
+    private CurrencyService currencyService;
+
+    @Inject
+    public RestBlockchainBlocksGetAction(Settings settings, RestController controller, Client client, RestSecurityController securityController,
+                                         CurrencyService currencyService) {
+        super(settings, controller, client);
+
+        securityController.allow(RestRequest.Method.GET, "(/[^/]+)?/blockchain/blocks/[0-9]+/[0-9]+");
+
+        controller.registerHandler(RestRequest.Method.GET, "/blockchain/blocks/{count}/{from}", this);
+        controller.registerHandler(RestRequest.Method.GET, "/{index}/blockchain/blocks/{count}/{from}", this);
+
+        this.client = client;
+        this.currencyService = currencyService;
+    }
+
+    @Override
+    protected void handleRequest(RestRequest request, RestChannel channel, Client client) {
+        String currency = currencyService.safeGetCurrency(request.param("index"));
+        int count = request.paramAsInt("count", 100);
+        int from = request.paramAsInt("from", 0);
+
+        try {
+            SearchRequestBuilder req = client.prepareSearch(currency)
+                    .setTypes(BlockDao.TYPE)
+                    .setFrom(0)
+                    .setSize(count)
+                    .setQuery(QueryBuilders.constantScoreQuery(QueryBuilders.boolQuery().filter(QueryBuilders.rangeQuery(BlockchainBlock.PROPERTY_NUMBER).lte(from))))
+                    .setFetchSource(true)
+                    .addSort(BlockchainBlock.PROPERTY_NUMBER, SortOrder.ASC);
+
+            XContentBuilder builder = RestXContentBuilder.restContentBuilder(request).startArray();
+
+            SearchResponse resp = req.execute().actionGet();
+            for (SearchHit hit: resp.getHits().getHits()) {
+                builder.rawValue(hit.getSourceRef());
+            }
+            builder.endArray();
+
+            channel.sendResponse(new XContentRestResponse(request, RestStatus.OK, builder));
+        }
+        catch(IOException ioe) {
+            throw new TechnicalException(String.format("Error while generating JSON for [/blockchain/blocks/<count>/<from>]: %s", ioe.getMessage()), ioe);
+        }
+    }
+}
\ No newline at end of file
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/blockchain/RestBlockchainWithNewcomersAction.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/blockchain/RestBlockchainWithNewcomersAction.java
new file mode 100644
index 00000000..0b17b595
--- /dev/null
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/blockchain/RestBlockchainWithNewcomersAction.java
@@ -0,0 +1,80 @@
+package org.duniter.elasticsearch.rest.blockchain;
+
+/*
+ * #%L
+ * duniter4j-elasticsearch-plugin
+ * %%
+ * Copyright (C) 2014 - 2016 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.duniter.core.client.model.bma.jackson.JacksonUtils;
+import org.duniter.core.exception.TechnicalException;
+import org.duniter.elasticsearch.rest.RestXContentBuilder;
+import org.duniter.elasticsearch.rest.XContentRestResponse;
+import org.duniter.elasticsearch.rest.security.RestSecurityController;
+import org.duniter.elasticsearch.service.BlockchainService;
+import org.elasticsearch.client.Client;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.rest.*;
+
+import java.io.IOException;
+
+/**
+ * A rest to post a request to process a new currency/peer.
+ *
+ */
+public class RestBlockchainWithNewcomersAction extends BaseRestHandler {
+
+    private BlockchainService blockchainService;
+
+    @Inject
+    public RestBlockchainWithNewcomersAction(Settings settings, RestController controller, Client client, RestSecurityController securityController,
+                                             BlockchainService blockchainService) {
+        super(settings, controller, client);
+
+        securityController.allow(RestRequest.Method.GET, "(/[^/]+)?/blockchain/with/newcomers");
+
+        controller.registerHandler(RestRequest.Method.GET, "/blockchain/with/newcomers", this);
+        controller.registerHandler(RestRequest.Method.GET, "/{index}/blockchain/with/newcomers", this);
+
+        this.blockchainService = blockchainService;
+    }
+
+    @Override
+    protected void handleRequest(RestRequest request, RestChannel channel, Client client) throws Exception {
+        String currency = request.param("index");
+
+        try {
+            XContentBuilder builder = RestXContentBuilder.restContentBuilder(request).startObject()
+                    .startObject("result")
+                    .field("blocks", blockchainService.getBlockNumberWithNewcomers(currency))
+                    .endObject()
+                    .endObject();
+            channel.sendResponse(new XContentRestResponse(request, RestStatus.OK, builder));
+        } catch(IOException ioe) {
+            throw new TechnicalException(String.format("Error while generating JSON for [/blockchain/with/newcomers]: %s", ioe.getMessage()), ioe);
+        }
+    }
+
+    protected ObjectMapper getObjectMapper() {
+        return JacksonUtils.getThreadObjectMapper();
+    }
+}
\ No newline at end of file
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/blockchain/RestBlockchainWithUdAction.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/blockchain/RestBlockchainWithUdAction.java
new file mode 100644
index 00000000..e6ebf152
--- /dev/null
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/blockchain/RestBlockchainWithUdAction.java
@@ -0,0 +1,85 @@
+package org.duniter.elasticsearch.rest.blockchain;
+
+/*
+ * #%L
+ * duniter4j-elasticsearch-plugin
+ * %%
+ * Copyright (C) 2014 - 2016 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.http.entity.ContentType;
+import org.duniter.core.client.model.bma.BlockchainBlock;
+import org.duniter.core.client.model.bma.jackson.JacksonUtils;
+import org.duniter.core.exception.TechnicalException;
+import org.duniter.elasticsearch.rest.RestXContentBuilder;
+import org.duniter.elasticsearch.rest.XContentRestResponse;
+import org.duniter.elasticsearch.rest.XContentThrowableRestResponse;
+import org.duniter.elasticsearch.rest.security.RestSecurityController;
+import org.duniter.elasticsearch.service.BlockchainService;
+import org.elasticsearch.client.Client;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.rest.*;
+
+import java.io.IOException;
+
+import static org.elasticsearch.ExceptionsHelper.detailedMessage;
+
+/**
+ * A rest to post a request to process a new currency/peer.
+ *
+ */
+public class RestBlockchainWithUdAction extends BaseRestHandler {
+
+    private BlockchainService blockchainService;
+
+    @Inject
+    public RestBlockchainWithUdAction(Settings settings, RestController controller, Client client, RestSecurityController securityController,
+                                      BlockchainService blockchainService) {
+        super(settings, controller, client);
+
+        securityController.allow(RestRequest.Method.GET, "(/[^/]+)?/blockchain/with/ud");
+
+        controller.registerHandler(RestRequest.Method.GET, "/blockchain/with/ud", this);
+        controller.registerHandler(RestRequest.Method.GET, "/{index}/blockchain/with/ud", this);
+
+        this.blockchainService = blockchainService;
+    }
+
+    @Override
+    protected void handleRequest(RestRequest request, RestChannel channel, Client client) throws Exception {
+        String currency = request.param("index");
+
+        try {
+            XContentBuilder builder = RestXContentBuilder.restContentBuilder(request).startObject()
+                    .startObject("result")
+                    .field("blocks", blockchainService.getBlockNumberWithUd(currency))
+                    .endObject()
+                    .endObject();
+            channel.sendResponse(new XContentRestResponse(request, RestStatus.OK, builder));
+        } catch(IOException ioe) {
+            throw new TechnicalException(String.format("Error while generating JSON for [/blockchain/with/ud]: %s", ioe.getMessage()), ioe);
+        }
+    }
+
+    protected ObjectMapper getObjectMapper() {
+        return JacksonUtils.getThreadObjectMapper();
+    }
+}
\ No newline at end of file
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/network/RestNetworkPeeringPostAction.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/network/RestNetworkPeeringPeersPostAction.java
similarity index 90%
rename from cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/network/RestNetworkPeeringPostAction.java
rename to cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/network/RestNetworkPeeringPeersPostAction.java
index 8d9d6d28..d6fd4802 100644
--- a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/network/RestNetworkPeeringPostAction.java
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/network/RestNetworkPeeringPeersPostAction.java
@@ -58,23 +58,23 @@ import java.util.Properties;
  * A rest to post a request to process a new currency/peer.
  *
  */
-public class RestNetworkPeeringPostAction extends BaseRestHandler {
+public class RestNetworkPeeringPeersPostAction extends BaseRestHandler {
 
 
     private NetworkService networkService;
 
     @Inject
-    public RestNetworkPeeringPostAction(Settings settings, PluginSettings pluginSettings, RestController controller, Client client,
-                                        RestSecurityController securityController,
-                                        NetworkService networkService) {
+    public RestNetworkPeeringPeersPostAction(Settings settings, PluginSettings pluginSettings, RestController controller, Client client,
+                                             RestSecurityController securityController,
+                                             NetworkService networkService) {
         super(settings, controller, client);
 
         if (StringUtils.isBlank(pluginSettings.getClusterRemoteHost())) {
             logger.warn(String.format("The cluster address can not be published on the network. /\\!\\\\ Fill in the options [cluster.remote.xxx] in the configuration (recommended)."));
         }
         else {
-            securityController.allow(RestRequest.Method.POST, "/network/peering");
-            controller.registerHandler(RestRequest.Method.POST, "/network/peering", this);
+            securityController.allow(RestRequest.Method.POST, "/network/peering/peers");
+            controller.registerHandler(RestRequest.Method.POST, "/network/peering/peers", this);
         }
 
         this.networkService = networkService;
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/security/RestSecurityFilter.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/security/RestSecurityFilter.java
index c38ed56e..53f20a82 100644
--- a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/security/RestSecurityFilter.java
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/security/RestSecurityFilter.java
@@ -42,7 +42,7 @@ public class RestSecurityFilter extends RestFilter {
         super();
         logger = Loggers.getLogger("duniter.security", pluginSettings.getSettings(), new String[0]);
         if (pluginSettings.enableSecurity()) {
-            logger.info("Enable security on all duniter4j indices");
+            logger.info("Enable security on all indices");
             controller.registerFilter(this);
         }
         this.securityController = securityController;
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/wot/RestWotLookupGetAction.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/wot/RestWotLookupGetAction.java
new file mode 100644
index 00000000..7d2c0863
--- /dev/null
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/wot/RestWotLookupGetAction.java
@@ -0,0 +1,87 @@
+package org.duniter.elasticsearch.rest.wot;
+
+/*
+ * #%L
+ * duniter4j-elasticsearch-plugin
+ * %%
+ * Copyright (C) 2014 - 2016 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.duniter.core.client.model.bma.jackson.JacksonUtils;
+import org.duniter.core.exception.TechnicalException;
+import org.duniter.core.util.StringUtils;
+import org.duniter.elasticsearch.dao.BlockDao;
+import org.duniter.elasticsearch.rest.RestXContentBuilder;
+import org.duniter.elasticsearch.rest.XContentRestResponse;
+import org.duniter.elasticsearch.rest.security.RestSecurityController;
+import org.duniter.elasticsearch.service.BlockchainService;
+import org.elasticsearch.action.get.GetResponse;
+import org.elasticsearch.client.Client;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.rest.*;
+
+import java.io.IOException;
+
+/**
+ * A rest to post a request to process a new currency/peer.
+ *
+ */
+public class RestWotLookupGetAction extends BaseRestHandler {
+
+    private Client client;
+    private BlockchainService blockchainService;
+
+    @Inject
+    public RestWotLookupGetAction(Settings settings, RestController controller, Client client, RestSecurityController securityController,
+                                  BlockchainService blockchainService) {
+        super(settings, controller, client);
+
+        securityController.allow(RestRequest.Method.GET, "(/[^/]+)?/wot/lookup/[^/]+");
+
+        controller.registerHandler(RestRequest.Method.GET, "/wot/lookup/{uid}", this);
+        controller.registerHandler(RestRequest.Method.GET, "/{index}/wot/lookup/{uid}", this);
+
+        this.client = client;
+        this.blockchainService = blockchainService;
+    }
+
+    @Override
+    protected void handleRequest(RestRequest request, RestChannel channel, Client client) throws Exception {
+        String currency = request.param("index");
+        String uid = request.param("uid");
+
+        try {
+            // TODO: implement
+            XContentBuilder builder = RestXContentBuilder.restContentBuilder(request).startObject()
+                    .field("partials", false)
+                    .field("results", new Object[0])
+                    .endObject();
+            channel.sendResponse(new XContentRestResponse(request, RestStatus.OK, builder));
+        }
+        catch(IOException ioe) {
+            throw new TechnicalException(String.format("Error while generating JSON for [/wot/lookup]: %s", ioe.getMessage()), ioe);
+        }
+    }
+
+    protected ObjectMapper getObjectMapper() {
+        return JacksonUtils.getThreadObjectMapper();
+    }
+}
\ No newline at end of file
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/wot/RestWotMembersGetAction.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/wot/RestWotMembersGetAction.java
new file mode 100644
index 00000000..0a120b4c
--- /dev/null
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/rest/wot/RestWotMembersGetAction.java
@@ -0,0 +1,92 @@
+package org.duniter.elasticsearch.rest.wot;
+
+/*
+ * #%L
+ * duniter4j-elasticsearch-plugin
+ * %%
+ * Copyright (C) 2014 - 2016 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.duniter.core.client.model.bma.jackson.JacksonUtils;
+import org.duniter.core.exception.TechnicalException;
+import org.duniter.elasticsearch.dao.BlockDao;
+import org.duniter.elasticsearch.rest.RestXContentBuilder;
+import org.duniter.elasticsearch.rest.XContentRestResponse;
+import org.duniter.elasticsearch.rest.security.RestSecurityController;
+import org.duniter.elasticsearch.service.BlockchainService;
+import org.duniter.elasticsearch.service.WotService;
+import org.elasticsearch.client.Client;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.settings.Settings;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.rest.*;
+
+import java.io.IOException;
+import java.util.Map;
+
+/**
+ * A GET request similar as /wot/members in Duniter BMA API
+ *
+ */
+public class RestWotMembersGetAction extends BaseRestHandler {
+
+    private Client client;
+    private WotService wotService;
+
+    @Inject
+    public RestWotMembersGetAction(Settings settings, RestController controller, Client client, RestSecurityController securityController,
+                                   WotService wotService) {
+        super(settings, controller, client);
+
+        securityController.allow(RestRequest.Method.GET, "(/[^/]+)?/wot/members");
+
+        controller.registerHandler(RestRequest.Method.GET, "/wot/members", this);
+        controller.registerHandler(RestRequest.Method.GET, "/{index}/members", this);
+
+        this.client = client;
+        this.wotService = wotService;
+    }
+
+    @Override
+    protected void handleRequest(RestRequest request, RestChannel channel, Client client) throws Exception {
+        String currency = request.param("index");
+
+
+        try {
+            Map<String, String> members = wotService.getMembers(currency);
+            XContentBuilder builder = RestXContentBuilder.restContentBuilder(request).startObject()
+                    .startArray("results");
+            for (Map.Entry<String, String> entry: members.entrySet()) {
+                builder.startObject()
+                        .field("pubkey", entry.getKey())
+                        .field("uid", entry.getValue())
+                        .endObject();
+            }
+            builder.endArray().endObject();
+            channel.sendResponse(new XContentRestResponse(request, RestStatus.OK, builder));
+        }
+        catch(IOException ioe) {
+            throw new TechnicalException(String.format("Error while generating JSON for [/wot/lookup]: %s", ioe.getMessage()), ioe);
+        }
+    }
+
+    protected ObjectMapper getObjectMapper() {
+        return JacksonUtils.getThreadObjectMapper();
+    }
+}
\ No newline at end of file
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/service/BlockchainService.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/service/BlockchainService.java
index ec22a811..94f80500 100644
--- a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/service/BlockchainService.java
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/service/BlockchainService.java
@@ -44,7 +44,6 @@ import org.duniter.core.util.CollectionUtils;
 import org.duniter.core.util.ObjectUtils;
 import org.duniter.core.util.Preconditions;
 import org.duniter.core.util.StringUtils;
-import org.duniter.core.util.cache.Cache;
 import org.duniter.core.util.cache.SimpleCache;
 import org.duniter.core.util.json.JsonAttributeParser;
 import org.duniter.core.util.websocket.WebsocketClientEndpoint;
@@ -57,6 +56,7 @@ import org.duniter.elasticsearch.threadpool.ThreadPool;
 import org.elasticsearch.action.bulk.BulkItemResponse;
 import org.elasticsearch.action.bulk.BulkRequestBuilder;
 import org.elasticsearch.action.bulk.BulkResponse;
+import org.elasticsearch.common.bytes.BytesReference;
 import org.elasticsearch.common.inject.Inject;
 import org.nuiton.i18n.I18n;
 
@@ -479,37 +479,36 @@ public class BlockchainService extends AbstractService {
         }
     }
 
-    public BlockchainBlock getBlockById(String currency, final int number) {
 
-        // Retrieve the currency to use
-        boolean enableBlockchainIndexation = pluginSettings.enableBlockchainIndexation() && currencyDao.existsIndex();
-        if (StringUtils.isBlank(currency)) {
-            List<String> currencyIds = enableBlockchainIndexation ? currencyDao.getCurrencyIds() : null;
-            if (CollectionUtils.isNotEmpty(currencyIds)) {
-                currency = currencyIds.get(0);
-            } else {
-                currency = DEFAULT_BLOCK.getCurrency();
-            }
-        }
+    public long[] getBlockNumberWithUd(String currency) {
+        currency = safeGetCurrency(currency);
+        return blockDao.getBlockNumberWithUd(currency);
+    }
+
+    public long[] getBlockNumberWithNewcomers(String currency) {
+        currency = safeGetCurrency(currency);
+        return blockDao.getBlockNumberWithNewcomers(currency);
+    }
 
+    public BlockchainBlock getBlockById(String currency, final int number) {
+
+        currency = safeGetCurrency(currency);
         return blockDao.getBlockById(currency, String.valueOf(number));
     }
 
     public BlockchainBlock getCurrentBlock(String currency) {
-        // Retrieve the currency to use
-        boolean enableBlockchainIndexation = pluginSettings.enableBlockchainIndexation() && currencyDao.existsIndex();
-        if (StringUtils.isBlank(currency)) {
-            List<String> currencyIds = enableBlockchainIndexation ? currencyDao.getCurrencyIds() : null;
-            if (CollectionUtils.isNotEmpty(currencyIds)) {
-                currency = currencyIds.get(0);
-            } else {
-                currency = DEFAULT_BLOCK.getCurrency();
-            }
-        }
-
+        currency = safeGetCurrency(currency);
         return blockDao.getBlockById(currency, CURRENT_BLOCK_ID);
     }
 
+    public BytesReference getBlockByIdAsBytes(String currency, final int number) {
+        return blockDao.getBlockByIdAsBytes(safeGetCurrency(currency), String.valueOf(number));
+    }
+
+    public BytesReference getCurrentBlockAsBytes(String currency) {
+        return blockDao.getBlockByIdAsBytes(safeGetCurrency(currency), CURRENT_BLOCK_ID);
+    }
+
     public void deleteFrom(final String currencyName, final int fromBlock) {
         int maxBlock = blockDao.getMaxBlockNumber(currencyName);
 
@@ -624,7 +623,7 @@ public class BlockchainService extends AbstractService {
                 }
             }
 
-            // Peer send no blocks
+            // Peer sendBlock no blocks
             if (CollectionUtils.isEmpty(blocksAsJson)) {
 
                 // Add range to missing blocks
@@ -895,4 +894,17 @@ public class BlockchainService extends AbstractService {
     private String getBlockId(int number) {
         return number == -1 ? CURRENT_BLOCK_ID : String.valueOf(number);
     }
+
+
+    /**
+     * Return the given currency, or the default currency
+     * @param currency
+     * @return
+     */
+    protected String safeGetCurrency(String currency) {
+
+        if (StringUtils.isNotBlank(currency)) return currency;
+
+        return currencyDao.getDefaultCurrencyName();
+    }
 }
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/service/CurrencyService.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/service/CurrencyService.java
index a7c3b750..11df7407 100644
--- a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/service/CurrencyService.java
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/service/CurrencyService.java
@@ -35,6 +35,7 @@ import org.duniter.core.client.service.exception.HttpConnectException;
 import org.duniter.core.exception.TechnicalException;
 import org.duniter.core.service.CryptoService;
 import org.duniter.core.util.Preconditions;
+import org.duniter.core.util.StringUtils;
 import org.duniter.elasticsearch.PluginSettings;
 import org.duniter.elasticsearch.client.Duniter4jClient;
 import org.duniter.elasticsearch.dao.*;
@@ -93,6 +94,17 @@ public class CurrencyService extends AbstractService {
         return currencyDao.isExists(currencyName);
     }
 
+    /**
+     * Return the given currency, or the default currency
+     * @param currency
+     * @return
+     */
+    public String safeGetCurrency(String currency) {
+
+        if (StringUtils.isNotBlank(currency)) return currency;
+        return currencyDao.getDefaultCurrencyName();
+    }
+
     /**
      * Retrieve the blockchain data, from peer
      *
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/service/NetworkService.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/service/NetworkService.java
index 45c444a5..b0a17f5d 100644
--- a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/service/NetworkService.java
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/service/NetworkService.java
@@ -64,7 +64,7 @@ public class NetworkService extends AbstractService {
     private BlockchainService blockchainService;
     private Map<String, NetworkPeering> peeringByCurrencyCache = Maps.newHashMap();
 
-    // API where to send the peer document
+    // API where to sendBlock the peer document
     private final static Set<EndpointApi> targetPeersEndpointApis = Sets.newHashSet();
     // API to include inside the peer document
     private final static Set<EndpointApi> publishedEndpointApis = Sets.newHashSet();
@@ -292,15 +292,7 @@ public class NetworkService extends AbstractService {
         waitReady();
 
         // Retrieve the currency to use
-        boolean enableBlockchainIndexation = pluginSettings.enableBlockchainIndexation() && currencyDao.existsIndex();
-        if (StringUtils.isBlank(currency)) {
-            List<String> currencyIds = enableBlockchainIndexation ? currencyDao.getCurrencyIds() : null;
-            if (CollectionUtils.isNotEmpty(currencyIds)) {
-                currency = currencyIds.get(0);
-            } else {
-                currency = DEFAULT_BLOCK.getCurrency();
-            }
-        }
+        currency = blockchainService.safeGetCurrency(currency);
 
         // Get result from cache, is allow
         if (useCache) {
@@ -312,7 +304,7 @@ public class NetworkService extends AbstractService {
         NetworkPeering result = new NetworkPeering();
 
         // Get current block
-        BlockchainBlock currentBlock = enableBlockchainIndexation ? blockchainService.getCurrentBlock(currency) : null;
+        BlockchainBlock currentBlock = pluginSettings.enableBlockchainIndexation() ? blockchainService.getCurrentBlock(currency) : null;
         if (currentBlock == null) {
             currentBlock = DEFAULT_BLOCK;
             currency = currentBlock.getCurrency();
@@ -485,7 +477,7 @@ public class NetworkService extends AbstractService {
             currencyIds = null;
         }
         if (CollectionUtils.isEmpty(currencyIds)) {
-            logger.warn("Skipping the publication of peer document (no indexed currency)");
+            logger.warn("Skipping publication of peer document (no indexed currency)");
             return;
         }
 
@@ -525,6 +517,8 @@ public class NetworkService extends AbstractService {
         Preconditions.checkNotNull(peerDocument);
 
         try {
+            if (logger.isDebugEnabled()) logger.debug(String.format("[%s] [%s] Sending peer document", currencyId, peer));
+
             networkRemoteService.postPeering(peer, peerDocument);
             return true;
         }
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java
index 744cd2b5..54c7cfe0 100644
--- a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/service/ServiceModule.java
@@ -58,6 +58,9 @@ public class ServiceModule extends AbstractModule implements Module {
         bind(BlockchainListenerService.class).asEagerSingleton();
         bind(PeerService.class).asEagerSingleton();
 
+        // Wot service
+        bind(WotService.class).asEagerSingleton();
+
         // Duniter Client API beans
         bindWithLocator(BlockchainRemoteService.class);
         bindWithLocator(NetworkRemoteService.class);
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/service/WotService.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/service/WotService.java
new file mode 100644
index 00000000..5f2babb3
--- /dev/null
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/service/WotService.java
@@ -0,0 +1,120 @@
+package org.duniter.elasticsearch.service;
+
+/*
+ * #%L
+ * Duniter4j :: Core API
+ * %%
+ * Copyright (C) 2014 - 2015 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import org.duniter.core.client.dao.CurrencyDao;
+import org.duniter.core.client.model.bma.BlockchainBlock;
+import org.duniter.core.client.model.bma.BlockchainParameters;
+import org.duniter.core.client.model.bma.EndpointApi;
+import org.duniter.core.client.model.local.Peer;
+import org.duniter.core.client.service.bma.BlockchainRemoteService;
+import org.duniter.core.client.service.bma.NetworkRemoteService;
+import org.duniter.core.client.service.bma.WotRemoteService;
+import org.duniter.core.client.service.exception.BlockNotFoundException;
+import org.duniter.core.client.util.KnownBlocks;
+import org.duniter.core.client.util.KnownCurrencies;
+import org.duniter.core.exception.TechnicalException;
+import org.duniter.core.model.NullProgressionModel;
+import org.duniter.core.model.ProgressionModel;
+import org.duniter.core.model.ProgressionModelImpl;
+import org.duniter.core.util.CollectionUtils;
+import org.duniter.core.util.ObjectUtils;
+import org.duniter.core.util.Preconditions;
+import org.duniter.core.util.StringUtils;
+import org.duniter.core.util.cache.SimpleCache;
+import org.duniter.core.util.json.JsonAttributeParser;
+import org.duniter.core.util.websocket.WebsocketClientEndpoint;
+import org.duniter.elasticsearch.PluginSettings;
+import org.duniter.elasticsearch.client.Duniter4jClient;
+import org.duniter.elasticsearch.dao.BlockDao;
+import org.duniter.elasticsearch.dao.CurrencyExtendDao;
+import org.duniter.elasticsearch.exception.DuplicateIndexIdException;
+import org.duniter.elasticsearch.threadpool.ThreadPool;
+import org.elasticsearch.action.bulk.BulkItemResponse;
+import org.elasticsearch.action.bulk.BulkRequestBuilder;
+import org.elasticsearch.action.bulk.BulkResponse;
+import org.elasticsearch.action.get.GetResponse;
+import org.elasticsearch.common.inject.Inject;
+import org.nuiton.i18n.I18n;
+
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * Created by Benoit on 30/03/2015.
+ */
+public class WotService extends AbstractService {
+
+    private BlockDao blockDao;
+    private CurrencyExtendDao currencyDao;
+    private WotRemoteService wotRemoteService;
+    private BlockchainService blockchainService;
+
+    @Inject
+    public WotService(Duniter4jClient client,
+                      PluginSettings settings,
+                      ThreadPool threadPool,
+                      BlockDao blockDao,
+                      CurrencyDao currencyDao,
+                      BlockchainService blockchainService,
+                      final ServiceLocator serviceLocator){
+        super("duniter.wot", client, settings);
+        this.client = client;
+        this.blockDao = blockDao;
+        this.currencyDao = (CurrencyExtendDao) currencyDao;
+        this.blockchainService = blockchainService;
+        threadPool.scheduleOnStarted(() -> {
+            wotRemoteService = serviceLocator.getWotRemoteService();
+            setIsReady(true);
+        });
+    }
+
+    public Map<String, String> getMembers(String currency) {
+
+        currency = safeGetCurrency(currency);
+
+        if (pluginSettings.enableBlockchainIndexation()) {
+            BlockchainParameters p = blockchainService.getParameters(currency);
+            return blockDao.getMembers(p);
+        }
+        else {
+            // TODO: check if it works !
+            return wotRemoteService.getMembersUids(currency);
+        }
+
+    }
+
+    /**
+     * Return the given currency, or the default currency
+     * @param currency
+     * @return
+     */
+    protected String safeGetCurrency(String currency) {
+        if (StringUtils.isNotBlank(currency)) return currency;
+        return currencyDao.getDefaultCurrencyName();
+    }
+}
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeEvent.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeEvent.java
index b01a30f1..70ae0b7f 100644
--- a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeEvent.java
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeEvent.java
@@ -39,9 +39,15 @@ package org.duniter.elasticsearch.service.changes;
 */
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
+import org.duniter.core.exception.TechnicalException;
 import org.elasticsearch.common.bytes.BytesReference;
+import org.elasticsearch.common.io.stream.BytesStreamOutput;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.json.JsonXContent;
 import org.joda.time.DateTime;
 
+import java.io.IOException;
+
 public class ChangeEvent {
     private final String id;
     private final String index;
@@ -50,6 +56,7 @@ public class ChangeEvent {
     private final Operation operation;
     private final long version;
     private final BytesReference source;
+    private String sourceText; // cache
 
     public enum Operation {
         INDEX,CREATE,DELETE
@@ -108,4 +115,18 @@ public class ChangeEvent {
         return source != null;
     }
 
+    @JsonIgnore
+    public String getSourceText(){
+        if (sourceText != null) return sourceText;
+        if (source == null) return null;
+        try {
+            XContentBuilder builder = new XContentBuilder(JsonXContent.jsonXContent, new BytesStreamOutput());
+            builder.rawValue(source);
+            sourceText = builder.string();
+            return sourceText;
+        } catch (IOException e) {
+            throw new TechnicalException("Error while generating JSON from source", e);
+        }
+    }
+
 }
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeService.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeService.java
index d969c5bb..14e201c7 100644
--- a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeService.java
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/service/changes/ChangeService.java
@@ -177,7 +177,7 @@ public class ChangeService {
                                     try {
                                         listener.onChange(change);
                                     } catch (Exception e) {
-                                        log.error("Failed to send message", e);
+                                        log.error("Failed to sendBlock message", e);
                                     }
                                 }
                             }
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/threadpool/ThreadPool.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/threadpool/ThreadPool.java
index f83a140f..51ec6cd4 100644
--- a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/threadpool/ThreadPool.java
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/threadpool/ThreadPool.java
@@ -92,7 +92,9 @@ public class ThreadPool extends AbstractLifecycleComponent<ThreadPool> {
     }
 
     public void doStop(){
-        scheduler.shutdown();
+        if (!scheduler.isShutdown()) {
+            scheduler.shutdown();
+        }
     }
 
     public void doClose() {}
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/WebSocketModule.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/WebSocketModule.java
index 7152c435..acacbd53 100644
--- a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/WebSocketModule.java
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/WebSocketModule.java
@@ -22,28 +22,28 @@ package org.duniter.elasticsearch.websocket;
  * #L%
  */
 
-/*
-    Copyright 2015 ForgeRock AS
-
-    Licensed under the Apache License, Version 2.0 (the "License");
-    you may not use this file except in compliance with the License.
-    You may obtain a copy of the License at
-
-        http://www.apache.org/licenses/LICENSE-2.0
-
-    Unless required by applicable law or agreed to in writing, software
-    distributed under the License is distributed on an "AS IS" BASIS,
-    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-    See the License for the specific language governing permissions and
-    limitations under the License.
-*/
 
+import org.duniter.elasticsearch.websocket.netty.NettyWebSocketBlockHandler;
+import org.duniter.elasticsearch.websocket.netty.NettyWebSocketChangesHandler;
+import org.duniter.elasticsearch.websocket.netty.NettyWebSocketPeerHandler;
+import org.duniter.elasticsearch.websocket.tyrus.WebSocketBlockEndPoint;
+import org.duniter.elasticsearch.websocket.tyrus.WebSocketChangesEndPoint;
 import org.elasticsearch.common.inject.AbstractModule;
 
 public class WebSocketModule extends AbstractModule {
     @Override
     protected void configure() {
-        bind(WebSocketServer.class).asEagerSingleton();
+
+        // Netty handler
+        bind(NettyWebSocketBlockHandler.Init.class).asEagerSingleton();
+        bind(NettyWebSocketChangesHandler.Init.class).asEagerSingleton();
+        bind(NettyWebSocketPeerHandler.Init.class).asEagerSingleton();
+
+
+        // Tyrus Web socket Server
         bind(WebSocketChangesEndPoint.Init.class).asEagerSingleton();
+        bind(WebSocketBlockEndPoint.Init.class).asEagerSingleton();
+
     }
+
 }
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/netty/NettyWebSocketBlockHandler.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/netty/NettyWebSocketBlockHandler.java
new file mode 100644
index 00000000..259f919f
--- /dev/null
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/netty/NettyWebSocketBlockHandler.java
@@ -0,0 +1,200 @@
+package org.duniter.elasticsearch.websocket.netty;
+
+/*
+ * #%L
+ * Duniter4j :: ElasticSearch Plugin
+ * %%
+ * Copyright (C) 2014 - 2016 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+
+import com.google.common.collect.ImmutableList;
+import org.duniter.core.client.model.bma.BlockchainBlock;
+import org.duniter.core.exception.TechnicalException;
+import org.duniter.core.util.StringUtils;
+import org.duniter.core.util.json.JsonAttributeParser;
+import org.duniter.elasticsearch.http.netty.NettyWebSocketServer;
+import org.duniter.elasticsearch.http.netty.websocket.NettyBaseWebSocketEndpoint;
+import org.duniter.elasticsearch.http.netty.websocket.NettyWebSocketSession;
+import org.duniter.elasticsearch.service.BlockchainService;
+import org.duniter.elasticsearch.service.CurrencyService;
+import org.duniter.elasticsearch.service.changes.ChangeEvent;
+import org.duniter.elasticsearch.service.changes.ChangeService;
+import org.duniter.elasticsearch.service.changes.ChangeSource;
+import org.duniter.elasticsearch.threadpool.ThreadPool;
+import org.elasticsearch.common.bytes.BytesReference;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.io.stream.BytesStreamOutput;
+import org.elasticsearch.common.logging.ESLogger;
+import org.elasticsearch.common.logging.Loggers;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.json.JsonXContent;
+
+import javax.websocket.CloseReason;
+import javax.websocket.OnClose;
+import javax.websocket.OnError;
+import javax.websocket.OnMessage;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+
+public class NettyWebSocketBlockHandler extends NettyBaseWebSocketEndpoint implements ChangeService.ChangeListener {
+
+    private final static String PATH = WEBSOCKET_PATH + "/block";
+
+    private final static JsonAttributeParser<Long> numberAttributeParser = new JsonAttributeParser<>(BlockchainBlock.PROPERTY_NUMBER, Long.class);
+    private final static JsonAttributeParser<String> hashAttributeParser = new JsonAttributeParser<>(BlockchainBlock.PROPERTY_HASH, String.class);
+
+    private static ESLogger logger = null;
+    private static BlockchainService blockchainService;
+    private static CurrencyService currencyService;
+    private static boolean isReady = false;
+
+
+    public static class Init {
+        @Inject
+        public Init( NettyWebSocketServer webSocketServer,
+                     CurrencyService currencyService,
+                     BlockchainService blockchainService,
+                     ThreadPool threadPool) {
+            logger = Loggers.getLogger("duniter.ws.block");
+
+            NettyWebSocketBlockHandler.currencyService = currencyService;
+            NettyWebSocketBlockHandler.blockchainService = blockchainService;
+
+            webSocketServer.addEndpoint(PATH, NettyWebSocketBlockHandler.class);
+
+            threadPool.scheduleOnClusterReady(() -> isReady = true);
+        }
+    }
+
+    private NettyWebSocketSession session;
+    private String currency;
+    private List<ChangeSource> sources;
+    private String lastBlockstampSent;
+
+    @Override
+    public void onOpen(NettyWebSocketSession session){
+
+        this.session = session;
+        this.lastBlockstampSent = null;
+
+        if (!isReady) {
+            session.close(new CloseReason(CloseReason.CloseCodes.SERVICE_RESTART, "Pod is not ready"));
+            return;
+        }
+
+        // Use given currency or default currency
+        try {
+            currency = currencyService.safeGetCurrency(session.getPathParameters().get("currency"));
+        } catch (Exception e) {
+            logger.debug(String.format("Cannot open websocket session on {/ws/block}: %s", e.getMessage()), e);
+        }
+
+        // Failed if no currency on this pod, or if pod is not ready yet
+        if (StringUtils.isBlank(currency)) {
+            session.close(new CloseReason(CloseReason.CloseCodes.CANNOT_ACCEPT, "Missing currency to listen"));
+            return;
+        }
+
+        logger.debug(String.format("[%s] Opening websocket session on {/ws/block}, id {%s}", currency, session.getId()));
+
+        // After opening, sent the current block
+        BytesReference currentBlock = blockchainService.getCurrentBlockAsBytes(currency);
+        if (currentBlock != null) sendJson(currentBlock);
+
+        // Listening changes
+        this.sources = ImmutableList.of(new ChangeSource(currency, BlockchainService.BLOCK_TYPE));
+        ChangeService.registerListener(this);
+    }
+
+    @Override
+    public void onChange(ChangeEvent event) {
+        switch (event.getOperation()) {
+            case CREATE:
+            //case INDEX:
+                sendSourceIfNotNull(event);
+                break;
+            default:
+                // Ignoring (if delete)
+        }
+    }
+
+    @Override
+    public String getId() {
+        return session == null ? null : session.getId();
+    }
+
+    @Override
+    public Collection<ChangeSource> getChangeSources() {
+        return sources;
+    }
+
+    @Override
+    public void onMessage(String message) {
+        // Ignoring
+    }
+
+    @Override
+    public void onClose(CloseReason reason) {
+        logger.debug("Closing websocket: "+reason);
+        ChangeService.unregisterListener(this);
+        this.session = null;
+        this.lastBlockstampSent = null;
+    }
+
+    public void onError(Throwable t) {
+        logger.error(String.format("[%s] Error on websocket endpoint {%s} session {%s}", currency, PATH, (session == null ? null : session.getId())), t);
+    }
+
+    /* -- internal methods -- */
+
+    protected void sendSourceIfNotNull(ChangeEvent event) {
+
+        if (!event.hasSource()) return; // Skip
+
+        try {
+            String sourceText = event.getSourceText();
+
+            Long number = numberAttributeParser.getValue(sourceText);
+            String hash = hashAttributeParser.getValue(sourceText);
+
+            // Check if not already sent
+            String blocktamp = String.format("%s-%s", number, hash);
+            if (!blocktamp.equals(this.lastBlockstampSent)) {
+                this.lastBlockstampSent = blocktamp;
+                session.sendText(sourceText);
+            }
+
+        } catch(Exception e) {
+            logger.error(String.format("[%s] Cannot sent websocket response {%s} to session {%s}: %s", currency, PATH, session.getId(), e.getMessage()), e);
+        }
+
+    }
+
+    protected void sendJson(BytesReference bytes) {
+        try {
+            XContentBuilder builder = new XContentBuilder(JsonXContent.jsonXContent, new BytesStreamOutput());
+            builder.rawValue(bytes);
+            session.sendText(builder.string());
+        } catch (IOException e) {
+            throw new TechnicalException("Error while generating JSON from source", e);
+        }
+    }
+
+}
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/netty/NettyWebSocketChangesHandler.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/netty/NettyWebSocketChangesHandler.java
new file mode 100644
index 00000000..e28c0628
--- /dev/null
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/netty/NettyWebSocketChangesHandler.java
@@ -0,0 +1,154 @@
+package org.duniter.elasticsearch.websocket.netty;
+
+/*
+ * #%L
+ * Duniter4j :: ElasticSearch Plugin
+ * %%
+ * Copyright (C) 2014 - 2016 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+/*
+    Copyright 2015 ForgeRock AS
+
+    Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+        http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+*/
+
+import com.google.common.collect.Maps;
+import org.apache.commons.collections4.MapUtils;
+import org.duniter.elasticsearch.PluginSettings;
+import org.duniter.elasticsearch.http.netty.NettyWebSocketServer;
+import org.duniter.elasticsearch.http.netty.websocket.NettyBaseWebSocketEndpoint;
+import org.duniter.elasticsearch.http.netty.websocket.NettyWebSocketSession;
+import org.duniter.elasticsearch.service.changes.ChangeEvent;
+import org.duniter.elasticsearch.service.changes.ChangeEvents;
+import org.duniter.elasticsearch.service.changes.ChangeService;
+import org.duniter.elasticsearch.service.changes.ChangeSource;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.logging.ESLogger;
+import org.elasticsearch.common.logging.Loggers;
+
+import javax.websocket.CloseReason;
+import javax.websocket.OnError;
+import javax.websocket.OnOpen;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+public class NettyWebSocketChangesHandler extends NettyBaseWebSocketEndpoint implements ChangeService.ChangeListener{
+
+    private final static String PATH = WEBSOCKET_PATH + "/_changes";
+    public static Collection<ChangeSource> DEFAULT_SOURCES = null;
+
+    private static ESLogger logger;
+    private NettyWebSocketSession session;
+    private Map<String, ChangeSource> sources;
+
+    public static class Init {
+
+        @Inject
+        public Init(NettyWebSocketServer webSocketServer, PluginSettings pluginSettings) {
+            logger = Loggers.getLogger("duniter.ws.changes");
+
+            // Init default sources
+            final String[] sourcesStr = pluginSettings.getWebSocketChangesListenSource();
+            List<ChangeSource> sources = new ArrayList<>();
+            for(String sourceStr : sourcesStr) {
+                sources.add(new ChangeSource(sourceStr));
+            }
+            DEFAULT_SOURCES = sources;
+
+            // Register endpoint
+            webSocketServer.addEndpoint(PATH, NettyWebSocketChangesHandler.class);
+        }
+    }
+
+
+    @OnOpen
+    public void onOpen(NettyWebSocketSession session){
+        logger.debug("Connected ... " + session.getId());
+        this.session = session;
+        this.sources = null;
+        ChangeService.registerListener(this);
+    }
+
+    @Override
+    public void onChange(ChangeEvent changeEvent) {
+        session.sendText(ChangeEvents.toJson(changeEvent));
+    }
+
+    @Override
+    public String getId() {
+        return session == null ? null : session.getId();
+    }
+
+    @Override
+    public Collection<ChangeSource> getChangeSources() {
+        if (MapUtils.isEmpty(sources)) return DEFAULT_SOURCES;
+        return sources.values();
+    }
+
+    @Override
+    public void onMessage(String message) {
+        addSourceFilter(message);
+    }
+
+    @Override
+    public void onClose(CloseReason reason) {
+        logger.debug("Closing websocket: "+reason);
+        ChangeService.unregisterListener(this);
+        this.session = null;
+    }
+
+    @OnError
+    public void onError(Throwable t) {
+        logger.error("Error on websocket "+(session == null ? null : session.getId()), t);
+    }
+
+
+    /* -- internal methods -- */
+
+    private void addSourceFilter(String filter) {
+
+        ChangeSource source = new ChangeSource(filter);
+        if (source.isEmpty()) {
+            logger.debug("Rejecting changes filter (seems to be empty): " + filter);
+            return;
+        }
+
+        String sourceKey = source.toString();
+        if (sources == null || !sources.containsKey(sourceKey)) {
+            logger.debug("Adding changes filter: " + filter);
+            if (sources == null) {
+                sources = Maps.newHashMap();
+            }
+            sources.put(sourceKey, source);
+            ChangeService.refreshListener(this);
+        }
+    }
+}
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/netty/NettyWebSocketPeerHandler.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/netty/NettyWebSocketPeerHandler.java
new file mode 100644
index 00000000..4e6c3065
--- /dev/null
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/netty/NettyWebSocketPeerHandler.java
@@ -0,0 +1,165 @@
+package org.duniter.elasticsearch.websocket.netty;
+
+/*
+ * #%L
+ * Duniter4j :: ElasticSearch Plugin
+ * %%
+ * Copyright (C) 2014 - 2016 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+
+import com.google.common.collect.ImmutableList;
+import org.duniter.core.client.model.bma.BlockchainBlock;
+import org.duniter.core.exception.TechnicalException;
+import org.duniter.core.util.StringUtils;
+import org.duniter.core.util.json.JsonAttributeParser;
+import org.duniter.elasticsearch.dao.PeerDao;
+import org.duniter.elasticsearch.http.netty.NettyWebSocketServer;
+import org.duniter.elasticsearch.http.netty.websocket.NettyBaseWebSocketEndpoint;
+import org.duniter.elasticsearch.http.netty.websocket.NettyWebSocketSession;
+import org.duniter.elasticsearch.service.BlockchainService;
+import org.duniter.elasticsearch.service.CurrencyService;
+import org.duniter.elasticsearch.service.PeerService;
+import org.duniter.elasticsearch.service.changes.ChangeEvent;
+import org.duniter.elasticsearch.service.changes.ChangeService;
+import org.duniter.elasticsearch.service.changes.ChangeSource;
+import org.duniter.elasticsearch.threadpool.ThreadPool;
+import org.elasticsearch.common.bytes.BytesReference;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.io.stream.BytesStreamOutput;
+import org.elasticsearch.common.logging.ESLogger;
+import org.elasticsearch.common.logging.Loggers;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.json.JsonXContent;
+
+import javax.websocket.CloseReason;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+
+public class NettyWebSocketPeerHandler extends NettyBaseWebSocketEndpoint implements ChangeService.ChangeListener {
+
+    private final static String PATH = WEBSOCKET_PATH + "/peer";
+
+    private static ESLogger logger = null;
+    private static CurrencyService currencyService;
+    private static boolean isReady = false;
+
+    public static class Init {
+        @Inject
+        public Init( NettyWebSocketServer webSocketServer,
+                     CurrencyService currencyService,
+                     ThreadPool threadPool) {
+            logger = Loggers.getLogger("duniter.ws.peer");
+
+            NettyWebSocketPeerHandler.currencyService = currencyService;
+
+            webSocketServer.addEndpoint(PATH, NettyWebSocketPeerHandler.class);
+
+            threadPool.scheduleOnClusterReady(() -> isReady = true);
+        }
+    }
+
+    private NettyWebSocketSession session;
+    private String currency;
+    private List<ChangeSource> sources;
+
+    @Override
+    public void onOpen(NettyWebSocketSession session){
+
+        this.session = session;
+
+        if (!isReady) {
+            session.close(new CloseReason(CloseReason.CloseCodes.SERVICE_RESTART, "Pod is not ready"));
+            return;
+        }
+
+        // Use given currency or default currency
+        try {
+            currency = currencyService.safeGetCurrency(session.getPathParameters().get("currency"));
+        } catch (Exception e) {
+            logger.debug(String.format("Cannot open websocket session on {%s}: %s", PATH, e.getMessage()), e);
+        }
+
+        // Failed if no currency on this pod, or if pod is not ready yet
+        if (StringUtils.isBlank(currency)) {
+            session.close(new CloseReason(CloseReason.CloseCodes.CANNOT_ACCEPT, "Missing currency to listen"));
+            return;
+        }
+
+        logger.debug(String.format("[%s] Opening websocket session on {%s}, id {%s}", currency, PATH, session.getId()));
+
+        // Listening changes
+        this.sources = ImmutableList.of(new ChangeSource(currency, PeerDao.TYPE));
+        ChangeService.registerListener(this);
+    }
+
+    @Override
+    public void onChange(ChangeEvent event) {
+        switch (event.getOperation()) {
+            case CREATE:
+            //case INDEX:
+                sendSourceIfNotNull(event);
+                break;
+            default:
+                // Ignoring (if delete)
+        }
+    }
+
+    @Override
+    public String getId() {
+        return session == null ? null : session.getId();
+    }
+
+    @Override
+    public Collection<ChangeSource> getChangeSources() {
+        return sources;
+    }
+
+    @Override
+    public void onMessage(String message) {
+        // Ignoring
+    }
+
+    @Override
+    public void onClose(CloseReason reason) {
+        logger.debug("Closing websocket: "+reason);
+        ChangeService.unregisterListener(this);
+        this.session = null;
+    }
+
+    public void onError(Throwable t) {
+        logger.error(String.format("[%s] Error on websocket endpoint {%s} session {%s}", currency, PATH, (session == null ? null : session.getId())), t);
+    }
+
+    /* -- internal methods -- */
+
+    protected void sendSourceIfNotNull(ChangeEvent event) {
+
+        if (!event.hasSource()) return; // Skip
+
+        try {
+            session.sendText(event.getSourceText());
+
+        } catch(Exception e) {
+            logger.error(String.format("[%s] Cannot sent websocket response {%s} to session {%s}: %s", currency, PATH, session.getId(), e.getMessage()), e);
+        }
+
+    }
+
+}
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/tyrus/WebSocketBlockEndPoint.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/tyrus/WebSocketBlockEndPoint.java
new file mode 100644
index 00000000..2912e398
--- /dev/null
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/tyrus/WebSocketBlockEndPoint.java
@@ -0,0 +1,168 @@
+package org.duniter.elasticsearch.websocket.tyrus;
+
+/*
+ * #%L
+ * Duniter4j :: ElasticSearch Plugin
+ * %%
+ * Copyright (C) 2014 - 2016 EIS
+ * %%
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, either version 3 of the 
+ * License, or (at your option) any later version.
+ * 
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ * 
+ * You should have received a copy of the GNU General Public 
+ * License along with this program.  If not, see
+ * <http://www.gnu.org/licenses/gpl-3.0.html>.
+ * #L%
+ */
+
+
+import com.fasterxml.jackson.databind.util.ByteBufferBackedOutputStream;
+import com.google.common.collect.ImmutableList;
+import org.duniter.core.util.StringUtils;
+import org.duniter.elasticsearch.http.tyrus.TyrusWebSocketServer;
+import org.duniter.elasticsearch.service.BlockchainService;
+import org.duniter.elasticsearch.service.CurrencyService;
+import org.duniter.elasticsearch.service.changes.ChangeEvent;
+import org.duniter.elasticsearch.service.changes.ChangeService;
+import org.duniter.elasticsearch.service.changes.ChangeSource;
+import org.duniter.elasticsearch.threadpool.ThreadPool;
+import org.elasticsearch.common.bytes.BytesReference;
+import org.elasticsearch.common.inject.Inject;
+import org.elasticsearch.common.logging.ESLogger;
+import org.elasticsearch.common.logging.Loggers;
+import org.elasticsearch.common.xcontent.XContentBuilder;
+import org.elasticsearch.common.xcontent.json.JsonXContent;
+
+import javax.websocket.*;
+import javax.websocket.server.ServerEndpoint;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.Collection;
+import java.util.List;
+
+@ServerEndpoint(value = "/block")
+public class WebSocketBlockEndPoint implements ChangeService.ChangeListener{
+
+    private static BlockchainService blockchainService;
+    private static CurrencyService currencyService;
+    private static boolean isReady = false;
+    private static ESLogger logger = null;
+
+    public static class Init {
+
+
+        @Inject
+        public Init(TyrusWebSocketServer webSocketServer,
+                    CurrencyService currencyService,
+                    BlockchainService blockchainService,
+                    ThreadPool threadPool) {
+            webSocketServer.addEndPoint(WebSocketBlockEndPoint.class);
+            WebSocketBlockEndPoint.currencyService = currencyService;
+            WebSocketBlockEndPoint.blockchainService = blockchainService;
+            logger = Loggers.getLogger("duniter.ws.block");
+
+            //server.addLifecycleListener();
+            threadPool.scheduleOnClusterReady(() -> {
+                isReady = true;
+            });
+        }
+    }
+
+    private Session session;
+    private String currency;
+    private List<ChangeSource> sources;
+
+    @OnOpen
+    public void onOpen(Session session) throws IOException {
+        this.session = session;
+
+        if (!isReady) {
+            session.close(new CloseReason(CloseReason.CloseCodes.SERVICE_RESTART, "Pod is not ready"));
+            return;
+        }
+
+        // Use given currency or default currency
+        try {
+            currency = currencyService.safeGetCurrency(session.getPathParameters().get("currency"));
+        } catch (Exception e) {
+            logger.debug(String.format("Cannot open websocket session on {/ws/block}: %s", e.getMessage()), e);
+        }
+
+        // Failed if no currency on this pod, or if pod is not ready yet
+        if (StringUtils.isBlank(currency)) {
+            session.close(new CloseReason(CloseReason.CloseCodes.CANNOT_ACCEPT, "Missing currency to listen"));
+            return;
+        }
+
+        logger.debug(String.format("[%s] Opening websocket session on {/ws/block}, id {%s}", currency, session.getId()));
+
+        // After opening, sent the current block
+        sendBinary(blockchainService.getCurrentBlockAsBytes(currency));
+
+        // Listening changes
+        this.sources = ImmutableList.of(new ChangeSource(currency, BlockchainService.BLOCK_TYPE));
+        ChangeService.registerListener(this);
+    }
+
+    @Override
+    public void onChange(ChangeEvent changeEvent) {
+        switch (changeEvent.getOperation()) {
+            case CREATE:
+            case INDEX:
+                if (changeEvent.hasSource()) {
+                    sendBinary(changeEvent.getSource());
+                }
+                break;
+            default:
+                // Ignoring (if delete)
+        }
+    }
+
+    @Override
+    public String getId() {
+        return session == null ? null : session.getId();
+    }
+
+    @Override
+    public Collection<ChangeSource> getChangeSources() {
+        return sources;
+    }
+
+    @OnMessage
+    public void onMessage(String message) {
+        // Ignoring
+    }
+
+    @OnClose
+    public void onClose(CloseReason reason) {
+        logger.debug("Closing websocket: "+reason);
+        ChangeService.unregisterListener(this);
+        this.session = null;
+    }
+
+    @OnError
+    public void onError(Throwable t) {
+        logger.error("Error on websocket endpoint /ws/block "+(session == null ? null : session.getId()), t);
+    }
+
+    /* -- internal methods -- */
+
+    protected void sendBinary(BytesReference source) {
+        try {
+            ByteBuffer bf = ByteBuffer.allocate(1024*1000);
+            XContentBuilder builder = new XContentBuilder(JsonXContent.jsonXContent, new ByteBufferBackedOutputStream(bf));
+            builder.rawValue(source);
+            session.getAsyncRemote().sendBinary(bf);
+        } catch(IOException e) {
+            logger.error(String.format("[%s] Cannot sent response to session {%s} on {/ws/block}: %s", currency, session.getId(), e.getMessage()), e);
+        }
+
+    }
+}
diff --git a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/WebSocketChangesEndPoint.java b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/tyrus/WebSocketChangesEndPoint.java
similarity index 86%
rename from cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/WebSocketChangesEndPoint.java
rename to cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/tyrus/WebSocketChangesEndPoint.java
index 32b22b11..0f5ff58c 100644
--- a/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/WebSocketChangesEndPoint.java
+++ b/cesium-plus-pod-core/src/main/java/org/duniter/elasticsearch/websocket/tyrus/WebSocketChangesEndPoint.java
@@ -1,4 +1,4 @@
-package org.duniter.elasticsearch.websocket;
+package org.duniter.elasticsearch.websocket.tyrus;
 
 /*
  * #%L
@@ -41,6 +41,7 @@ package org.duniter.elasticsearch.websocket;
 import com.google.common.collect.Maps;
 import org.apache.commons.collections4.MapUtils;
 import org.duniter.elasticsearch.PluginSettings;
+import org.duniter.elasticsearch.http.tyrus.TyrusWebSocketServer;
 import org.duniter.elasticsearch.service.changes.ChangeEvent;
 import org.duniter.elasticsearch.service.changes.ChangeEvents;
 import org.duniter.elasticsearch.service.changes.ChangeService;
@@ -59,15 +60,14 @@ import java.util.Map;
 @ServerEndpoint(value = "/_changes")
 public class WebSocketChangesEndPoint implements ChangeService.ChangeListener{
 
-    public static String PATH_PARAM_INDEX = "index";
-    public static String PATH_PARAM_TYPE = "type";
-
     public static Collection<ChangeSource> DEFAULT_SOURCES = null;
+    private static ESLogger logger;
 
     public static class Init {
 
         @Inject
-        public Init(WebSocketServer webSocketServer, PluginSettings pluginSettings) {
+        public Init(TyrusWebSocketServer webSocketServer, PluginSettings pluginSettings) {
+            logger = Loggers.getLogger("duniter.ws.changes");
             webSocketServer.addEndPoint(WebSocketChangesEndPoint.class);
             final String[] sourcesStr = pluginSettings.getWebSocketChangesListenSource();
             List<ChangeSource> sources = new ArrayList<>();
@@ -78,13 +78,12 @@ public class WebSocketChangesEndPoint implements ChangeService.ChangeListener{
         }
     }
 
-    private final ESLogger log = Loggers.getLogger("duniter.ws.changes");
     private Session session;
     private Map<String, ChangeSource> sources;
 
     @OnOpen
     public void onOpen(Session session) {
-        log.debug("Connected ... " + session.getId());
+        logger.debug("Connected ... " + session.getId());
         this.session = session;
         this.sources = null;
         ChangeService.registerListener(this);
@@ -113,14 +112,14 @@ public class WebSocketChangesEndPoint implements ChangeService.ChangeListener{
 
     @OnClose
     public void onClose(CloseReason reason) {
-        log.debug("Closing websocket: "+reason);
+        logger.debug("Closing websocket: "+reason);
         ChangeService.unregisterListener(this);
         this.session = null;
     }
 
     @OnError
     public void onError(Throwable t) {
-        log.error("Error on websocket "+(session == null ? null : session.getId()), t);
+        logger.error("Error on websocket "+(session == null ? null : session.getId()), t);
     }
 
 
@@ -130,13 +129,13 @@ public class WebSocketChangesEndPoint implements ChangeService.ChangeListener{
 
         ChangeSource source = new ChangeSource(filter);
         if (source.isEmpty()) {
-            log.debug("Rejecting changes filter (seems to be empty): " + filter);
+            logger.debug("Rejecting changes filter (seems to be empty): " + filter);
             return;
         }
 
         String sourceKey = source.toString();
         if (sources == null || !sources.containsKey(sourceKey)) {
-            log.debug("Adding changes filter: " + filter);
+            logger.debug("Adding changes filter: " + filter);
             if (sources == null) {
                 sources = Maps.newHashMap();
             }
diff --git a/cesium-plus-pod-subscription/src/main/java/org/duniter/elasticsearch/subscription/PluginSettings.java b/cesium-plus-pod-subscription/src/main/java/org/duniter/elasticsearch/subscription/PluginSettings.java
index e6ba1ee8..60b293f4 100644
--- a/cesium-plus-pod-subscription/src/main/java/org/duniter/elasticsearch/subscription/PluginSettings.java
+++ b/cesium-plus-pod-subscription/src/main/java/org/duniter/elasticsearch/subscription/PluginSettings.java
@@ -99,8 +99,12 @@ public class PluginSettings extends AbstractLifecycleComponent<PluginSettings> {
         return this.settings.get("duniter.subscription.email.link.url", getCesiumUrl());
     }
 
+    public String getEmailLinkName() {
+        return this.settings.get("duniter.subscription.email.link.name", "Cesium+");
+    }
+
     /**
-     * Should email subscription be send at startup ?
+     * Should email subscription be sendBlock at startup ?
      * @return
      */
     public boolean isEmailSubscriptionsExecuteAtStartup() {
diff --git a/cesium-plus-pod-subscription/src/main/java/org/duniter/elasticsearch/subscription/service/SubscriptionService.java b/cesium-plus-pod-subscription/src/main/java/org/duniter/elasticsearch/subscription/service/SubscriptionService.java
index 55c73092..f05e8866 100644
--- a/cesium-plus-pod-subscription/src/main/java/org/duniter/elasticsearch/subscription/service/SubscriptionService.java
+++ b/cesium-plus-pod-subscription/src/main/java/org/duniter/elasticsearch/subscription/service/SubscriptionService.java
@@ -73,6 +73,7 @@ public class SubscriptionService extends AbstractService {
     private UserEventService userEventService;
     private UserService userService;
     private String emailSubjectPrefix;
+    private String emailLinkName;
     private STGroup templates;
     private boolean debug;
 
@@ -99,6 +100,7 @@ public class SubscriptionService extends AbstractService {
         if (StringUtils.isNotBlank(emailSubjectPrefix)) {
             emailSubjectPrefix += " "; // add one trailing space
         }
+        this.emailLinkName = pluginSettings.getEmailLinkName().trim();
         this.debug = logger.isDebugEnabled();
 
         // Configure springtemplate engine
@@ -290,7 +292,7 @@ public class SubscriptionService extends AbstractService {
         if (lastExecution != null) {
             lastExecutionTime = lastExecution.getTime();
         }
-        // If first email execution: only send event from the last 7 days.
+        // If first email execution: only sendBlock event from the last 7 days.
         else  {
             Calendar defaultDateLimit = new GregorianCalendar();
             defaultDateLimit.setTimeInMillis(System.currentTimeMillis());
@@ -323,7 +325,8 @@ public class SubscriptionService extends AbstractService {
                 profileTitles,
                 userEvents,
                 userLocale,
-                pluginSettings.getEmailLinkUrl())
+                pluginSettings.getEmailLinkUrl(),
+                emailLinkName)
                 .render(userLocale);
 
         // Compute HTML content
@@ -335,12 +338,13 @@ public class SubscriptionService extends AbstractService {
                 profileTitles,
                 userEvents,
                 userLocale,
-                pluginSettings.getEmailLinkUrl())
+                pluginSettings.getEmailLinkUrl(),
+                emailLinkName)
                 .render(userLocale);
 
         final String object = emailSubjectPrefix + I18n.t("duniter4j.es.subscription.email.subject", userEvents.size());
         if (pluginSettings.isEmailSubscriptionsDebug()) {
-            logger.info(String.format("---- Email to send (debug mode) ------\nTo:%s\nObject: %s\nText content:\n%s",
+            logger.info(String.format("---- Email to sendBlock (debug mode) ------\nTo:%s\nObject: %s\nText content:\n%s",
                     subscription.getContent().getEmail(),
                     object,
                     text));
@@ -376,7 +380,8 @@ public class SubscriptionService extends AbstractService {
                                   Map<String, String> issuerProfilNames,
                                   List<UserEvent> userEvents,
                                   final Locale issuerLocale,
-                                  String cesiumSiteUrl) {
+                                  String linkUrl,
+                                  String linkName) {
         String issuerName = issuerProfilNames != null && issuerProfilNames.containsKey(subscription.getIssuer()) ?
                 issuerProfilNames.get(subscription.getIssuer()) :
                 ModelUtils.minifyPubkey(subscription.getIssuer());
@@ -387,7 +392,8 @@ public class SubscriptionService extends AbstractService {
 
         try {
             // Compute body
-            template.add("url", cesiumSiteUrl);
+            template.add("linkName", linkName);
+            template.add("url", linkUrl);
             template.add("issuerPubkey", subscription.getIssuer());
             template.add("issuerName", issuerName);
             template.add("senderPubkey", senderPubkey);
diff --git a/cesium-plus-pod-subscription/src/main/resources/i18n/cesium-plus-pod-subscription_en_GB.properties b/cesium-plus-pod-subscription/src/main/resources/i18n/cesium-plus-pod-subscription_en_GB.properties
index 3277f18a..d8340fa1 100644
--- a/cesium-plus-pod-subscription/src/main/resources/i18n/cesium-plus-pod-subscription_en_GB.properties
+++ b/cesium-plus-pod-subscription/src/main/resources/i18n/cesium-plus-pod-subscription_en_GB.properties
@@ -1,13 +1,13 @@
-duniter4j.es.subscription.email.footer.disableHelp=You can disable this email notification service in the page %2$% (%1$s).
+duniter4j.es.subscription.email.footer.disableHelp=You can disable this email notification service in the page %2$% %3$% (%1$s).
 duniter4j.es.subscription.email.footer.sendBy=This email has sent you the Cesium+ node of %2$s (%1$s).
 duniter4j.es.subscription.email.hello=Hello %s\!
 duniter4j.es.subscription.email.html.footer.disableHelp=You can disable this email notification service in <a href\="%s">online services</a> page.
-duniter4j.es.subscription.email.html.footer.sendBy=This email has sent you the Cesium+ node of <a href\="%s">%s</a>..
+duniter4j.es.subscription.email.html.footer.sendBy=This email has sent you the %3$% node of <a href\="%1$%">%2$%</a>..
 duniter4j.es.subscription.email.html.hello=Hello <b>%s</b>\!
 duniter4j.es.subscription.email.html.pubkey=Public key\: <a href\="%s">%s</a>
 duniter4j.es.subscription.email.html.unreadCount=You received <b>%s new notifications</b>.
 duniter4j.es.subscription.email.notificationsDivider=Notifications list\:
-duniter4j.es.subscription.email.openCesium=Open Cesium+
+duniter4j.es.subscription.email.open=Open
 duniter4j.es.subscription.email.pubkey=Public key\: %2$s (%1$s)
 duniter4j.es.subscription.email.start=Email subscriptions\: daily mailing [at %1$s\:00] and weekly [on %2$s at %1$s\:00]
 duniter4j.es.subscription.email.subject=You received %s new notifications
diff --git a/cesium-plus-pod-subscription/src/main/resources/i18n/cesium-plus-pod-subscription_fr_FR.properties b/cesium-plus-pod-subscription/src/main/resources/i18n/cesium-plus-pod-subscription_fr_FR.properties
index 28faba1c..ece559c0 100644
--- a/cesium-plus-pod-subscription/src/main/resources/i18n/cesium-plus-pod-subscription_fr_FR.properties
+++ b/cesium-plus-pod-subscription/src/main/resources/i18n/cesium-plus-pod-subscription_fr_FR.properties
@@ -1,13 +1,13 @@
-duniter4j.es.subscription.email.footer.disableHelp=Vous pouvez désactiver ce service de notification par email, dans la rubrique "Services en ligne" de Cesium+ (%s).
-duniter4j.es.subscription.email.footer.sendBy=Cet email vous a été envoyé le noeud Cesium+ de %2$s (%1$s).
+duniter4j.es.subscription.email.footer.disableHelp=Vous pouvez désactiver ce service de notification par email, dans la rubrique "Services en ligne" de %2$s (%1$s).
+duniter4j.es.subscription.email.footer.sendBy=Cet email vous a été envoyé le noeud %3$s de %2$s (%1$s).
 duniter4j.es.subscription.email.hello=Bonjour %s \!
-duniter4j.es.subscription.email.html.footer.disableHelp=Vous pouvez désactiver ce service de notification par email, dans <a href\="%s">la rubrique services en ligne</a> de Cesium+.
-duniter4j.es.subscription.email.html.footer.sendBy=Cet email vous a été envoyé depuis le noeud Cesium+ de <a href\="%1$s">%2$s</a>.
+duniter4j.es.subscription.email.html.footer.disableHelp=Vous pouvez désactiver ce service de notification par email, dans <a href\="%1$s">la rubrique services en ligne</a> de %2$s.
+duniter4j.es.subscription.email.html.footer.sendBy=Cet email vous a été envoyé depuis le noeud %3$s de <a href\="%1$s">%2$s</a>.
 duniter4j.es.subscription.email.html.hello=Bonjour <b>%s</b> \!
 duniter4j.es.subscription.email.html.pubkey=Clé publique \: <a href\="%s">%s</a>
 duniter4j.es.subscription.email.html.unreadCount=Vous avez <b>%s notifications</b> non lues.
 duniter4j.es.subscription.email.notificationsDivider=Liste des notifications \:
-duniter4j.es.subscription.email.openCesium=Ouvrir Cesium+
+duniter4j.es.subscription.email.open=Ouvrir
 duniter4j.es.subscription.email.pubkey=Clé publique \: %2$s (%1$s)
 duniter4j.es.subscription.email.start=Abonnement email\: envoi quotidien [à %1$s\:00] et hebdomadaire [le %2$s à %1$s\:00]
 duniter4j.es.subscription.email.subject=%s nouvelles notifications non lues
diff --git a/cesium-plus-pod-subscription/src/main/resources/org/duniter/elasticsearch/subscription/templates/html_email_content.st b/cesium-plus-pod-subscription/src/main/resources/org/duniter/elasticsearch/subscription/templates/html_email_content.st
index c6fa7ed4..af20871b 100644
--- a/cesium-plus-pod-subscription/src/main/resources/org/duniter/elasticsearch/subscription/templates/html_email_content.st
+++ b/cesium-plus-pod-subscription/src/main/resources/org/duniter/elasticsearch/subscription/templates/html_email_content.st
@@ -1,4 +1,4 @@
-html_email_content(issuerPubkey, issuerName, senderPubkey, senderName, events, url) ::= <<
+html_email_content(issuerPubkey, issuerName, senderPubkey, senderName, events, url, linkName) ::= <<
 <table cellspacing="0" cellpadding="0" width="100%"
   style="font-size:12px;font-family:Helvetica Neue,Helvetica,Lucida Grande,tahoma,verdana,arial,sans-serif;border-spacing:0px;border-collapse:collapse;max-width:600px!important;">
     <tr>
@@ -33,7 +33,7 @@ html_email_content(issuerPubkey, issuerName, senderPubkey, senderName, events, u
                     <td>
                         <p style="margin:0px;width:100%;text-align:right;min-height: 64px;padding: 16px 0px;">
                             <a style="overflow:hidden!important;background-color:#387ef5;border-color:transparent;border-radius:2px;border-shadow: 2px 2px rgba(50,50,50,0.32);box-sizing: border-box;color:white;display:inline-block;font-size:14px;font-weight: 500;height: 47px;letter-spacing: 0.5px;line-height:42px;margin:0;min-height:47px;min-width:52px;padding-bottom:0px;padding-left:24px;padding-right:24px;padding-top:0px;text-align:center;text-decoration:none;text-transform:uppercase;"
-                               href="$url$">$i18n("duniter4j.es.subscription.email.openCesium")$ &gt;&gt;</a>
+                               href="$url$">$i18n("duniter4j.es.subscription.email.open")$ $linkName$ &gt;&gt;</a>
                         </p>
                     </td>
                 </tr>
@@ -63,10 +63,10 @@ html_email_content(issuerPubkey, issuerName, senderPubkey, senderName, events, u
         <td>
             <div style="background-color: rgb(236, 240, 247) !important;border-color: rgb(221, 223, 226) !important;width:100%;text-align:center;border-radius:4px;">
                 <p style="margin:0px;padding:8px 0px;text-align:center;color:grey !important;text-decoration:none !important;">
-                   $i18n_args("duniter4j.es.subscription.email.html.footer.sendBy", [{$[url, "/#/app/wot/", senderPubkey, "/"]; separator=""$}, senderName])$
+                   $i18n_args("duniter4j.es.subscription.email.html.footer.sendBy", [{$[url, "/#/app/wot/", senderPubkey, "/"]; separator=""$}, senderName, linkName])$
                    <br/>
                    <small>
-                       $i18n_args("duniter4j.es.subscription.email.html.footer.disableHelp", {$[url, "/#/app/wallet/subscriptions"]; separator=""$})$
+                       $i18n_args("duniter4j.es.subscription.email.html.footer.disableHelp", [{$[url, "/#/app/wallet/subscriptions"]; separator=""$}, linkName])$
                    </small>
                 </p>
             </div>
diff --git a/cesium-plus-pod-subscription/src/main/resources/org/duniter/elasticsearch/subscription/templates/text_email.st b/cesium-plus-pod-subscription/src/main/resources/org/duniter/elasticsearch/subscription/templates/text_email.st
index 004367d9..870b8832 100644
--- a/cesium-plus-pod-subscription/src/main/resources/org/duniter/elasticsearch/subscription/templates/text_email.st
+++ b/cesium-plus-pod-subscription/src/main/resources/org/duniter/elasticsearch/subscription/templates/text_email.st
@@ -1,15 +1,15 @@
-text_email(issuerPubkey, issuerName, senderPubkey, senderName, events, url) ::= <<
+text_email(issuerPubkey, issuerName, senderPubkey, senderName, events, url, linkName) ::= <<
 $i18n_args("duniter4j.es.subscription.email.hello", issuerName)$
 $i18n_args("duniter4j.es.subscription.email.unreadCount", {$length(events)$} )$
 
 $i18n("duniter4j.es.subscription.email.notificationsDivider")$
 $events:{e|$text_event_item(e)$}$
 
-$i18n("duniter4j.es.subscription.email.openCesium")$ : $url$
-$if(issuerPubkey)$$i18n_args("duniter4j.es.subscription.email.pubkey", [{$[url, "/#/app/wot/", issuerPubkey, "/"]; separator=""$}, {$issuerPubkey; format="pubkey"$}])$$endif$
+$i18n("duniter4j.es.subscription.email.open")$ $linkName$ : $url$
+$if(issuerPubkey)$$i18n_args("duniter4j.es.subscription.email.pubkey", [{$[url, "/#/app/wot/", issuerPubkey, "/"]; separator=""$}, {$issuerPubkey; format="pubkey"$}, linkName])$$endif$
 
 -----------------------------------------------
-$i18n_args("duniter4j.es.subscription.email.footer.sendBy", [{$[url, "/#/app/wot/", senderPubkey, "/"]; separator=""$}, senderName])$
-$i18n_args("duniter4j.es.subscription.email.footer.disableHelp", {$[url, "/#/app/wallet/subscriptions"]; separator=""$})$
+$i18n_args("duniter4j.es.subscription.email.footer.sendBy", [{$[url, "/#/app/wot/", senderPubkey, "/"]; separator=""$}, senderName, linkName])$
+$i18n_args("duniter4j.es.subscription.email.footer.disableHelp", [{$[url, "/#/app/wallet/subscriptions"]; separator=""$}, linkName])$
 
 >>
\ No newline at end of file
diff --git a/cesium-plus-pod-user/src/main/java/org/duniter/elasticsearch/user/websocket/WebsocketUserEventEndPoint.java b/cesium-plus-pod-user/src/main/java/org/duniter/elasticsearch/user/websocket/WebsocketUserEventEndPoint.java
index 6f2c8983..fd425384 100644
--- a/cesium-plus-pod-user/src/main/java/org/duniter/elasticsearch/user/websocket/WebsocketUserEventEndPoint.java
+++ b/cesium-plus-pod-user/src/main/java/org/duniter/elasticsearch/user/websocket/WebsocketUserEventEndPoint.java
@@ -45,7 +45,7 @@ import org.duniter.core.util.StringUtils;
 import org.duniter.elasticsearch.user.PluginSettings;
 import org.duniter.elasticsearch.user.model.UserEvent;
 import org.duniter.elasticsearch.user.service.UserEventService;
-import org.duniter.elasticsearch.websocket.WebSocketServer;
+import org.duniter.elasticsearch.http.tyrus.TyrusWebSocketServer;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.logging.ESLogger;
 import org.elasticsearch.common.logging.Loggers;
@@ -66,7 +66,7 @@ public class WebsocketUserEventEndPoint implements UserEventService.UserEventLis
     public static class Init {
 
         @Inject
-        public Init(WebSocketServer webSocketServer, PluginSettings pluginSettings) {
+        public Init(TyrusWebSocketServer webSocketServer, PluginSettings pluginSettings) {
             webSocketServer.addEndPoint(WebsocketUserEventEndPoint.class);
             defaultLocale = pluginSettings.getI18nLocale();
             if (defaultLocale == null) defaultLocale = new Locale("en", "GB");
diff --git a/pom.xml b/pom.xml
index f66a1bdb..27a47968 100644
--- a/pom.xml
+++ b/pom.xml
@@ -29,7 +29,7 @@
     <signatureVersion>1.0</signatureVersion>
 
     <!-- Commons versions -->
-    <duniter4j.version>1.2.0</duniter4j.version>
+    <duniter4j.version>1.2.5-SNAPSHOT</duniter4j.version>
     <log4j.version>1.2.17</log4j.version>
     <slf4j.version>1.7.6</slf4j.version>
     <guava.version>22.0</guava.version>
-- 
GitLab