diff --git a/www/i18n/locale-fr-FR.json b/www/i18n/locale-fr-FR.json
index 6985a1d58635e512dd726728e37d0c8f6dd655ac..acc8819fcd1228d5b57dd8a3998a438f955ae2b1 100644
--- a/www/i18n/locale-fr-FR.json
+++ b/www/i18n/locale-fr-FR.json
@@ -114,6 +114,7 @@
     "PEER": "Adresse du nœud Duniter",
     "USE_LOCAL_STORAGE": "Activer le stockage local",
     "ENABLE_HELPTIP": "Activer les bulles d'aide contextuelles",
+    "ENABLE_UI_EFFECTS": "Activer les effets visuels",
     "HISTORY_SETTINGS": "Mon compte",
     "DISPLAY_UD_HISTORY": "Afficher les dividendes produits ?",
     "AUTHENTICATION_SETTINGS": "Authentification",
diff --git a/www/index.html b/www/index.html
index 3ae85bf09286f11af671ff285aaffae8a8300bd4..99cd88368dc4a76de74d35e0bcdb718bd1883513 100644
--- a/www/index.html
+++ b/www/index.html
@@ -115,6 +115,7 @@
   <script src="dist/dist_js/plugins/es/js/services.js"></script>
   <script src="dist/dist_js/plugins/es/js/services/comment-services.js"></script>
   <script src="dist/dist_js/plugins/es/js/services/http-services.js"></script>
+  <script src="dist/dist_js/plugins/es/js/services/settings-services.js"></script>
   <script src="dist/dist_js/plugins/es/js/services/market-services.js"></script>
   <script src="dist/dist_js/plugins/es/js/services/registry-services.js"></script>
   <script src="dist/dist_js/plugins/es/js/services/social-services.js"></script>
@@ -123,6 +124,7 @@
   <script src="dist/dist_js/plugins/es/js/services/message-services.js"></script>
   <script src="dist/dist_js/plugins/es/js/services/modal-services.js"></script>
   <script src="dist/dist_js/plugins/es/js/services/blockchain-services.js"></script>
+  <script src="dist/dist_js/plugins/es/js/services/group-services.js"></script>
   <script src="dist/dist_js/plugins/es/js/controllers/common-controllers.js"></script>
   <script src="dist/dist_js/plugins/es/js/controllers/app-controllers.js"></script>
   <script src="dist/dist_js/plugins/es/js/controllers/settings-controllers.js"></script>
@@ -136,6 +138,7 @@
   <script src="dist/dist_js/plugins/es/js/controllers/notification-controllers.js"></script>
   <script src="dist/dist_js/plugins/es/js/controllers/blockchain-controllers.js"></script>
   <script src="dist/dist_js/plugins/es/js/controllers/network-controllers.js"></script>
+  <script src="dist/dist_js/plugins/es/js/controllers/group-controllers.js"></script>
 
 
   <!--endRemoveIf(no-plugin)-->
diff --git a/www/js/config.js b/www/js/config.js
index 5142bccf37f25925e9c72d0ed4a6ffb9a3779429..401aab98d70952733dda01bc8a987a4b816e1354 100644
--- a/www/js/config.js
+++ b/www/js/config.js
@@ -10,32 +10,35 @@ angular.module("cesium.config", [])
 
 .constant("csConfig", {
 	"cacheTimeMs": 60000,
-	"fallbackLanguage": "en",
-	"rememberMe": false,
+	"fallbackLanguage": "fr-FR",
+	"defaultLanguage": "fr-FR",
+	"rememberMe": true,
 	"showUDHistory": false,
-	"timeout": 10000,
+	"timeout": 6000,
 	"timeWarningExpireMembership": 5184000,
 	"timeWarningExpire": 7776000,
 	"useLocalStorage": true,
 	"useRelative": true,
 	"initPhase": false,
-	"expertMode": false,
-	"decimalCount": 4,
-	"httpsMode": false,
+	"expertMode": true,
 	"helptip": {
-		"enable": true,
-		"installDocUrl": "https://github.com/duniter/duniter/blob/master/doc/install-a-node.md"
+		"enable": false,
+		"installDocUrl": {
+			"fr-FR": "http://www.le-sou.org/devenir-noeud/",
+			"en": "https://github.com/duniter/duniter/blob/master/doc/install-a-node.md"
+		}
 	},
 	"node": {
-		"host": "gtest.duniter.org",
+		"host": "gtest.duniter.fr",
 		"port": "10900"
 	},
 	"plugins": {
 		"es": {
 			"enable": true,
 			"askEnable": false,
-			"host": "data.gtest.duniter.fr",
-			"port": "80",
+			"host": "localhost",
+			"port": 9200,
+			"wsPort": 9400,
 			"notifications": {
 				"txSent": true,
 				"txReceived": true,
@@ -45,7 +48,7 @@ angular.module("cesium.config", [])
 		}
 	},
 	"version": "0.9.34",
-	"build": "2017-02-18T08:58:50.508Z",
+	"build": "2017-02-23T14:40:58.183Z",
 	"newIssueUrl": "https://github.com/duniter/cesium/issues/new?labels=bug"
 })
 
diff --git a/www/js/controllers/settings-controllers.js b/www/js/controllers/settings-controllers.js
index 1c4b32be6210e28f2889472ef469173b61d3ae13..4c93074f0618071c80475ef8ba52d9514c9e14b3 100644
--- a/www/js/controllers/settings-controllers.js
+++ b/www/js/controllers/settings-controllers.js
@@ -62,8 +62,10 @@ function SettingsController($scope, $q, $ionicPopup, $timeout, $translate, csHtt
     if ($scope.actionsPopover) {
       $scope.actionsPopover.hide();
     }
+    $scope.pendingSaving = true;
     csSettings.reset();
     angular.merge($scope.formData, csSettings.data);
+    $scope.pendingSaving = false;
   };
 
   $scope.changeLanguage = function(langKey) {
diff --git a/www/js/controllers/wot-controllers.js b/www/js/controllers/wot-controllers.js
index f7b5fbd24c656c158316310899f3abf3cbe1c9f4..bf116cd5d2f322af10f0bb98f0e2257890d8a470 100644
--- a/www/js/controllers/wot-controllers.js
+++ b/www/js/controllers/wot-controllers.js
@@ -383,7 +383,7 @@ function WotLookupController($scope, $state, $timeout, $focus, $ionicPopover, $i
     if (!$scope.search.results.length) return;
 
     // Motion
-    if (res.length > 0) {
+    if (res.length > 0 && $scope.motion) {
       $scope.motion.show({selector: '.lookupForm .item.ink'});
     }
   };
@@ -425,6 +425,7 @@ function WotLookupModalController($scope, $controller, $focus){
   $scope.search.loading = false;
   $scope.enableFilter = false;
 
+
   $scope.wotSearchTextId = 'wotSearchTextModal';
   $scope.cancel = function(){
     $scope.closeModal();
@@ -437,6 +438,10 @@ function WotLookupModalController($scope, $controller, $focus){
     });
   };
 
+  $scope.doRefreshLocationHref = function() {
+    // Do NOT change location href
+  };
+
   $scope.showHelpTip = function() {
     // silent
   };
@@ -459,7 +464,7 @@ function WotLookupModalController($scope, $controller, $focus){
  * @param csWallet
  * @constructor
  */
-function WotIdentityAbstractController($scope, $rootScope, $state, $timeout, $translate, UIUtils, Modals, csConfig, csWot, csWallet) {
+function WotIdentityAbstractController($scope, $rootScope, $state, $translate, UIUtils, Modals, csConfig, csWot, csWallet) {
   'ngInject';
 
   $scope.formData = {};
diff --git a/www/js/services/crypto-services.js b/www/js/services/crypto-services.js
index 6110bd3a854b3f1a57cbcc57c753538ead6c38eb..c710479e03bd6cf1125c12ff3adde4f5227b3edb 100644
--- a/www/js/services/crypto-services.js
+++ b/www/js/services/crypto-services.js
@@ -629,7 +629,7 @@ angular.module('cesium.crypto.services', ['ngResource', 'cesium.device.services'
         })
         .then(function() {
           service.copy(serviceImpl);
-          console.debug('[crypto] Loaded  \'{0}\' implementation in {1}ms'.format(service.id, new Date().getTime() - now));
+          console.debug('[crypto] Loaded \'{0}\' implementation in {1}ms'.format(service.id, new Date().getTime() - now));
         });
 
     });
diff --git a/www/js/services/device-services.js b/www/js/services/device-services.js
index 64456c56fdba70e7a229a093e2219d849c0d43c4..ee980fb1df76262f55284b929838970b4bb3de58 100644
--- a/www/js/services/device-services.js
+++ b/www/js/services/device-services.js
@@ -29,7 +29,17 @@ angular.module('cesium.device.services', ['ngResource', 'cesium.utils.services']
       function ready() {
         if (!readyPromise) {
           readyPromise = $ionicPlatform.ready().then(function(){
-            console.debug('[ionic] Platform is ready');
+
+            var enableCamera = !!navigator.camera;
+            exports.enable = enableCamera;
+
+            if (exports.enable){
+              var enableBarcodeScanner = cordova && cordova.plugins && !!cordova.plugins.barcodeScanner;
+              console.debug('[device] Ionic platform ready, with [barcodescanner={0}] [camera={1}]'.format(enableBarcodeScanner, enableCamera));
+            }
+            else {
+              console.debug('[device] Ionic platform ready - no device detected.');
+            }
           });
         }
         return readyPromise;
@@ -128,21 +138,6 @@ angular.module('cesium.device.services', ['ngResource', 'cesium.utils.services']
         return deferred.promise;
       }
 
-      // On platform ready: check if device could be used
-      ready().then(function() {
-        var enableCamera = !!navigator.camera;
-
-        exports.enable = enableCamera;
-
-        if (exports.enable){
-          var enableBarcodeScanner = cordova && cordova.plugins && !!cordova.plugins.barcodeScanner;
-          console.debug('[device] Ready with [barcodescanner={0}] [camera={1}]'.format(enableBarcodeScanner, enableCamera));
-        }
-        else {
-          console.debug('[device] No device detected');
-        }
-      });
-
       exports.ready = ready;
       exports.clipboard = {copy: copy};
       exports.camera = {
diff --git a/www/js/services/http-services.js b/www/js/services/http-services.js
index 764a0b8220e6fa98bf2b9bb79469bd9dc483e5fb..0e276ba9549ef23054611d879ce241779536f617 100644
--- a/www/js/services/http-services.js
+++ b/www/js/services/http-services.js
@@ -18,8 +18,8 @@ angular.module('cesium.http.services', ['ngResource', 'cesium.cache.services'])
       return  !host ? null : (host + (port ? ':' + port : ''));
     }
 
-    function getUrl(host, port, path) {
-      var protocol = (port == 443 ? 'https' : 'http');
+    function getUrl(host, port, path, useSsl) {
+      var protocol = (port == 443 || useSsl) ? 'https' : 'http';
       return  protocol + '://' + getServer(host, port) + (path ? path : '');
     }
 
@@ -48,7 +48,7 @@ angular.module('cesium.http.services', ['ngResource', 'cesium.cache.services'])
 
       _.forEach(pkeys, function(pkey){
         var prevURI = newUri;
-        newUri = newUri.replace(new RegExp(':' + pkey), params[pkey]);
+        newUri = newUri.replace(':' + pkey, params[pkey]);
         if (prevURI == newUri) {
           queryParams[pkey] = params[pkey];
         }
@@ -57,7 +57,6 @@ angular.module('cesium.http.services', ['ngResource', 'cesium.cache.services'])
       return callback(newUri, config);
     }
 
-
     function getResource(host, port, path) {
       var url = getUrl(host, port, path);
       return function(params) {
@@ -137,33 +136,38 @@ angular.module('cesium.http.services', ['ngResource', 'cesium.cache.services'])
       };
     }
 
-    function ws(uri) {
+    function ws(host, port, path, useSsl) {
+      var uri = ((port == 443 || useSsl) ? 'wss' : 'ws') + '://' + getServer(host, port) + path;
       var sock = null;
       var callbacks = [];
 
       function _waitOpen() {
         if (!sock) throw new Error('Websocket not opened');
-        if (sock && sock.readyState === 1) {
+        if (sock.readyState == 1) {
           return $q.when(sock);
         }
+        if (sock.readyState == 3) {
+          return $q.reject('Unable to connect to Websocket ['+sock.url+']');
+        }
         return $timeout(_waitOpen, 100);
       }
 
       function _open(self, callback, params) {
         if (!sock) {
           prepare(uri, params, {}, function(uri) {
+            console.debug('[http] Listening on websocket ['+path+']...');
             sock = new WebSocket(uri);
+            sock.onerror = function(e) {
+              sock.readyState=3;
+            };
+            sock.onmessage = function(e) {
+              var obj = JSON.parse(e.data);
+              _.forEach(callbacks, function(callback) {
+                callback(obj);
+              });
+            };
             sockets.push(self);
           });
-          sock.onerror = function(e) {
-            console.error(e);
-          };
-          sock.onmessage = function(e) {
-            var obj = JSON.parse(e.data);
-            _.forEach(callbacks, function(callback) {
-              callback(obj);
-            });
-          };
         }
         if (callback) callbacks.push(callback);
         return _waitOpen();
@@ -184,6 +188,7 @@ angular.module('cesium.http.services', ['ngResource', 'cesium.cache.services'])
         },
         close: function() {
           if (sock) {
+            console.debug('[http] Stopping websocket ['+path+']...');
             sock.close();
             sock = null;
             callbacks = [];
@@ -247,6 +252,7 @@ angular.module('cesium.http.services', ['ngResource', 'cesium.cache.services'])
     };
   }
 
+
   return factory(csSettings.data.timeout);
 })
 ;
diff --git a/www/js/services/settings-services.js b/www/js/services/settings-services.js
index 038d6b9c5d284fc2ca5a14ff8d26903eb9b06cfe..6c763ba3a2d1a96fb2de4c4180396b6858bf9234 100644
--- a/www/js/services/settings-services.js
+++ b/www/js/services/settings-services.js
@@ -4,7 +4,7 @@ angular.module('cesium.settings.services', ['ngResource', 'ngApi', 'cesium.confi
 .factory('csSettings', function($q, Api, localStorage, $translate, csConfig, Device) {
   'ngInject';
 
-    CSSettings = function(id) {
+    function Factory() {
 
       // Define app locales
       var locales = [
@@ -81,11 +81,13 @@ angular.module('cesium.settings.services', ['ngResource', 'ngApi', 'cesium.confi
       }, csConfig),
 
       data = angular.copy(defaultSettings),
+      previousData,
 
-      api = new Api(this, "csSettings-" + id),
+      api = new Api(this, "csSettings"),
 
       reset = function() {
         angular.merge(data, defaultSettings);
+        store();
       },
 
       getByPath = function(path, defaultValue) {
@@ -101,6 +103,14 @@ angular.module('cesium.settings.services', ['ngResource', 'ngApi', 'cesium.confi
         return obj;
       },
 
+      emitChangedEvent = function() {
+        var hasChanged = previousData && !angular.equals(previousData, data);
+        previousData = angular.copy(data);
+        if (hasChanged) {
+          api.data.raise.changed(data);
+        }
+      },
+
       store = function() {
         if (data.useLocalStorage) {
           localStorage.setObject(constants.STORAGE_KEY, data);
@@ -111,29 +121,26 @@ angular.module('cesium.settings.services', ['ngResource', 'ngApi', 'cesium.confi
 
         // Emit event on store
         return api.data.raisePromise.store(data)
-          .then(function() {
-            // Emit event on changed
-            api.data.raise.changed(data);
-          });
+          .then(emitChangedEvent);
       },
 
       restore = function(first) {
+        var now = new Date().getTime();
         return $q(function(resolve, reject){
-          console.debug("[settings] Trying to restore settings...");
+          console.debug("[settings] Loading from local storage...");
           var storedData = localStorage.getObject(constants.STORAGE_KEY);
 
           var finishRestore = function() {
-            console.debug("[settings] Restored");
-
-            // Emit event on changed
-            api.data.raise.changed(data);
+            emitChangedEvent();
             resolve();
           };
 
           // No settings stored
           if (!storedData) {
-            if (defaultSettings.locale.id !== $translate.use()) {
-              $translate.use(defaultSettings.locale.id);
+            console.debug("[settings] No settings in local storage");
+            if (data.locale.id !== $translate.use()) {
+              console.debug("[settings] Changing locale to [{0}]...".format(data.locale.id));
+              $translate.use(data.locale.id);
               finishRestore();
             }
             return;
@@ -186,6 +193,7 @@ angular.module('cesium.settings.services', ['ngResource', 'ngApi', 'cesium.confi
             $translate.use(fixLocale(data.locale.id));
           }
 
+          console.debug('[settings] Loaded from local storage in '+(new Date().getTime()-now)+'ms');
           finishRestore();
         });
       };
@@ -195,7 +203,6 @@ angular.module('cesium.settings.services', ['ngResource', 'ngApi', 'cesium.confi
     api.registerEvent('data', 'ready');
 
     return {
-      id: id,
       data: data,
       getByPath: getByPath,
       reset: reset,
@@ -206,11 +213,9 @@ angular.module('cesium.settings.services', ['ngResource', 'ngApi', 'cesium.confi
       api: api,
       locales: locales
     };
-  };
-
-  var service = CSSettings('default');
+  }
 
-  service.instance = CSSettings;
+  var service = Factory();
 
   service.restore()
     .then(function() {
diff --git a/www/js/services/wallet-services.js b/www/js/services/wallet-services.js
index 2a11ec31f1eb0ef60dc196f2ce723270c9cdac07..d480adbcec4a376de92c5b3d1d95e00fa98da408 100644
--- a/www/js/services/wallet-services.js
+++ b/www/js/services/wallet-services.js
@@ -4,7 +4,7 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser
 
 
 .factory('csWallet', function($q, $rootScope, $timeout, $translate, $filter, Api, localStorage,
-                              CryptoUtils, BMA, csConfig, csSettings, FileSaver, Blob) {
+                              CryptoUtils, BMA, csConfig, csSettings, FileSaver, Blob, csWot) {
   'ngInject';
 
   factory = function(id) {
@@ -1014,19 +1014,19 @@ angular.module('cesium.wallet.services', ['ngResource', 'ngApi', 'cesium.bma.ser
               }
 
               // Add TX to pendings
-              BMA.wot.member.get(destPub)
-                .then(function(member) {
-                  data.tx.pendings.unshift({
-                    time: (Math.floor(moment().utc().valueOf() / 1000)),
-                    amount: -amount,
-                    pubkey: destPub,
-                    uid: member ? member.uid : null,
-                    comment: comments,
-                    isUD: false,
-                    hash: res.hash,
-                    locktime: 0,
-                    block_number: null
-                  });
+              var pendingTx = {
+                time: (Math.floor(moment().utc().valueOf() / 1000)),
+                amount: -amount,
+                pubkey: destPub,
+                comment: comments,
+                isUD: false,
+                hash: res.hash,
+                locktime: 0,
+                block_number: null
+              };
+              csWot.extendAll([pendingTx], 'pubkey')
+                .then(function() {
+                  data.tx.pendings.unshift(pendingTx);
                   store(); // save pendings in local storage
                   resolve();
                 }).catch(function(err){reject(err);});
diff --git a/www/js/services/wot-services.js b/www/js/services/wot-services.js
index 13f266ab31a74000e5ad100b92fd29619485f7da..4b06cd29e25348eaf97c1dfe87915e80bf5e06ac 100644
--- a/www/js/services/wot-services.js
+++ b/www/js/services/wot-services.js
@@ -520,7 +520,7 @@ angular.module('cesium.wot.services', ['ngResource', 'ngApi', 'cesium.bma.servic
         if (pubkey) {
           data = withCache ? identityCache.get(pubkey) : null;
           if (data && (!uid || data.uid == uid)) {
-            console.debug("[wot] Found cached identity " + pubkey.substring(0, 8));
+            console.debug("[wot] Identity " + pubkey.substring(0, 8) + " found in cache");
             return $q.when(data);
           }
           console.debug("[wot] Loading identity " + pubkey.substring(0, 8) + "...");
@@ -618,7 +618,7 @@ angular.module('cesium.wot.services', ['ngResource', 'ngApi', 'cesium.bma.servic
             if (!data.pubkey) return undefined; // not found
             delete data.lookup; // not need anymore
             identityCache.put(data.pubkey, data); // add to cache
-            console.debug('[wallet] Identity '+ data.pubkey.substring(0, 8) +' loaded in '+ (new Date().getTime()-now) +'ms');
+            console.debug('[wot] Identity '+ data.pubkey.substring(0, 8) +' loaded in '+ (new Date().getTime()-now) +'ms');
             return data;
           });
       },
diff --git a/www/plugins/es/i18n/locale-fr-FR.json b/www/plugins/es/i18n/locale-fr-FR.json
index eb17ff208925b2b0c57db0a2c7d7797bb7ed1f24..9c2e50753955a154acb5c980daf19fb6cdad418c 100644
--- a/www/plugins/es/i18n/locale-fr-FR.json
+++ b/www/plugins/es/i18n/locale-fr-FR.json
@@ -31,6 +31,9 @@
     },
     "EVENT": {
       "MEMBER_WITHOUT_PROFILE": "Pour obtenir vos certifications plus rapidement, completez <a href=\"#/app/user/profile/edit\">votre profil utilisateur</a>. Les membres accorderont plus facilement leur confiance à une identité vérifiable."
+    },
+    "ERROR": {
+      "WS_CONNECTION_FAILED": "Cesium ne peut plus recevoir les nouvelles notifications, à cause d'une erreur technique (connexion au noeud de noeud Cesium+).<br/><br/>Si le problème persiste, veuillez <b>changer de noeud de données</b> dans les paramètres de l'extension Cesium+."
     }
   },
   "COMMENTS": {
@@ -149,7 +152,11 @@
       "OPEN_GROUP": "Groupe ouvert",
       "OPEN_GROUP_HELP": "Un groupe ouvert est accessible par n'importe quel membre de la monnaie.",
       "MANAGED_GROUP": "Groupe administré",
-      "MANAGED_GROUP_HELP": "un groupe administré est gérer par des administrateurs et des modérateurs, qui peuvent accepter, refuser ou exclure un membre en son sein."
+      "MANAGED_GROUP_HELP": "un groupe administré est gérer par des administrateurs et des modérateurs, qui peuvent accepter, refuser ou exclure un membre en son sein.",
+      "ENUM": {
+        "OPEN": "Groupe ouvert",
+        "MANAGED": "Groupe administré"
+      }
     },
     "EDIT": {
       "TITLE": "Groupe",
diff --git a/www/plugins/es/js/controllers/group-controllers.js b/www/plugins/es/js/controllers/group-controllers.js
new file mode 100644
index 0000000000000000000000000000000000000000..525b654fcbee9a78d4f12213060aef1c38150ba1
--- /dev/null
+++ b/www/plugins/es/js/controllers/group-controllers.js
@@ -0,0 +1,505 @@
+angular.module('cesium.es.group.controllers', ['cesium.es.services'])
+
+  .config(function($stateProvider) {
+    'ngInject';
+
+    $stateProvider
+
+      .state('app.groups', {
+        url: "/group?type&location",
+        views: {
+          'menuContent': {
+            templateUrl: "plugins/es/templates/group/lookup.html",
+            controller: 'ESGroupListCtrl'
+          }
+        }
+      })
+
+      .state('app.add_group', {
+        url: "/group/add/:type",
+        views: {
+          'menuContent': {
+            templateUrl: "plugins/es/templates/group/edit_group.html",
+            controller: 'ESGroupEditCtrl'
+          }
+        }
+      })
+
+      .state('app.edit_group', {
+        url: "/group/edit/:id",
+        views: {
+          'menuContent': {
+            templateUrl: "plugins/es/templates/group/edit_group.html",
+            controller: 'ESGroupEditCtrl'
+          }
+        }
+      })
+
+
+      .state('app.view_group', {
+        url: "/group/view/:id",
+        views: {
+          'menuContent': {
+            templateUrl: "plugins/es/templates/group/view_record.html",
+            controller: 'ESGroupViewCtrl'
+          }
+        }
+      })
+
+    ;
+  })
+
+  .controller('ESGroupListCtrl', ESGroupListController)
+
+  .controller('ESGroupViewCtrl', ESGroupViewController)
+
+  .controller('ESGroupEditCtrl', ESGroupEditController)
+
+  .controller('PopoverGroupCtrl', PopoverGroupController)
+
+;
+
+function ESGroupListController($scope, UIUtils, $state, csWallet, esGroup, ModalUtils) {
+  'ngInject';
+
+  var defaultSearchLimit = 40;
+
+  $scope.search = {
+    loading : true,
+    results: null,
+    type: 'last',
+    hasMore : false,
+    loadingMore : false,
+    limit: defaultSearchLimit
+  };
+  $scope.enableFilter = !UIUtils.screen.isSmall();
+
+  $scope.$on('$ionicView.enter', function() {
+    if ($scope.search.loading) {
+      $scope.doSearch();
+    }
+  });
+
+  $scope.doSearch = function(from, size) {
+    var options = {};
+    options.from = options.from || from || 0;
+    options.size = options.size || size || defaultSearchLimit;
+    $scope.search.loading = true;
+    return esGroup.record.search(options)
+      .then(function(res) {
+        if (!from) {
+          $scope.search.results = res || [];
+        }
+        else if (res){
+          $scope.search.results = $scope.search.results.concat(res);
+        }
+        $scope.search.loading = false;
+        $scope.search.hasMore = $scope.search.results.length >= $scope.search.limit;
+        $scope.updateView();
+      })
+      .catch(function(err) {
+        $scope.search.loading = false;
+        if (!from) {
+          $scope.search.results = [];
+        }
+        $scope.search.hasMore = false;
+        UIUtils.onError('GROUP.ERROR.SEARCH_GROUPS_FAILED')(err);
+      });
+  };
+
+  $scope.updateView = function() {
+
+    $scope.$broadcast('$$rebind::rebind'); // notify binder
+    $scope.motion.show({selector: '.list.{0} .item'.format($scope.motion.ionListClass)});
+  };
+
+  $scope.select = function(item) {
+    if (item && item.id) $state.go('app.view_group', {id: item.id});
+  };
+
+  $scope.showMore = function() {
+    $scope.search.limit = $scope.search.limit || defaultSearchLimit;
+    $scope.search.limit += defaultSearchLimit;
+    if ($scope.search.limit < defaultSearchLimit) {
+      $scope.search.limit = defaultSearchLimit;
+    }
+    $scope.search.loadingMore = true;
+    $scope.load(
+      $scope.search.results.length, // from
+      $scope.search.limit)
+      .then(function() {
+        $scope.search.loadingMore = false;
+        $scope.$broadcast('scroll.infiniteScrollComplete');
+      });
+  };
+
+  $scope.resetData = function() {
+    if ($scope.search.loading) return;
+    console.debug("[ES] [group] Resetting data (settings or account may have changed)");
+    $scope.search.hasMore = false;
+    $scope.search.results = [];
+    $scope.search.loading = true;
+    delete $scope.search.limit;
+  };
+  // When logout: force reload
+  csWallet.api.data.on.logout($scope, $scope.resetData);
+
+  /* -- modals and views -- */
+
+  $scope.showNewRecordModal = function() {
+    $scope.loadWallet({minData: true})
+      .then(function(walletData) {
+        UIUtils.loading.hide();
+        $scope.walletData = walletData;
+        ModalUtils.show('plugins/es/templates/group/modal_record_type.html')
+          .then(function(type){
+            if (type) {
+              $state.go('app.add_group', {type: type});
+            }
+          });
+      });
+  };
+}
+
+function PopoverGroupController($scope, $timeout, UIUtils, $state, csWallet, esNotification, esGroup, esModals) {
+  'ngInject';
+
+  var defaultSearchLimit = 40;
+
+  $scope.search = {
+    loading : true,
+    results: null,
+    hasMore : false,
+    loadingMore : false,
+    limit: defaultSearchLimit,
+    options: {
+      codes: {
+        includes: esNotification.constants.GROUP_CODES
+      }
+    }
+  };
+
+  $scope.load = function(from, size) {
+    var options = angular.copy($scope.search.options);
+    options.from = options.from || from || 0;
+    options.size = options.size || size || defaultSearchLimit;
+
+    return esNotification.load(csWallet.data.pubkey, options)
+      .then(function(notifications) {
+        if (!from) {
+          $scope.search.results = notifications;
+        }
+        else {
+          $scope.search.results = $scope.search.results.concat(notifications);
+        }
+        $scope.search.loading = false;
+        $scope.search.hasMore = ($scope.search.results && $scope.search.results.length >= $scope.search.limit);
+        $scope.updateView();
+      })
+      .catch(function(err) {
+        $scope.search.loading = false;
+        if (!from) {
+          $scope.search.results = [];
+        }
+        $scope.search.hasMore = false;
+        UIUtils.onError('MESSAGE.ERROR.LOAD_NOTIFICATIONS_FAILED')(err);
+      });
+  };
+
+  $scope.updateView = function() {
+
+    $timeout(function() {
+      UIUtils.ink({selector: '.popover-notification .item.ink'});
+    }, 100);
+  };
+
+  $scope.showMore = function() {
+    $scope.search.limit = $scope.search.limit || defaultSearchLimit;
+    $scope.search.limit = $scope.search.limit * 2;
+    if ($scope.search.limit < defaultSearchLimit) {
+      $scope.search.limit = defaultSearchLimit;
+    }
+    $scope.search.loadingMore = true;
+    $scope.load(
+      $scope.search.results.length, // from
+      $scope.search.limit)
+      .then(function() {
+        $scope.search.loadingMore = false;
+        $scope.$broadcast('scroll.infiniteScrollComplete');
+      });
+  };
+
+  $scope.onNewNotification = function(notification) {
+    if (!$scope.search.loading && !$scope.search.loadingMore &&  notification.isMessage) {
+      console.debug("[popover] detected new message (from notification service)");
+
+      if (notification.reference) {
+        console.log("[popover] new message has a reference !");
+      }
+      $scope.search.results.splice(0,0,notification);
+      $scope.updateView();
+    }
+  };
+
+  $scope.select = function(notification) {
+    if (!notification.read) notification.read = true;
+    $state.go('app.user_view_message', {id: notification.id});
+    $scope.closePopover(notification);
+  };
+
+  $scope.resetData = function() {
+    if ($scope.search.loading) return;
+    console.debug("[ES] [messages] Resetting data (settings or account may have changed)");
+    $scope.search.hasMore = false;
+    $scope.search.results = [];
+    $scope.search.loading = true;
+    delete $scope.search.limit;
+  };
+
+  csWallet.api.data.on.logout($scope, $scope.resetData);
+
+  /* -- Modals -- */
+
+  $scope.showNewMessageModal = function(parameters) {
+    $scope.closePopover();
+    return esModals.showMessageCompose(parameters)
+      .then(function(sent) {
+        if (sent) UIUtils.toast.show('MESSAGE.INFO.MESSAGE_SENT');
+      });
+  };
+
+  esNotification.api.data.on.new($scope, $scope.onNewNotification);
+
+  /* -- default popover action -- */
+  if ($scope.search.loading) {
+    $scope.load();
+  }
+
+}
+
+
+function ESGroupViewController($scope, $state, UIUtils, esGroup, csWallet) {
+  'ngInject';
+
+  $scope.formData = {};
+  $scope.id = null;
+  $scope.pictures = [];
+  $scope.loading = true;
+
+  $scope.$on('$ionicView.enter', function(e, state) {
+    if (state.stateParams && state.stateParams.id) { // Load by id
+      if ($scope.loading || state.stateParams.refresh) { // prevent reload if same id (if not forced)
+        $scope.load(state.stateParams.id, state.stateParams.anchor);
+      }
+      UIUtils.loading.hide();
+      $scope.$broadcast('$recordView.enter', state);
+    }
+    else {
+      $state.go('app.groups');
+    }
+  });
+
+  $scope.load = function(id) {
+    esGroup.record.load(id, {
+      fetchPictures: true
+    })
+      .then(function (data) {
+        $scope.id = data.id;
+        $scope.formData = data.record;
+        $scope.issuer= data.issuer;
+        $scope.canEdit = csWallet.isUserPubkey($scope.formData.issuer);
+
+        $scope.pictures = data.record.pictures || [];
+        delete data.record.pictures; // remove, as already stored in $scope.pictures
+
+        $scope.loading = false;
+        UIUtils.loading.hide();
+        $scope.updateView();
+
+      })
+      .catch(UIUtils.onError('GROUP.ERROR.LOAD_RECORD_FAILED'));
+  };
+
+  $scope.updateView = function() {
+    $scope.motion.show();
+  };
+
+  // Edit click
+  $scope.edit = function() {
+    UIUtils.loading.show();
+    $state.go('app.edit_group', {id: $scope.id});
+  };
+}
+
+function ESGroupEditController($scope, esGroup, UIUtils, $state, $q, Device,
+                               $ionicHistory, ModalUtils, $focus, $timeout, esHttp) {
+  'ngInject';
+
+  $scope.walletData = {};
+  $scope.formData = {};
+  $scope.id = null;
+  $scope.pictures = [];
+  $scope.loading = true;
+
+  $scope.setForm =  function(form) {
+    $scope.form = form;
+  };
+
+  $scope.$on('$ionicView.enter', function(e, state) {
+    $scope.loadWallet({minData: true})
+      .then(function(walletData) {
+        $scope.walletData = walletData;
+        if (state.stateParams && state.stateParams.id) { // Load by id
+          $scope.load(state.stateParams.id);
+        }
+        else {
+          if (state.stateParams && state.stateParams.type) {
+            $scope.formData.type=state.stateParams.type;
+          }
+          $scope.loading = false;
+          UIUtils.loading.hide();
+          $scope.updateView();
+        }
+        // removeIf(device)
+        $focus('group-record-title');
+        // endRemoveIf(device)
+      });
+  });
+
+  $scope.load = function(id) {
+    esGroup.record.load(id, {
+      fetchPictures: true,
+      html: false
+    })
+      .then(function (data) {
+        $scope.formData = data.record;
+        $scope.issuer= data.issuer;
+        $scope.id= data.id;
+
+        $scope.pictures = data.record.pictures || [];
+        delete data.record.pictures; // remove, as already stored in $scope.pictures
+
+        $scope.loading = false;
+        UIUtils.loading.hide();
+        $scope.updateView();
+
+      })
+      .catch(UIUtils.onError('GROUP.ERROR.LOAD_RECORD_FAILED'));
+  };
+
+  $scope.updateView = function() {
+    $scope.motion.show({selector: '.list.{0} .item, .card-gallery'.format($scope.motion.ionListClass)});
+  };
+
+  $scope.save = function() {
+    $scope.form.$submitted=true;
+    if($scope.saving || // avoid multiple save
+      !$scope.form.$valid ||
+      ($scope.formData.type !== 'managed' && $scope.formData.type !== 'open')) {
+      return;
+    }
+    $scope.saving = true;
+    return UIUtils.loading.show()
+
+      .then(function(){
+        var json = $scope.formData;
+        json.time = esHttp.date.now();
+
+        // Resize pictures
+        json.picturesCount = $scope.pictures.length;
+        if (json.picturesCount > 0) {
+          json.pictures = $scope.pictures.reduce(function(res, pic) {
+            return res.concat({file: esHttp.image.toAttachment(pic)});
+          }, []);
+          return UIUtils.image.resizeSrc($scope.pictures[0].src, true) // resize thumbnail
+            .then(function(imageSrc) {
+              json.thumbnail = esHttp.image.toAttachment({src: imageSrc});
+              return json;
+            });
+        }
+        else {
+          if (json.thumbnail) {
+            // FIXME: this is a workaround to allow content deletion
+            // Is it a bug in the ES attachment-mapper ?
+            json.thumbnail = {
+              _content: '',
+              _content_type: ''
+            };
+          }
+          json.pictures = [];
+          return json;
+        }
+      })
+      .then(function(json){
+        // Create
+        if (!$scope.id) {
+          json.creationTime = esHttp.date.now();
+          return esGroup.record.add(json);
+        }
+        // Update
+        return esGroup.record.update(json, {id: $scope.id});
+      })
+
+      .then(function(id) {
+        $scope.id = $scope.id || id;
+        $scope.saving = false;
+        $ionicHistory.clearCache($ionicHistory.currentView().stateId); // clear current view
+        $ionicHistory.nextViewOptions({historyRoot: true});
+        return $state.go('app.view_group', {id: $scope.id, refresh: true});
+      })
+
+      .catch(function(err) {
+        $scope.saving = false;
+        UIUtils.onError('GROUP.ERROR.SAVE_RECORD_FAILED')(err);
+      });
+  };
+
+  $scope.openPicturePopup = function() {
+    Device.camera.getPicture()
+      .then(function(imageData) {
+        $scope.pictures.push({src: "data:image/png;base64," + imageData});
+        $scope.$apply();
+      })
+      .catch(UIUtils.onError('ERROR.TAKE_PICTURE_FAILED'));
+  };
+
+  $scope.fileChanged = function(event) {
+    UIUtils.loading.show();
+    return $q(function(resolve, reject) {
+      var file = event.target.files[0];
+      UIUtils.image.resizeFile(file)
+        .then(function(imageData) {
+          $scope.pictures.push({src: imageData});
+          UIUtils.loading.hide();
+          resolve();
+        });
+    });
+  };
+
+  $scope.removePicture = function(index){
+    $scope.pictures.splice(index, 1);
+  };
+
+  $scope.favoritePicture = function(index){
+    if (index > 0) {
+      var item = $scope.pictures[index];
+      $scope.pictures.splice(index, 1);
+      $scope.pictures.splice(0, 0, item);
+    }
+  };
+
+  $scope.cancel = function() {
+    $ionicHistory.goBack();
+  };
+
+  /* -- modals -- */
+  $scope.showRecordTypeModal = function() {
+    ModalUtils.show('plugins/es/templates/group/modal_record_type.html')
+      .then(function(type){
+        if (type) {
+          $scope.formData.type = type;
+        }
+      });
+  };
+
+}
diff --git a/www/plugins/es/js/controllers/notification-controllers.js b/www/plugins/es/js/controllers/notification-controllers.js
index fb73cb71250421cf4c9a6121278ee693aa7c48e2..586b8b805b80c3424dcd00375c2271e4d5e211b8 100644
--- a/www/plugins/es/js/controllers/notification-controllers.js
+++ b/www/plugins/es/js/controllers/notification-controllers.js
@@ -17,8 +17,6 @@ angular.module('cesium.es.notification.controllers', ['cesium.es.services'])
         }
       })
     ;
-
-
   })
 
   .controller('NotificationsCtrl', NotificationsController)
@@ -27,7 +25,7 @@ angular.module('cesium.es.notification.controllers', ['cesium.es.services'])
 
 ;
 
-function NotificationsController($scope, $rootScope, $timeout, UIUtils, $state, csWallet, esNotification) {
+function NotificationsController($scope, $rootScope, UIUtils, $state, csWallet, esNotification) {
   'ngInject';
 
   var defaultSearchLimit = 40;
diff --git a/www/plugins/es/js/controllers/registry-controllers.js b/www/plugins/es/js/controllers/registry-controllers.js
index 297bc77dd8f9a890e12b0723616da8cb34665503..e0cc8f5abee2f7a10ea8218525a42e5b6754f5fe 100644
--- a/www/plugins/es/js/controllers/registry-controllers.js
+++ b/www/plugins/es/js/controllers/registry-controllers.js
@@ -282,14 +282,7 @@ function ESRegistryLookupController($scope, $state, $focus, $timeout, esRegistry
         $scope.search.loading = false;
 
         if (records.length > 0) {
-          // Set Motion
-          $timeout(function() {
-            UIUtils.motion.ripple({
-              startVelocity: 3000
-            });
-            // Set Ink
-            UIUtils.ink();
-          }, 10);
+          $scope.motion.show();
         }
       })
       .catch(function(err) {
@@ -382,6 +375,7 @@ function ESRegistryRecordViewController($scope, $state, $q, $timeout, $ionicPopo
   $scope.pictures = [];
   $scope.canEdit = false;
   $scope.loading = true;
+  $scope.motion = UIUtils.motion.fadeSlideIn;
 
   $scope.$on('$ionicView.enter', function(e, state) {
     if (state.stateParams && state.stateParams.id) { // Load by id
@@ -410,14 +404,7 @@ function ESRegistryRecordViewController($scope, $state, $q, $timeout, $ionicPopo
         UIUtils.loading.hide();
         $scope.loading = false;
         // Set Motion (only direct children, to exclude .lazy-load children)
-        $timeout(function() {
-          UIUtils.motion.fadeSlideIn({
-            selector: '.list > .item, .list > ng-if > .item',
-            startVelocity: 3000
-          });
-          UIUtils.ink({
-            selector: '.list .item.ink'});
-        }, 10);
+        $scope.motion.show({selector: '.list > .item, .list > ng-if > .item'});
       })
       .catch(function(err) {
         // Retry (ES could have error)
diff --git a/www/plugins/es/js/controllers/settings-controllers.js b/www/plugins/es/js/controllers/settings-controllers.js
index c78a9055f944e6f52fb3c00e9e1fcd4504b06d82..ff6ca7ba8601c8248a02287cdf8fd83f30f1cade 100644
--- a/www/plugins/es/js/controllers/settings-controllers.js
+++ b/www/plugins/es/js/controllers/settings-controllers.js
@@ -48,7 +48,7 @@ function ESExtendSettingsController ($scope, PluginService, csSettings) {
 /*
  * Settings extend controller
  */
-function ESPluginSettingsController ($scope, $q,  $translate, $ionicPopup, UIUtils, Modals, esHttp, csSettings, csHttp, esUser) {
+function ESPluginSettingsController ($scope, $q,  $translate, $ionicPopup, UIUtils, Modals, csHttp, csSettings, esUser, esHttp) {
   'ngInject';
 
   $scope.formData = {};
@@ -81,23 +81,22 @@ function ESPluginSettingsController ($scope, $q,  $translate, $ionicPopup, UIUti
       }
       UIUtils.loading.show();
 
-      return esHttp.get(newNode.host, newNode.port, '/node/summary')() // ping the node
-        .then(function(json) {
-          var valid = json && json.duniter && json.duniter.software === 'duniter4j-elasticsearch';
-          if (!valid) throw 'stop';
+      var newEsNode = esHttp.instance(newNode.host, newNode.port);
+      return newEsNode.isAlive() // ping the node
+        .then(function(alive) {
+          if (!alive) {
+            UIUtils.loading.hide();
+            return UIUtils.alert.error('ERROR.INVALID_NODE_SUMMARY')
+              .then(function(){
+                $scope.changeEsNode(newNode); // loop
+              });
+          }
 
           UIUtils.loading.hide();
           $scope.formData.host = newNode.host;
           $scope.formData.port = newNode.port;
-          esUser.copy(esUser.instance('default', newNode.host, newNode.port));
 
-        })
-        .catch(function(err) {
-          UIUtils.loading.hide();
-          UIUtils.alert.error('ERROR.INVALID_NODE_SUMMARY')
-            .then(function(){
-              $scope.changeEsNode(newNode); // loop
-            });
+          esHttp.copy(newEsNode);
         });
     });
   };
diff --git a/www/plugins/es/js/controllers/user-controllers.js b/www/plugins/es/js/controllers/user-controllers.js
index 22546d0177f1160801c3aad6b9066403c11163fd..ba76816c293db6696f2a198bae350d1aebec95f0 100644
--- a/www/plugins/es/js/controllers/user-controllers.js
+++ b/www/plugins/es/js/controllers/user-controllers.js
@@ -255,6 +255,7 @@ function ProfileController($scope, $rootScope, $timeout, $state, $focus, $transl
         .then(function(imageData) {
           if (!imageData) return;
           $scope.avatar = {src: imageData};
+          $scope.avatarStyle={'background-image':'url("'+imageData+'")'};
           $scope.dirty = true;
         });
     }
diff --git a/www/plugins/es/js/plugin.js b/www/plugins/es/js/plugin.js
index daf538695d4173ee762fe49ec3b99df8644179e6..f1a7d239316ec931a447d3486f90d48207dda76d 100644
--- a/www/plugins/es/js/plugin.js
+++ b/www/plugins/es/js/plugin.js
@@ -13,6 +13,8 @@ angular.module('cesium.es.plugin', [
     'cesium.es.message.controllers',
     'cesium.es.notification.controllers',
     'cesium.es.blockchain.controllers',
-    'cesium.es.network.controllers'
+    'cesium.es.network.controllers',
+    'cesium.es.registry.controllers',
+    'cesium.es.group.controllers'
   ])
 ;
diff --git a/www/plugins/es/js/services.js b/www/plugins/es/js/services.js
index c66b12a63a53b52462190dfcac3ac4e5415fb520..18ebff6271227bda6f47ff02d3fc808c53519f8a 100644
--- a/www/plugins/es/js/services.js
+++ b/www/plugins/es/js/services.js
@@ -4,10 +4,13 @@ angular.module('cesium.es.services', [
     'cesium.es.http.services',
     'cesium.es.comment.services',
     'cesium.es.social.services',
+    'cesium.es.settings.services',
     'cesium.es.user.services',
     'cesium.es.notification.services',
     'cesium.es.message.services',
     'cesium.es.modal.services',
-    'cesium.es.blockchain.services'
+    'cesium.es.blockchain.services',
+    'cesium.es.registry.services',
+    'cesium.es.group.services'
   ])
 ;
diff --git a/www/plugins/es/js/services/blockchain-services.js b/www/plugins/es/js/services/blockchain-services.js
index 70eddb31369c144076adc857e4ac9179da63c2c3..f99c66252c906a70b816e39505cbf9ae754e5485 100644
--- a/www/plugins/es/js/services/blockchain-services.js
+++ b/www/plugins/es/js/services/blockchain-services.js
@@ -1,14 +1,4 @@
 angular.module('cesium.es.blockchain.services', ['cesium.services', 'cesium.es.http.services', 'cesium.es.user.services'])
-.config(function(PluginServiceProvider, csConfig) {
-    'ngInject';
-
-    var enable = csConfig.plugins && csConfig.plugins.es;
-    if (enable) {
-      // Will force to load this service
-      PluginServiceProvider.registerEagerLoadingService('esBlockchain');
-    }
-
-  })
 
 .factory('esBlockchain', function($rootScope, $q, $timeout, esHttp, csConfig, csSettings, esUser) {
   'ngInject';
@@ -24,7 +14,6 @@ angular.module('cesium.es.blockchain.services', ['cesium.services', 'cesium.es.h
         MINIMAL: ['number', 'hash', 'medianTime', 'issuer'],
         COMMONS: ['number', 'hash', 'medianTime', 'issuer', 'currency', 'version', 'powMin', 'dividend', 'membersCount', 'identities', 'joiners', 'actives', 'leavers', 'revoked', 'excluded', 'certifications', 'transactions']
       },
-      listeners,
       exports = {
         node: {},
         block: {},
@@ -45,7 +34,6 @@ angular.module('cesium.es.blockchain.services', ['cesium.services', 'cesium.es.h
     }
 
     function copy(otherNode) {
-      removeListeners();
       if (!!this.instance) {
         var instance = this.instance;
         angular.copy(otherNode, this);
@@ -54,7 +42,6 @@ angular.module('cesium.es.blockchain.services', ['cesium.services', 'cesium.es.h
       else {
         angular.copy(otherNode, this);
       }
-      addListeners();
     }
 
     exports.node.parseEndPoint = function(endpoint) {
@@ -140,47 +127,6 @@ angular.module('cesium.es.blockchain.services', ['cesium.services', 'cesium.es.h
         .then(exports.raw.block.processSearchResult);
     };
 
-
-    function removeListeners() {
-      console.debug('[ES] [blockchain] Disable');
-      _.forEach(listeners, function(remove){
-        remove();
-      });
-      listeners = [];
-    }
-
-    function addListeners() {
-      console.debug('[ES] [blockchain] Enable');
-
-      listeners = [
-        //csWot.api.data.on.search($rootScope, onWotSearch, this)
-      ];
-    }
-
-    function isEnable() {
-      return csSettings.data.plugins &&
-         csSettings.data.plugins.es &&
-         host && csSettings.data.plugins.es.enable;
-    }
-
-    function refreshListeners() {
-      var enable = isEnable();
-      if (!enable && listeners && listeners.length > 0) {
-        removeListeners();
-      }
-      else if (enable && (!listeners || listeners.length === 0)) {
-        addListeners();
-      }
-    }
-
-    // Listen for settings changed
-    csSettings.api.data.on.changed($rootScope, function(){
-      refreshListeners();
-    });
-
-    // Default action
-    refreshListeners();
-
     return exports;
   }
 
diff --git a/www/plugins/es/js/services/comment-services.js b/www/plugins/es/js/services/comment-services.js
index 3db1bc0cfd59e800ae4ede9ecdcf1531b7d5a7ca..1e9f15507b3df0ccaf815d4fc70e0fa47e0316d2 100644
--- a/www/plugins/es/js/services/comment-services.js
+++ b/www/plugins/es/js/services/comment-services.js
@@ -6,6 +6,7 @@ angular.module('cesium.es.comment.services', ['ngResource', 'cesium.bma.services
   function factory(host, port, wsPort, index) {
 
     var
+      that = this,
       DEFAULT_SIZE = 20,
       fields = {
         commons: ["issuer", "time", "message", "reply_to"],
@@ -16,11 +17,11 @@ angular.module('cesium.es.comment.services', ['ngResource', 'cesium.bma.services
           commons: fields.commons
         },
         raw: {
-          search: esHttp.post(host, port, '/'+index+'/comment/_search'),
-          remove: esHttp.record.remove(host, port, index, 'comment'),
-          wsChanges: esHttp.ws((wsPort == 443 ? 'wss' : 'ws') +'://' + esHttp.getServer(host, wsPort) + '/ws/_changes'),
-          add: new esHttp.record.post(host, port, '/'+index+'/comment'),
-          update: new esHttp.record.post(host, port, '/'+index+'/comment/:id/_update')
+          search: esHttp.post('/'+index+'/comment/_search'),
+          remove: esHttp.record.remove(index, 'comment'),
+          wsChanges: esHttp.ws('/ws/_changes'),
+          add: new esHttp.record.post('/'+index+'/comment'),
+          update: new esHttp.record.post('/'+index+'/comment/:id/_update')
         }
       };
 
diff --git a/www/plugins/es/js/services/group-services.js b/www/plugins/es/js/services/group-services.js
new file mode 100644
index 0000000000000000000000000000000000000000..e247c7db9190e5ed59f18680fded03c2552826a4
--- /dev/null
+++ b/www/plugins/es/js/services/group-services.js
@@ -0,0 +1,258 @@
+angular.module('cesium.es.group.services', ['ngResource', 'cesium.services', 'cesium.es.http.services',
+  'cesium.es.user.services', 'cesium.es.notification.services', 'cesium.es.comment.services'])
+  .config(function(PluginServiceProvider, csConfig) {
+    'ngInject';
+
+    var enable = csConfig.plugins && csConfig.plugins.es;
+    if (enable) {
+      // Will force to load this service
+      PluginServiceProvider.registerEagerLoadingService('esGroup');
+    }
+
+  })
+
+.factory('esGroup', function($q, $rootScope, csSettings, esHttp, CryptoUtils, esUser, csWallet, esNotification, esComment) {
+  'ngInject';
+
+  function factory(host, port, wsPort) {
+
+    var
+      listeners,
+      defaultLoadSize = 50,
+      fields = {
+        list: ["issuer", "title"],
+        commons: ["issuer", "title", "description", "creationTime", "time", "signature"],
+        notifications: ["issuer", "time", "hash", "read_signature"]
+      },
+      exports = {
+        _internal: {},
+        node: {
+          host: host,
+          port: port,
+          server: esHttp.getServer(host, port)
+        }
+      };
+
+    function copy(otherNode) {
+      if (!!this.instance) {
+        var instance = this.instance;
+        angular.copy(otherNode, this);
+        this.instance = instance;
+      }
+      else {
+        angular.copy(otherNode, this);
+      }
+    }
+
+    function onWalletInit(data) {
+      data.groups = data.groups || {};
+      data.groups.unreadCount = null;
+    }
+
+    function onWalletReset(data) {
+      if (data.groups) {
+        delete data.groups;
+      }
+    }
+
+    function onWalletLogin(data, deferred) {
+      deferred = deferred || $q.defer();
+      if (!data || !data.pubkey) {
+        deferred.resolve();
+        return deferred.promise;
+      }
+
+      // Count unread notifications
+      esNotification.unreadCount(data.pubkey, {codes: {
+        includes: ['GROUP_INVITATION'],
+        excludes: []
+      }})
+        .then(function(unreadCount){
+          data.groups = data.groups || {};
+          data.groups.unreadCount = unreadCount;
+          console.debug('[ES] [group] Detecting ' + unreadCount + ' unread notifications');
+          deferred.resolve(data);
+        })
+        .catch(function(err){
+          console.error('Error while counting group notifications: ' + (err.message ? err.message : err));
+          deferred.resolve(data);
+        });
+      return deferred.promise;
+    }
+
+    function readRecordFromHit(hit, html) {
+      if (!hit) return;
+      var record = hit._source;
+      if (html && hit.highlight) {
+        if (hit.highlight.title) {
+          record.title = hit.highlight.title[0];
+        }
+        if (hit.highlight.description) {
+          record.description = hit.highlight.description[0];
+        }
+        if (hit.highlight.location) {
+          record.location = hit.highlight.location[0];
+        }
+        if (hit.highlight.tags) {
+          data.tags = hit.highlight.tags.reduce(function(res, tag){
+            return res.concat(tag.replace('<em>', '').replace('</em>', ''));
+          },[]);
+        }
+      }
+
+      // description
+      if (html) {
+        record.description = esHttp.util.trustAsHtml(record.description);
+      }
+
+      // thumbnail
+      record.thumbnail = esHttp.image.fromHit(hit, 'thumbnail');
+
+      // pictures
+      if (hit._source.pictures && hit._source.pictures.reduce) {
+        record.pictures = hit._source.pictures.reduce(function(res, pic) {
+          return res.concat(esHttp.image.fromAttachment(pic.file));
+        }, []);
+      }
+
+      return record;
+    }
+
+    exports._internal.search = esHttp.post('/group/record/_search');
+
+    function searchGroups(options) {
+      if (!csWallet.isLogin()) {
+        return $q.when([]);
+      }
+
+      options = options || {};
+      options.from = options.from || 0;
+      options.size = options.size || defaultLoadSize;
+      options._source = options._source || fields.list;
+      var request = {
+        sort: {
+          "time" : "desc"
+        },
+        from: options.from,
+        size: options.size,
+        _source: options._source
+      };
+
+      return exports._internal.search(request)
+        .then(function(res) {
+          if (!res || !res.hits || !res.hits.total) {
+            return [];
+          }
+          var groups = res.hits.hits.reduce(function(res, hit) {
+            var record = readRecordFromHit(hit, true/*html*/);
+            record.id = hit._id;
+            return record ? res.concat(record) : res;
+          }, []);
+
+          console.debug('[ES] [group] Loading {0} {1} messages'.format(groups.length, options.type));
+
+          return groups;
+        });
+    }
+
+    exports._internal.get = esHttp.get('/group/record/:id');
+    exports._internal.getCommons = esHttp.get('/group/record/:id?_source=' + fields.commons.join(','));
+
+    function loadData(id, options) {
+      options = options || {};
+      options.fecthPictures = angular.isDefined(options.fetchPictures) ? options.fetchPictures : false;
+      options.html = angular.isDefined(options.html) ? options.html : true;
+
+      // Do get source
+      var promise = options.fecthPictures ?
+        exports._internal.get({id: id}) :
+        exports._internal.getCommons({id: id});
+
+      return promise
+        .then(function(hit) {
+          var record = readRecordFromHit(hit, options.html);
+
+          // Load issuer (avatar, name, uid, etc.)
+          return esUser.profile.fillAvatars([{pubkey: record.issuer}])
+            .then(function(idties) {
+              var data = {
+                id: hit._id,
+                issuer: idties[0],
+                record: record
+              };
+              return data;
+            });
+        });
+    }
+
+    function removeListeners() {
+      console.debug("[ES] [group] Disable");
+
+      _.forEach(listeners, function(remove){
+        remove();
+      });
+      listeners = [];
+    }
+
+    function addListeners() {
+      console.debug("[ES] [group] Enable");
+
+      // Extend csWallet.loadData()
+      listeners = [
+        csWallet.api.data.on.login($rootScope, onWalletLogin, this),
+        csWallet.api.data.on.init($rootScope, onWalletInit, this),
+        csWallet.api.data.on.reset($rootScope, onWalletReset, this),
+      ];
+    }
+
+    function isEnable() {
+      return csSettings.data.plugins &&
+        csSettings.data.plugins.es &&
+        host && csSettings.data.plugins.es.enable;
+    }
+
+    function refreshListeners() {
+      var enable = isEnable();
+      if (!enable && listeners && listeners.length > 0) {
+        removeListeners();
+      }
+      else if (enable && (!listeners || listeners.length === 0)) {
+        addListeners();
+      }
+    }
+
+    // Listen for settings changed
+    csSettings.api.data.on.changed($rootScope, function(){
+      refreshListeners();
+      if (isEnable() && !csWallet.data.messages) {
+        onWalletLogin(csWallet.data);
+      }
+    });
+
+    // Default action
+    refreshListeners();
+
+    return {
+      record: {
+        search: searchGroups,
+        load: loadData,
+        add: esHttp.record.post('/group/record'),
+        update: esHttp.record.post('/group/record/:id/_update'),
+        remove: esHttp.record.remove('group', 'record'),
+        fields: {
+          commons: fields.commons
+        },
+        picture: {
+          all: esHttp.get('/group/record/:id?_source=pictures')
+        },
+        comment: new esComment.instance(wsPort, 'group')
+      }
+    };
+  }
+
+  var service = factory();
+
+  service.instance = factory;
+  return service;
+})
+;
diff --git a/www/plugins/es/js/services/http-services.js b/www/plugins/es/js/services/http-services.js
index cd6c44052fa122dd59cb18523f968b26e13fcf8b..6f6e4e8f6301fac80ca223aa4c4781d1f325acab 100644
--- a/www/plugins/es/js/services/http-services.js
+++ b/www/plugins/es/js/services/http-services.js
@@ -1,39 +1,148 @@
-angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'cesium.config'])
+angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.services', 'cesium.config'])
 
 /**
  * Elastic Search Http
  */
-.factory('esHttp', function($q, CryptoUtils, csHttp, $rootScope, $state, $sce, csConfig, csSettings, csWallet) {
+.factory('esHttp', function($q, $timeout, CryptoUtils, csHttp, $rootScope, $state, $sce, csConfig, csSettings, csWallet, Api) {
   'ngInject';
 
-  function factory() {
+  function Factory(host, port, wsPort, useSsl) {
 
     var
-      that,
+      that = this,
       regex = {
         IMAGE_SRC: exact('data:([A-Za-z//]+);base64,(.+)'),
-        HASH_TAG: new RegExp('#([\\wḡĞğ]+)'),
-        USER_TAG: new RegExp('@(\\w+)')
+        HASH_TAG: match('#([\\wḡĞğ]+)'),
+        USER_TAG: match('@(\\w+)')
       };
 
+    this.cache = _emptyCache();
+    that.alive = false;
+    that.host = host;
+    that.port = port || 80;
+    that.wsPort = wsPort || 80;
+    that.useSsl = angular.isDefined(useSsl) ? useSsl : false;
+    that.server = csHttp.getServer(host, port);
+    that.date = {};
+    that.api = new Api(this, "esHttp");
 
     function exact(regexpContent) {
       return new RegExp('^' + regexpContent + '$');
     }
+    function match(regexpContent) {
+      return new RegExp(regexpContent);
+    }
+
+    function _emptyCache() {
+      return {
+        getByPath: {},
+        postByPath: {},
+        wsByPath: {}
+      };
+    }
+
+    that.cleanCache = function() {
+      _.keys(that.cache.wsByPath).forEach(function(key) {
+        var sock = that.cache.wsByPath[key];
+        sock.close();
+      });
+      console.debug('[ES] [http] Cleaning requests cache');
+      that.cache = _emptyCache();
+    };
+
+    that.copy = function(otherNode) {
+      that.host = otherNode.host;
+      that.port = otherNode.port;
+      that.wsPort = otherNode.wsPort || otherNode.port;
+      that.useSsl = otherNode.useSsl;
+      that.server = csHttp.getServer(host, port);
+      return that.restart();
+    };
 
     // Get time (UTC)
-    function getTimeNow() {
+    that.date.now = function() {
        // TODO : use the block chain time
        return Math.floor(moment().utc().valueOf() / 1000);
-    }
+    };
 
-    function get(host, node, path) {
-      return csHttp.get(host, node, path);
-    }
+    that.getUrl  = function(path) {
+      return csHttp.getUrl(that.host, that.port, path, that.useSsl);
+    };
 
-    function post(host, node, path) {
-      return csHttp.post(host, node, path);
-    }
+    that.get = function (path) {
+      return function(params) {
+        var request = that.cache.getByPath[path];
+        if (!request) {
+          request =  csHttp.get(that.host, that.port, path, that.useSsl);
+          that.cache.getByPath[path] = request;
+        }
+        return request(params);
+      };
+    };
+
+    that.post = function(path) {
+      return function(obj, params) {
+        var request = that.cache.postByPath[path];
+        if (!request) {
+          request =  csHttp.post(that.host, that.port, path, that.useSsl);
+        that.cache.postByPath[path] = request;
+        }
+        return request(obj, params);
+      };
+    };
+
+    that.ws = function(path) {
+      return function() {
+        var sock = that.cache.wsByPath[path];
+        if (!sock) {
+          sock =  csHttp.ws(that.host, that.wsPort, path, that.useSsl);
+          that.cache.wsByPath[path] = sock;
+        }
+        return sock;
+      };
+    };
+
+    that.isAlive = function() {
+      return that.node.summary()
+        .then(function(json) {
+          return json && json.duniter && json.duniter.software == 'duniter4j-elasticsearch';
+        })
+        .catch(function() {
+          return false;
+        });
+    };
+
+    that.start = function() {
+
+      console.debug('[ES] [http] Starting on [{0}]...'.format(that.server));
+      var now = new Date().getTime();
+
+      return that.isAlive()
+        .then(function(alive) {
+          that.alive = alive;
+          if (!alive) {
+            console.error('[ES] [http] Could not start [{0}]: node unreachable'.format(that.server));
+            return false;
+          }
+          console.debug('[ES] [http] Started in '+(new Date().getTime()-now)+'ms');
+          that.api.node.raise.start();
+          return true;
+        });
+    };
+
+    that.stop = function() {
+      console.debug('[ES] [http] Stopped');
+      that.alive = false;
+      that.cleanCache();
+      that.api.node.raise.stop();
+    };
+
+    that.restart = function() {
+      that.stop();
+      return $timeout(function() {
+        that.start();
+      }, 200);
+    };
 
     function parseTagsFromText(value, prefix) {
       prefix = prefix || '#';
@@ -91,12 +200,8 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces
       });
     }
 
-    function postRecord(host, node, path) {
-      var that = this;
-      that.raw = that.raw || {};
-      that.raw.post = that.raw.post || {};
-      that.raw.post[path] = csHttp.post(host, node, path);
-
+    function postRecord(path) {
+      var postRequest = that.post(path);
       return function(record, params) {
         if (!csWallet.isLogin()) {
           var deferred = $q.defer();
@@ -105,7 +210,7 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces
         }
 
         if (!record.time) {
-          record.time = getTimeNow();
+          record.time = that.date.now();
         }
         var keypair = $rootScope.walletData.keypair;
         var obj = {};
@@ -125,7 +230,7 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces
               .then(function(signature) {
                 obj.hash = hash;
                 obj.signature = signature;
-                return that.raw.post[path](obj, params)
+                return postRequest(obj, params)
                   .then(function (id){
                     return id;
                   });
@@ -134,11 +239,7 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces
       };
     }
 
-    function removeRecord(host, node, index, type) {
-      var that = this;
-      that.raw = that.raw || {};
-      that.raw.delete = csHttp.post(host, node, '/history/delete');
-
+    function removeRecord(index, type) {
       return function(id) {
         if (!csWallet.isLogin()) {
           var deferred = $q.defer();
@@ -152,7 +253,7 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces
           type: type,
           id: id,
           issuer: $rootScope.walletData.pubkey,
-          time: getTimeNow()
+          time: that.date.now()
         };
         var str = JSON.stringify(obj);
         return CryptoUtils.util.hash(str)
@@ -161,7 +262,7 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces
               .then(function(signature) {
                 obj.hash = hash;
                 obj.signature = signature;
-                return that.raw.delete(obj)
+                return that.post('/history/delete')(obj)
                   .then(function (id){
                     return id;
                   });
@@ -170,6 +271,8 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces
       };
     }
 
+    that.image = {};
+
     function imageFromAttachment(attachment) {
       if (!attachment || !attachment._content_type || !attachment._content || attachment._content.length === 0) {
         return null;
@@ -211,7 +314,7 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces
      * @param imageField
      * @returns {{}}
      */
-    function imageFromHit(host, port, hit, imageField) {
+    that.image.fromHit = function(hit, imageField) {
       if (!hit || !hit._source) return;
       var attachment =  hit._source[imageField];
       if (!attachment || !attachment._content_type || !attachment._content_type.startsWith("image/")) return;
@@ -225,7 +328,7 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces
         var extension = attachment._content_type.substr(6);
         var path = [hit._index, hit._type, hit._id, '_image', imageField].join('/');
         path = '/' + path + '.' + extension;
-        image.src = csHttp.getUrl(host, port, path);
+        image.src = that.getUrl(path);
       }
       if (attachment._title) {
         image.title = attachment._title;
@@ -234,7 +337,7 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces
         image.name = attachment._name;
       }
       return image;
-    }
+    };
 
     function login(host, node, keypair) {
       return $q(function(resolve, reject) {
@@ -290,18 +393,19 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces
       };
     }
 
-    that = {
-      get: get,
-      post: post,
-      getUrl : csHttp.getUrl,
+    that.api.registerEvent('node', 'start');
+    that.api.registerEvent('node', 'stop');
+
+    var exports = {
       getServer: csHttp.getServer,
-      ws: csHttp.ws,
+      node: {
+        summary: that.get('/node/summary')
+      },
       record: {
         post: postRecord,
         remove: removeRecord
       },
       image: {
-        fromHit : imageFromHit,
         fromAttachment: imageFromAttachment,
         toAttachment: imageToAttachment
       },
@@ -316,17 +420,23 @@ angular.module('cesium.es.http.services', ['ngResource', 'cesium.services', 'ces
         parseTags: parseTagsFromText,
         trustAsHtml: trustAsHtml
       },
-      date: {
-        now: getTimeNow
-      },
       constants: {
         regexp: regex
       }
     };
-    return that;
+    angular.merge(that, exports);
   }
 
-  var service = factory();
+  var host = csSettings.data.plugins && csSettings.data.plugins.es ? csSettings.data.plugins.es.host : null;
+  var port = host ? csSettings.data.plugins.es.port : null;
+  var wsPort = host ? csSettings.data.plugins.es.wsPort : port;
+
+  var service = new Factory(host, port, wsPort);
+
+  service.instance = function(host, port, wsPort) {
+    return new Factory(host, port, wsPort);
+  };
+
   return service;
 })
 ;
diff --git a/www/plugins/es/js/services/message-services.js b/www/plugins/es/js/services/message-services.js
index 819037935d54e7efec99691fd98e9ae8c1725bec..9a8afcf20042c20621b4d722edefe69a959fbd2f 100644
--- a/www/plugins/es/js/services/message-services.js
+++ b/www/plugins/es/js/services/message-services.js
@@ -10,10 +10,10 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', '
 
   })
 
-.factory('esMessage', function($q, $rootScope, csSettings, esHttp, CryptoUtils, esUser, csWallet) {
+.factory('esMessage', function($q, $rootScope, csSettings, esHttp, CryptoUtils, esUser, csWallet, Device) {
   'ngInject';
 
-  function factory(host, port) {
+  function Factory() {
 
     var
     listeners,
@@ -21,19 +21,13 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', '
     fields = {
       commons: ["issuer", "recipient", "title", "content", "time", "nonce", "read_signature"],
       notifications: ["issuer", "time", "hash", "read_signature"]
+    },
+    raw = {
+      postSearch: esHttp.post('/message/inbox/_search'),
+      getByTypeAndId : esHttp.get('/message/:type/:id'),
+      postReadById: esHttp.post('/message/inbox/:id/_read')
     };
 
-    function copy(otherNode) {
-      if (!!this.instance) {
-        var instance = this.instance;
-        angular.copy(otherNode, this);
-        this.instance = instance;
-      }
-      else {
-        angular.copy(otherNode, this);
-      }
-    }
-
     function onWalletInit(data) {
       data.messages = data.messages || {};
       data.messages.unreadCount = null;
@@ -56,12 +50,15 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', '
         return deferred.promise;
       }
 
+      console.debug('[ES] [message] Loading count...');
+      var now = new Date().getTime();
+
       // Count unread messages
       countUnreadMessages(data.pubkey)
         .then(function(unreadCount){
           data.messages = data.messages || {};
           data.messages.unreadCount = unreadCount;
-          console.debug('[ES] [message] Detecting ' + unreadCount + ' unread messages');
+          console.debug('[ES] [message] Loaded count (' + unreadCount + ') in '+(new Date().getTime()-now)+'ms');
           deferred.resolve(data);
         })
         .catch(function(err){
@@ -105,7 +102,7 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', '
         }
       };
 
-      return esHttp.post(host, port, '/message/inbox/_count')(request)
+      return esHttp.post('/message/inbox/_count')(request)
         .then(function(res) {
           return res.count;
         });
@@ -159,7 +156,7 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', '
           ])
           .then(function(cypherTexts){
             // Send message
-            return esHttp.record.post(host, port, boxPath)({
+            return esHttp.record.post(boxPath)({
               issuer: message.issuer,
               recipient: message.recipient,
               title: cypherTexts[0],
@@ -187,7 +184,7 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', '
         _source: fields.notifications
       };
 
-      return esHttp.post(host, port, '/message/inbox/_search')(request)
+      return raw.postSearch(request)
         .then(function(res) {
           if (!res || !res.hits || !res.hits.total) {
             return [];
@@ -232,7 +229,7 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', '
         request.query = {bool: {filter: {term: {issuer: csWallet.data.pubkey}}}};
       }
 
-      return esHttp.post(host, port, '/message/:type/_search')(request, {type: options.type})
+      return esHttp.post('/message/:type/_search')(request, {type: options.type})
         .then(function(res) {
           if (!res || !res.hits || !res.hits.total) {
             return [];
@@ -286,7 +283,7 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', '
     function getAndDecrypt(params, keypair) {
       params.type = params.type || 'inbox';
       var avatarField = (params.type == 'inbox') ? 'issuer' : 'recipient';
-      return esHttp.get(host, port, '/message/:type/:id')(params)
+      return raw.getByTypeAndId(params)
         .then(function(hit) {
           if (!hit.found) {
             return;
@@ -363,7 +360,7 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', '
 
     function removeMessage(id, type) {
       type = type || 'inbox';
-      return esHttp.record.remove(host, port, 'message', type)(id)
+      return esHttp.record.remove('message', type)(id)
         .then(function(res) {
           // update message count
           if (type == 'inbox') {
@@ -384,7 +381,7 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', '
 
           // Remove each messages
           return $q.all(res.reduce(function(res, msg) {
-            return res.concat(esHttp.record.remove(host, port, 'message', type)(msg.id));
+            return res.concat(esHttp.record.remove('message', type)(msg.id));
           }, []));
         })
         .then(function() {
@@ -411,7 +408,7 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', '
 
         // Send read request
         .then(function(signature){
-          return esHttp.post(host, port, '/message/inbox/:id/_read')(signature, {id:message.id});
+          return raw.postReadById(signature, {id:message.id});
         })
 
         // Update message count
@@ -441,7 +438,7 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', '
               CryptoUtils.sign(message.hash, csWallet.data.keypair)
               // then send read request
               .then(function(signature){
-                return esHttp.post(host, port, '/message/inbox/:id/_read')(signature, {id:message.id});
+                return raw.postReadById(signature, {id:message.id});
               }));
           }, []));
         })
@@ -453,7 +450,7 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', '
     }
 
     function removeListeners() {
-      console.debug("[ES] Disable message extension");
+      console.debug("[ES] [message] Disable");
 
       _.forEach(listeners, function(remove){
         remove();
@@ -472,39 +469,28 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', '
       ];
     }
 
-    function isEnable() {
-      return csSettings.data.plugins &&
-        csSettings.data.plugins.es &&
-        host && csSettings.data.plugins.es.enable;
-    }
-
     function refreshListeners() {
-      var enable = isEnable();
+      var enable = esHttp.alive;
       if (!enable && listeners && listeners.length > 0) {
         removeListeners();
       }
       else if (enable && (!listeners || listeners.length === 0)) {
         addListeners();
+        if (csWallet.isLogin() && !csWallet.data.messages) {
+          onWalletLogin(csWallet.data);
+        }
       }
     }
 
-    // Listen for settings changed
-    csSettings.api.data.on.changed($rootScope, function(){
+    // Default action
+    Device.ready().then(function() {
       refreshListeners();
-      if (isEnable() && !csWallet.data.messages) {
-        onWalletLogin(csWallet.data);
-      }
+      esHttp.api.node.on.start($rootScope, refreshListeners, this);
+      esHttp.api.node.on.stop($rootScope, refreshListeners, this);
     });
 
-    // Default action
-    refreshListeners();
-
     return {
-      copy: copy,
-      node: {
-        server: esHttp.getServer(host, port)
-      },
-      search: esHttp.post(host, port, '/message/inbox/_search'),
+      search: raw.postSearch,
       notifications: {
         load: loadMessageNotifications
       },
@@ -521,12 +507,6 @@ angular.module('cesium.es.message.services', ['ngResource', 'cesium.services', '
     };
   }
 
-  var host = csSettings.data.plugins && csSettings.data.plugins.es ? csSettings.data.plugins.es.host : null;
-  var port = host ? csSettings.data.plugins.es.port : null;
-
-  var service = factory(host, port);
-
-  service.instance = factory;
-  return service;
+  return Factory();
 })
 ;
diff --git a/www/plugins/es/js/services/notification-services.js b/www/plugins/es/js/services/notification-services.js
index 146ba7c823e2d44e4985581b994b8ad1b0833945..9d3469a6af53c0f9db2e7ae10194fa99c6f552bd 100644
--- a/www/plugins/es/js/services/notification-services.js
+++ b/www/plugins/es/js/services/notification-services.js
@@ -13,7 +13,7 @@ angular.module('cesium.es.notification.services', ['cesium.services', 'cesium.es
 .factory('esNotification', function($rootScope, $q, $timeout, esHttp, csConfig, csSettings, csWallet, csWot, UIUtils, BMA, CryptoUtils, Device, Api, esUser) {
   'ngInject';
 
-  function factory(id, host, port, wsPort) {
+  function Factory() {
 
     var listeners,
       defaultLoadSize = 20,
@@ -24,9 +24,20 @@ angular.module('cesium.es.notification.services', ['cesium.services', 'cesium.es
       fields = {
         commons: ["type", "code", "params", "reference", "recipient", "time", "hash", "read_signature"]
       },
-      api = new Api(this, 'esNotification-' + id)
+      that = this,
+      api = new Api(this, 'esNotification')
     ;
 
+    that.raw = {
+      postCount: esHttp.post('/user/event/_count'),
+      postSearch: esHttp.post('/user/event/_search'),
+      postReadById: esHttp.post('/user/event/:id/_read'),
+      ws: {
+        getUserEvent: esHttp.ws('/ws/event/user/:pubkey/:locale'),
+        getChanges: esHttp.ws('/ws/_changes')
+      }
+    };
+
     // Create the filter query
     function createFilterQuery(pubkey, options) {
       options = options || {};
@@ -83,7 +94,7 @@ angular.module('cesium.es.notification.services', ['cesium.services', 'cesium.es
       };
       // Filter unread only
       request.query.bool.must.push({missing: { field : "read_signature" }});
-      return esHttp.post(host, port, '/user/event/_count')(request)
+      return that.raw.postCount(request)
         .then(function(res) {
           return res.count;
         });
@@ -104,7 +115,7 @@ angular.module('cesium.es.notification.services', ['cesium.services', 'cesium.es
         _source: fields.commons
       };
 
-      return esHttp.post(host, port, '/user/event/_search')(request)
+      return that.raw.postSearch(request)
         .then(function(res) {
           if (!res.hits || !res.hits.total) return [];
           var notifications = res.hits.hits.reduce(function(res, hit) {
@@ -117,34 +128,29 @@ angular.module('cesium.es.notification.services', ['cesium.services', 'cesium.es
         });
     }
 
-    function listenNewNotification(data) {
-      esHttp.ws((wsPort == 443 ? 'wss' : 'ws') +'://'+esHttp.getServer(host, wsPort)+'/ws/event/user/:pubkey/:locale')
-        .on(function(event) {
-            $rootScope.$apply(function() {
-              var notification = new Notification(event, markNotificationAsRead);
-              esUser.profile.fillAvatars([notification])
-                .then(function() {
-                  var isMessage = _.contains(constants.MESSAGE_CODES, event.code);
-                  var isGroup = _.contains(constants.GROUP_CODES, event.code);
-                  notification.isMessage = isMessage;
-                  if (isMessage) {
-                    data.messages = data.messages || {};
-                    data.messages.unreadCount++;
-                  }
-                  else if (isGroup) {
-                    data.groups = data.groups || {};
-                    data.groups.unreadCount++;
-                  }
-                  else {
-                    data.notifications = data.notifications || {};
-                    data.notifications.unreadCount++;
-                  }
-                  api.data.raise.new(notification);
-                });
-            });
-          },
-          {pubkey: data.pubkey, locale: csSettings.data.locale.id}
-        );
+    function onNewNotification(event, data) {
+      data = data || (csWallet.isLogin() ? csWallet.data : undefined);
+      if (!data) return;
+      var notification = new Notification(event, markNotificationAsRead);
+      return esUser.profile.fillAvatars([notification])
+        .then(function() {
+          var isMessage = _.contains(constants.MESSAGE_CODES, event.code);
+          var isGroup = _.contains(constants.GROUP_CODES, event.code);
+          notification.isMessage = isMessage;
+          if (isMessage) {
+            data.messages = data.messages || {};
+            data.messages.unreadCount++;
+          }
+          else if (isGroup) {
+            data.groups = data.groups || {};
+            data.groups.unreadCount++;
+          }
+          else {
+            data.notifications = data.notifications || {};
+            data.notifications.unreadCount++;
+          }
+          api.data.raise.new(notification);
+        });
     }
 
     // Mark a notification as read
@@ -153,7 +159,7 @@ angular.module('cesium.es.notification.services', ['cesium.services', 'cesium.es
       notification.read = true;
       CryptoUtils.sign(notification.hash, csWallet.data.keypair)
         .then(function(signature){
-          return esHttp.post(host, port, '/user/event/:id/_read')(signature, {id:notification.id});
+          return postReadById(signature, {id:notification.id});
         })
         .catch(function(err) {
           console.error('Error while trying to mark event as read:' + (err.message ? err.message : err));
@@ -163,6 +169,8 @@ angular.module('cesium.es.notification.services', ['cesium.services', 'cesium.es
     function onWalletReset(data) {
       data.notifications = data.notifications || {};
       data.notifications.unreadCount = null;
+      // Stop listening notification
+      that.raw.ws.getUserEvent().close();
     }
 
     function onWalletLogin(data, deferred) {
@@ -172,7 +180,8 @@ angular.module('cesium.es.notification.services', ['cesium.services', 'cesium.es
         return deferred.promise;
       }
 
-      console.debug('[ES] [notification] Loading count from ES node...');
+      console.debug('[ES] [notification] Loading count...');
+      var now = new Date().getTime();
 
       // Load unread notifications count
       loadUnreadNotificationsCount(
@@ -183,7 +192,7 @@ angular.module('cesium.es.notification.services', ['cesium.services', 'cesium.es
         .then(function(unreadCount) {
           data.notifications = data.notifications || {};
           data.notifications.unreadCount = unreadCount;
-          console.debug('[ES] [notification] Successfully load count from ES node');
+          console.debug('[ES] [notification] Loaded count (' + unreadCount + ') in '+(new Date().getTime()-now)+'ms');
           deferred.resolve(data);
         })
         .catch(function(err){
@@ -192,7 +201,22 @@ angular.module('cesium.es.notification.services', ['cesium.services', 'cesium.es
 
         // Listen new events
         .then(function(){
-          listenNewNotification(data);
+          console.debug('[ES] [notification] Starting listen user event...');
+          var userEventWs = that.raw.ws.getUserEvent();
+          listeners.push(userEventWs.close);
+          return userEventWs.on(
+              function(){
+                $rootScope.$apply(onNewNotification);
+              },
+              {pubkey: data.pubkey, locale: csSettings.data.locale.id}
+            )
+            .catch(function(err) {
+              console.error('[ES] [notification] Unable to listen user event');
+
+              // TODO : send a event to csHttp instead ?
+              // And display such connectivity errors in UI
+              UIUtils.alert.error('ACCOUNT.ERROR.WS_CONNECTION_FAILED');
+            });
         });
 
       return deferred.promise;
@@ -210,7 +234,7 @@ angular.module('cesium.es.notification.services', ['cesium.services', 'cesium.es
     function addListeners() {
       console.debug("[ES] [notification] Enable");
 
-      // Extend csWallet.loadData() and csWot.loadData()
+      // Listen some events
       listeners = [
         csWallet.api.data.on.login($rootScope, onWalletLogin, this),
         csWallet.api.data.on.init($rootScope, onWalletReset, this),
@@ -218,50 +242,41 @@ angular.module('cesium.es.notification.services', ['cesium.services', 'cesium.es
       ];
     }
 
-    function isEnable() {
-      return csSettings.data.plugins &&
-         csSettings.data.plugins.es &&
-         host && csSettings.data.plugins.es.enable;
-    }
-
     function refreshListeners() {
-      var enable = isEnable();
+      var enable = esHttp.alive;
       if (!enable && listeners && listeners.length > 0) {
         removeListeners();
       }
       else if (enable && (!listeners || listeners.length === 0)) {
         addListeners();
+        if (csWallet.isLogin()) {
+          return onWalletLogin(csWallet.data);
+        }
       }
     }
 
-    // Default action
-    refreshListeners();
-
     // Register extension points
     api.registerEvent('data', 'new');
 
+    // Default actions
+    Device.ready().then(function() {
+      esHttp.api.node.on.start($rootScope, refreshListeners, this);
+      esHttp.api.node.on.stop($rootScope, refreshListeners, this);
+      return refreshListeners();
+    });
+
     return {
       load: loadNotifications,
       unreadCount: loadUnreadNotificationsCount,
       api: api,
       websocket: {
-        event: function() {
-          return esHttp.ws((wsPort == 443 ? 'wss' : 'ws') +'://'+esHttp.getServer(host, wsPort)+'/ws/event/user/:pubkey/:locale');
-        },
-        change: function() {
-          return esHttp.ws((wsPort == 443 ? 'wss' : 'ws') +'://'+esHttp.getServer(host, wsPort)+'/ws/_changes');
-        }
+        event: that.raw.ws.getUserEvent,
+        change: that.raw.ws.getChanges
       },
       constants: constants
     };
   }
 
-  var host = csSettings.data.plugins && csSettings.data.plugins.es ? csSettings.data.plugins.es.host : null;
-  var port = host ? csSettings.data.plugins.es.port : null;
-  var wsPort = host && csSettings.data.plugins.es.wsPort ? csSettings.data.plugins.es.wsPort : port;
-
-  var service = factory('default', host, port, wsPort);
-  service.instance = factory;
-  return service;
+  return new Factory();
 })
 ;
diff --git a/www/plugins/es/js/services/settings-services.js b/www/plugins/es/js/services/settings-services.js
new file mode 100644
index 0000000000000000000000000000000000000000..b835fa1e2275ca031e1bf0931e19574348f52fa8
--- /dev/null
+++ b/www/plugins/es/js/services/settings-services.js
@@ -0,0 +1,326 @@
+angular.module('cesium.es.settings.services', ['cesium.services', 'cesium.es.http.services'])
+.config(function(PluginServiceProvider, csConfig) {
+    'ngInject';
+
+    var enable = csConfig.plugins && csConfig.plugins.es;
+    if (enable) {
+      // Will force to load this service
+      PluginServiceProvider.registerEagerLoadingService('esSettings');
+    }
+
+  })
+
+.factory('esSettings', function($rootScope, $q, $timeout, esHttp,
+                            csConfig, csSettings, CryptoUtils, Device, UIUtils, csWallet) {
+  'ngInject';
+
+  function Factory() {
+
+    var
+      SETTINGS_SAVE_SPEC = {
+        includes: ['locale', 'showUDHistory', 'useRelative', 'useLocalStorage', 'expertMode'],
+        excludes: ['time'],
+        plugins: {
+          es: {
+            excludes: ['enable', 'host', 'port', 'wsPort']
+          }
+        },
+        helptip: {
+          excludes: ['installDocUrl']
+        }
+      },
+      that = this,
+      previousRemoteData,
+      listeners,
+      ignoreSettingsChanged = false
+    ;
+
+    that.get = esHttp.get('/user/settings/:id');
+    that.add = esHttp.record.post('/user/settings');
+    that.update = esHttp.record.post('/user/settings/:id/_update');
+
+    that.isEnable = function() {
+      return csSettings.data.plugins &&
+        csSettings.data.plugins.es &&
+        csSettings.data.plugins.es.enable &&
+        !!csSettings.data.plugins.es.host;
+    };
+
+    function copyUsingSpec(data, copySpec) {
+      var result = {};
+
+      // Add implicit includes
+      if (copySpec.includes) {
+        _.forEach(_.keys(copySpec), function(key) {
+          if (key != "includes" && key != "excludes") {
+            copySpec.includes.push(key);
+          }
+        });
+      }
+
+      _.forEach(_.keys(data), function(key) {
+        if ((!copySpec.includes || _.contains(copySpec.includes, key)) &&
+          (!copySpec.excludes || !_.contains(copySpec.excludes, key))) {
+          if (data[key] && (typeof data[key] == 'object') &&
+            copySpec[key] && (typeof copySpec[key] == 'object')) {
+            result[key] = copyUsingSpec(data[key], copySpec[key]);
+          }
+          else {
+            result[key] = data[key];
+          }
+        }
+      });
+      return result;
+    }
+
+    // Load settings
+    function loadSettings(pubkey, keypair) {
+      var now = new Date().getTime();
+      return $q.all([
+          CryptoUtils.box.keypair.fromSignKeypair(keypair),
+          that.get({id: pubkey})
+            .catch(function(err){
+              if (err && err.ucode && err.ucode == 404) {
+                return null; // not found
+              }
+              else {
+                throw err;
+              }
+            })])
+        .then(function(res) {
+          var boxKeypair = res[0];
+          res = res[1];
+          if (!res || !res._source) {
+            return;
+          }
+          var record = res._source;
+          // Do not apply if same version
+          if (record.time === csSettings.data.time) {
+            console.debug('[ES] [settings] Loaded user settings in '+ (new Date().getTime()-now) +'ms (no update need)');
+            return;
+          }
+          var nonce = CryptoUtils.util.decode_base58(record.nonce);
+          // Decrypt settings content
+          return CryptoUtils.box.open(record.content, nonce, boxKeypair.boxPk, boxKeypair.boxSk)
+            .then(function(json) {
+              var settings = JSON.parse(json || '{}');
+              settings.time = record.time;
+              console.debug('[ES] [settings] Loaded user settings in '+ (new Date().getTime()-now) +'ms');
+              return settings;
+            })
+            // if error: skip stored content
+            .catch(function(err){
+              console.error('[ES] [settings] Could not read stored settings: ' + (err && err.message || 'decryption error'));
+              // make sure to remove time, to be able to save it again
+              delete csSettings.data.time;
+              return null;
+            });
+        });
+    }
+
+    function onWalletLogin(data, deferred) {
+      deferred = deferred || $q.defer();
+      if (!data || !data.pubkey || !data.keypair) {
+        deferred.resolve();
+        return deferred.promise;
+      }
+
+      // Waiting to load crypto libs
+      if (!CryptoUtils.isLoaded()) {
+        console.debug('[ES] [settings] Waiting crypto lib loading...');
+        return $timeout(function() {
+          return onWalletLogin(data, deferred);
+        }, 50);
+      }
+
+      console.debug('[ES] [settings] Loading user settings...');
+
+      // Load settings
+      loadSettings(data.pubkey, data.keypair)
+        .then(function(settings) {
+          if (!settings) return; // not found or up to date
+          angular.merge(csSettings.data, settings);
+
+          // Remember for comparison
+          previousRemoteData = settings;
+
+          console.debug('[ES] [settings] Successfully load settings from ES');
+          return storeSettingsLocally();
+        })
+      .then(function() {
+        deferred.resolve(data);
+      })
+      .catch(function(err){
+        deferred.reject(err);
+      });
+
+      return deferred.promise;
+    }
+
+    // Listen for settings changed
+    function onSettingsChanged(data) {
+      // avoid recursive call, because storeSettingsLocally() could emit event again
+      if (ignoreSettingsChanged) return;
+
+      var wasEnable = listeners && listeners.length > 0;
+
+      refreshListeners();
+
+      var isEnable = that.isEnable();
+      if (csWallet.isLogin()) {
+        if (!wasEnable && isEnable) {
+
+          onWalletLogin(csWallet.data);
+        }
+        else {
+          storeSettingsRemotely(data);
+        }
+      }
+    }
+
+    function storeSettingsLocally() {
+      if (ignoreSettingsChanged) return $q.when();
+      ignoreSettingsChanged = true;
+      return csSettings.store()
+        .then(function(){
+          ignoreSettingsChanged = false;
+        })
+        .catch(function(err) {
+          ignoreSettingsChanged = false;
+          throw err;
+        });
+    }
+
+    function storeSettingsRemotely(data) {
+      if (!csWallet.isLogin()) return $q.when();
+
+      var filteredData = copyUsingSpec(data, SETTINGS_SAVE_SPEC);
+      if (previousRemoteData && angular.equals(filteredData, previousRemoteData)) {
+        return $q.when();
+      }
+
+      // Waiting to load crypto libs
+      if (!CryptoUtils.isLoaded()) {
+        console.debug('[ES] [settings] Waiting crypto lib loading...');
+        return $timeout(function() {
+          return storeSettingsRemotely();
+        }, 50);
+      }
+
+      var time = esHttp.date.now();
+      console.debug('[ES] [settings] Saving user settings... at time ' + time);
+
+      return $q.all([
+          CryptoUtils.box.keypair.fromSignKeypair(csWallet.data.keypair),
+          CryptoUtils.util.random_nonce()
+        ])
+        .then(function(res) {
+          var boxKeypair = res[0];
+          var nonce = res[1];
+
+          var record = {
+            issuer: csWallet.data.pubkey,
+            nonce: CryptoUtils.util.encode_base58(nonce),
+            time: time
+          };
+
+          var json = JSON.stringify(filteredData);
+
+          return CryptoUtils.box.pack(json, nonce, boxKeypair.boxPk, boxKeypair.boxSk)
+            .then(function(cypherText) {
+              record.content = cypherText;
+              // create or update
+              return !data.time ?
+                that.add(record) :
+                that.update(record, {id: record.issuer});
+            });
+        })
+        .then(function() {
+          // Update settings version, then store (on local store only)
+          csSettings.data.time = time;
+          previousRemoteData = filteredData;
+          console.debug('[ES] [settings] Saved user settings in ' + (esHttp.date.now() - time) + 'ms');
+          return storeSettingsLocally();
+        })
+        .catch(function(err) {
+          console.error(err);
+          throw err;
+        })
+      ;
+    }
+
+    function removeListeners() {
+      console.debug("[ES] [settings] Disable");
+      _.forEach(listeners, function(remove){
+        remove();
+      });
+      listeners = [];
+    }
+
+    function addListeners() {
+      console.debug("[ES] [settings] Enable");
+
+      // Extend csWallet.login()
+      listeners = [
+        csWallet.api.data.on.login($rootScope, onWalletLogin, this),
+
+      ];
+    }
+
+    function refreshListeners() {
+      var enable = that.isEnable();
+      if (!enable && listeners && listeners.length > 0) {
+        removeListeners();
+        return esHttp.stop();
+      }
+      else if (enable && (!listeners || listeners.length === 0)) {
+        return esHttp.start()
+          .then(function(started) {
+            if (!started) {
+              // TODO : alert user ?
+              console.error('ES node could not be started !!');
+            }
+            else {
+              addListeners();
+              if (csWallet.isLogin()) {
+                return onWalletLogin(csWallet.data);
+              }
+            }
+          });
+      }
+    }
+
+    Device.ready().then(function() {
+
+      csSettings.api.data.on.changed($rootScope, onSettingsChanged, this);
+      esHttp.api.node.on.stop($rootScope, function() {
+        previousRemoteData = null;
+      }, this);
+      return refreshListeners();
+    })
+
+    .then(function() {
+      // Ask (once) user to enable ES plugin
+      if (csConfig.plugins && csConfig.plugins.es && csConfig.plugins.es.askEnable && // if config ask enable
+        csSettings.data.plugins.es && !csSettings.data.plugins.es.enable && // AND user settings has disable plugin
+        csSettings.data.plugins.es.askEnable // AND user has not yet answer 'NO'
+      ) {
+        return UIUtils.alert.confirm('ES_SETTINGS.CONFIRM.ASK_ENABLE', 'ES_SETTINGS.CONFIRM.ASK_ENABLE_TITLE',
+          {
+            cancelText: 'COMMON.BTN_NO',
+            okText: 'COMMON.BTN_YES'
+          })
+          .then(function (confirm) {
+            if (confirm) {
+              csSettings.data.plugins.es.enable = true;
+            }
+            csSettings.data.plugins.es.askEnable = false;
+            return csSettings.store();
+          });
+      }
+    });
+  }
+
+  return new Factory();
+})
+;
diff --git a/www/plugins/es/js/services/user-services.js b/www/plugins/es/js/services/user-services.js
index 8721bc94362f00b08bc26eb8d241c0e616de01a7..fed979b3a7d0756b27257bd2f6725780e7a6f390 100644
--- a/www/plugins/es/js/services/user-services.js
+++ b/www/plugins/es/js/services/user-services.js
@@ -11,91 +11,30 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se
   })
 
 .factory('esUser', function($rootScope, $q, $timeout, esHttp, $state, $sce, $sanitize,
-                            csConfig, csSettings, CryptoUtils, Device, UIUtils, csWallet, csWot, BMA) {
+                            esSettings, CryptoUtils, UIUtils, csWallet, csWot, BMA, Device) {
   'ngInject';
 
-  function factory(id, host, port, wsPort) {
+  function Factory() {
 
     var
+      that = this,
       CONSTANTS = {
         contentTypeImagePrefix: "image/",
         ES_USER_API_ENDPOINT: "ES_USER_API( ([a-z_][a-z0-9-_.]*))?( ([0-9.]+))?( ([0-9a-f:]+))?( ([0-9]+))"
       },
       REGEX = {
         ES_USER_API_ENDPOINT: exact(CONSTANTS.ES_USER_API_ENDPOINT)
-      },
-      SETTINGS_SAVE_SPEC = {
-        includes: ['locale', 'showUDHistory', 'useRelative', 'useLocalStorage', 'expertMode'],
-        excludes: ['time'],
-        plugins: {
-          es: {
-            excludes: ['enable', 'host', 'port', 'wsPort']
-          }
-        },
-        helptip: {
-          excludes: ['installDocUrl']
-        }
       };
 
     var listeners,
-      restoringSettings = false,
-      getRequestFields = esHttp.get(host, port, '/user/profile/:id?&_source_exclude=avatar._content&_source=:fields'),
-      getRequest = esHttp.get(host, port, '/user/profile/:id?&_source_exclude=avatar._content')
+      getRequestFields = esHttp.get('/user/profile/:id?&_source_exclude=avatar._content&_source=:fields'),
+      getRequest = esHttp.get('/user/profile/:id?&_source_exclude=avatar._content')
     ;
 
     function exact(regexpContent) {
       return new RegExp("^" + regexpContent + "$");
     }
 
-    function copy(otherNode) {
-      removeListeners();
-      if (!!this.instance) {
-        var instance = this.instance;
-        angular.copy(otherNode, this);
-        this.instance = instance;
-      }
-      else {
-        angular.copy(otherNode, this);
-      }
-      addListeners();
-    }
-
-    function copyUsingSpec(data, copySpec) {
-      var result = {};
-
-      // Add implicit includes
-      if (copySpec.includes) {
-        _.forEach(_.keys(copySpec), function(key) {
-          if (key != "includes" && key != "excludes") {
-            copySpec.includes.push(key);
-          }
-        });
-      }
-
-      _.forEach(_.keys(data), function(key) {
-        if ((!copySpec.includes || _.contains(copySpec.includes, key)) &&
-          (!copySpec.excludes || !_.contains(copySpec.excludes, key))) {
-          if (data[key] && (typeof data[key] == 'object') &&
-            copySpec[key] && (typeof copySpec[key] == 'object')) {
-            result[key] = copyUsingSpec(data[key], copySpec[key]);
-          }
-          else {
-            result[key] = data[key];
-          }
-        }
-      });
-      return result;
-    }
-
-    function readAvatarFromSource(source) {
-      var extension = source.avatar && source.avatar._content_type && source.avatar._content_type.startsWith(CONSTANTS.contentTypeImagePrefix) ?
-        source.avatar._content_type.substr(CONSTANTS.contentTypeImagePrefix.length) : null;
-      if (extension) {
-        return esHttp.getUrl(host, port, '/user/profile/' + pubkey + '/_image/avatar.' + extension);
-      }
-      return null;
-    }
-
     function loadProfileAvatarAndName(pubkey) {
       return getRequestFields({id: pubkey, fields: 'title,avatar._content_type'})
         .then(function(res) {
@@ -104,7 +43,7 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se
             // name
             profile = {name: res._source.title};
             // avatar
-            profile.avatar = esHttp.image.fromHit(host, port, res, 'avatar');
+            profile.avatar = esHttp.image.fromHit(res, 'avatar');
           }
           return profile;
         })
@@ -131,7 +70,7 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se
             profile.source = res._source;
 
             // avatar
-            profile.avatar = esHttp.image.fromHit(host, port, res, 'avatar');
+            profile.avatar = esHttp.image.fromHit(res, 'avatar');
             delete profile.source.avatar; // not need anymore
 
             profile.source.description = esHttp.util.trustAsHtml(profile.source.description);
@@ -154,47 +93,6 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se
       return onWotSearch(null, datas, pubkeyAtributeName);
     }
 
-    // Load settings
-    function loadSettings(pubkey, keypair) {
-      return $q.all([
-          CryptoUtils.box.keypair.fromSignKeypair(keypair),
-          esHttp.get(host, port, '/user/settings/:id')({id: pubkey})
-            .catch(function(err){
-              if (err && err.ucode && err.ucode == 404) {
-                return null; // not found
-              }
-              else {
-                throw err;
-              }
-            })])
-        .then(function(res) {
-          var boxKeypair = res[0];
-          res = res[1];
-          if (!res || !res._source) {
-            return;
-          }
-          var record = res._source;
-          // Do not apply if same version
-          if (record.time === csSettings.data.time) {
-            console.debug('[ES] [user] Local settings already up to date');
-            return;
-          }
-          var nonce = CryptoUtils.util.decode_base58(record.nonce);
-          // Decrypt settings content
-          return CryptoUtils.box.open(record.content, nonce, boxKeypair.boxPk, boxKeypair.boxSk)
-            .then(function(json) {
-              var settings = JSON.parse(json || '{}');
-              settings.time = record.time;
-              return settings;
-            })
-            // if error: skip stored content
-            .catch(function(err){
-              console.error('[ES] [user] Could not read stored settings: ' + (err && err.message || 'decryption error'));
-              return null;
-            });
-        });
-    }
-
     function onWalletReset(data) {
       data.avatar = null;
       data.avatarStyle = null;
@@ -212,45 +110,30 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se
       // Waiting to load crypto libs
       if (!CryptoUtils.isLoaded()) {
         console.debug('[ES] [user] Waiting crypto lib loading...');
-        //throw 'stop';
-        $timeout(function() {
-          onWalletLogin(data, deferred);
+        return $timeout(function() {
+          return onWalletLogin(data, deferred);
         }, 50);
-        return;
       }
 
-      console.debug('[ES] [user] Loading user data from ES node...');
-      $q.all([
-        // Load settings
-        loadSettings(data.pubkey, data.keypair)
-          .then(function(settings) {
-            if (!settings) { // not found
-              // make sure to remove save timestamp
-              delete csSettings.data.time;
-              return;
-            }
-            angular.merge(csSettings.data, settings);
-            restoringSettings = true;
-            csSettings.store();
-          }),
+      console.debug('[ES] [user] Loading user avatar+name...');
+      var now = new Date().getTime();
 
-        // Load profile avatar and name
-        loadProfileAvatarAndName(data.pubkey)
-          .then(function(profile) {
-            if (profile) {
-              data.name = profile.name;
-              data.avatarStyle = profile.avatarStyle;
-              data.avatar = profile.avatar;
-            }
-          })
-      ])
-      .then(function() {
-        console.debug('[ES] [user] Successfully loaded user data from ES node');
-        deferred.resolve(data);
-      })
-      .catch(function(err){
-        deferred.reject(err);
-      });
+      loadProfileAvatarAndName(data.pubkey)
+        .then(function(profile) {
+          if (profile) {
+            data.name = profile.name;
+            data.avatarStyle = profile.avatarStyle;
+            data.avatar = profile.avatar;
+            console.debug('[ES] [user] Loaded user avatar+name in '+ (new Date().getTime()-now) +'ms');
+          }
+          else {
+            console.debug('[ES] [user] No user avatar+name found');
+          }
+          deferred.resolve(data);
+        })
+        .catch(function(err){
+          deferred.reject(err);
+        });
 
       return deferred.promise;
     }
@@ -262,6 +145,9 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se
         data.events.push({type:'info',message: 'ACCOUNT.EVENT.MEMBER_WITHOUT_PROFILE'});
       }
 
+      console.debug('[ES] [user] Loading full user profile...');
+      var now = new Date().getTime();
+
       // Load full profile
       loadProfile(data.pubkey)
         .then(function(profile) {
@@ -280,7 +166,7 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se
                 return social.url;
               });
             }
-
+            console.debug('[ES] [user] Loaded full user profile in '+ (new Date().getTime()-now) +'ms');
           }
           deferred.resolve();
         });
@@ -288,6 +174,16 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se
       return deferred.promise;
     }
 
+    function onWalletLoadTx(tx, deferred) {
+      fillAvatars((tx.history || []).concat(tx.pendings||[]), 'pubkey')
+        .then(function() {
+          deferred.resolve();
+        })
+        .catch(function(err) {
+          console.error(err);
+          deferred.resolve(); // silent
+        });
+    }
 
     function onWotSearch(text, datas, pubkeyAtributeName, deferred) {
       deferred = deferred || $q.defer();
@@ -380,7 +276,7 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se
           .then(function(res){
             uidsByPubkey = res;
           }),
-        esHttp.post(host, port, '/user/profile/_search')(request)
+        esHttp.post('/user/profile/_search')(request)
           .then(function(res){
             hits = res.hits;
           })
@@ -395,7 +291,7 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se
               values=[value];
               datas.push(value);
             }
-            var avatar = esHttp.image.fromHit(host, port, hit, 'avatar');
+            var avatar = esHttp.image.fromHit(hit, 'avatar');
             _.forEach(values, function(data) {
               // name (basic or highlighted)
               data.name = hit._source.title;
@@ -488,73 +384,6 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se
       return deferred.promise;
     }
 
-    function onSettingsChanged(data) {
-      if (!csWallet.isLogin()) return;
-
-      // Waiting to load crypto libs
-      if (!CryptoUtils.isLoaded()) {
-        console.debug('[ES] [user] Waiting crypto lib loading...');
-        $timeout(function() {
-          onSettingsChanged(data);
-        }, 200);
-        return;
-      }
-
-      console.debug('[ES] [user] Saving user settings to ES...');
-
-      return $q.all([
-          CryptoUtils.box.keypair.fromSignKeypair(csWallet.data.keypair),
-          CryptoUtils.util.random_nonce()
-        ])
-        .then(function(res) {
-          var boxKeypair = res[0];
-          var nonce = res[1];
-          var record = {
-            issuer: csWallet.data.pubkey,
-            nonce: CryptoUtils.util.encode_base58(nonce),
-            time: esHttp.date.now()
-          };
-
-          var filteredData = copyUsingSpec(data, SETTINGS_SAVE_SPEC);
-
-          var json = JSON.stringify(filteredData);
-
-          return CryptoUtils.box.pack(json, nonce, boxKeypair.boxPk, boxKeypair.boxSk)
-            .then(function(cypherText) {
-              record.content = cypherText;
-              return !data.time ?
-                // create
-                esHttp.record.post(host, port, '/user/settings')(record) :
-                // or update
-                esHttp.record.post(host, port, '/user/settings/:pubkey/_update')(record, {pubkey: record.issuer});
-            })
-            .then(function() {
-              // Change settings version
-              csSettings.data.time = record.time;
-              restoringSettings = true;
-              csSettings.store();
-              console.debug('[ES] [user] User settings saved in ES');
-            });
-        })
-        .catch(function(err) {
-          console.error(err);
-          throw err;
-        })
-      ;
-    }
-
-    function onWalletLoadTx(tx, deferred) {
-      fillAvatars((tx.history || []).concat(tx.pendings||[]), 'pubkey')
-        .then(function() {
-          deferred.resolve();
-        })
-        .catch(function(err) {
-          console.error(err);
-          deferred.resolve(); // silent
-        });
-    }
-
-
     function removeListeners() {
       console.debug("[ES] [user] Disable");
       _.forEach(listeners, function(remove){
@@ -578,19 +407,19 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se
       ];
     }
 
-    function isEnable() {
-      return csSettings.data.plugins &&
-         csSettings.data.plugins.es &&
-         host && csSettings.data.plugins.es.enable;
-    }
-
     function refreshListeners() {
-      var enable = isEnable();
+      var enable = esHttp.alive;
       if (!enable && listeners && listeners.length > 0) {
         removeListeners();
+        if (csWallet.isLogin()) {
+          return onWalletReset(csWallet.data);
+        }
       }
       else if (enable && (!listeners || listeners.length === 0)) {
         addListeners();
+        if (csWallet.isLogin()) {
+          return onWalletLogin(csWallet.data);
+        }
       }
     }
 
@@ -605,86 +434,46 @@ angular.module('cesium.es.user.services', ['cesium.services', 'cesium.es.http.se
       };
     }
 
-    // Listen for settings changed
-    csSettings.api.data.on.changed($rootScope, function(data){
-      if (restoringSettings) {
-        restoringSettings = false;
-        return;
-      }
-
-      var wasEnable = listeners && listeners.length > 0;
-
-      refreshListeners();
-
-      if (!wasEnable && isEnable()) {
-        return onWalletLogin(csWallet.data);
-      }
-      else {
-        onSettingsChanged(data);
-      }
-    });
-
-    // Ask (once) user to enable ES plugin
+    // Default actions
     Device.ready().then(function() {
-
-      if (csConfig.plugins && csConfig.plugins.es && csConfig.plugins.es.askEnable && // if config ask enable
-        csSettings.data.plugins.es && !csSettings.data.plugins.es.enable && // AND user settings has disable plugin
-        csSettings.data.plugins.es.askEnable // AND user has not yet answer 'NO'
-      ) {
-        UIUtils.alert.confirm('ES_SETTINGS.CONFIRM.ASK_ENABLE', 'ES_SETTINGS.CONFIRM.ASK_ENABLE_TITLE', {
-          cancelText: 'COMMON.BTN_NO',
-          okText: 'COMMON.BTN_YES'
-        })
-          .then(function (confirm) {
-            if (confirm) {
-              csSettings.data.plugins.es.enable = true;
-            }
-            csSettings.data.plugins.es.askEnable = false;
-            csSettings.store();
-          });
-      }
+      esHttp.api.node.on.start($rootScope, refreshListeners, this);
+      esHttp.api.node.on.stop($rootScope, refreshListeners, this);
+      return refreshListeners();
     });
 
-    // Default action
-    refreshListeners();
-
-    return {
-      copy: copy,
+    var exports  = {
       node: {
-        server: esHttp.getServer(host, port),
-        summary: esHttp.get(host, port, '/node/summary'),
+        summary: esHttp.get('/node/summary'),
         parseEndPoint: parseEndPoint
       },
       profile: {
-        get: esHttp.get(host, port, '/user/profile/:id'),
-        add: esHttp.record.post(host, port, '/user/profile'),
-        update: esHttp.record.post(host, port, '/user/profile/:id/_update'),
-        avatar: esHttp.get(host, port, '/user/profile/:id?_source=avatar'),
+        get: esHttp.get('/user/profile/:id'),
+        add: esHttp.record.post('/user/profile'),
+        update: esHttp.record.post('/user/profile/:id/_update'),
+        avatar: esHttp.get('/user/profile/:id?_source=avatar'),
         fillAvatars: fillAvatars
       },
       settings: {
-        get: esHttp.get(host, port, '/user/settings/:id'),
-        add: esHttp.record.post(host, port, '/user/settings'),
-        update: esHttp.record.post(host, port, '/user/settings/:id/_update'),
+        get: esHttp.get('/user/settings/:id'),
+        add: esHttp.record.post('/user/settings'),
+        update: esHttp.record.post('/user/settings/:id/_update'),
       },
       websocket: {
         event: function() {
-          return esHttp.ws((wsPort == 443 ? 'wss' : 'ws') +'://'+esHttp.getServer(host, wsPort)+'/ws/event/user/:pubkey/:locale');
+          return esHttp.ws('/ws/event/user/:pubkey/:locale');
         },
         change: function() {
-          return esHttp.ws((wsPort == 443 ? 'wss' : 'ws') +'://'+esHttp.getServer(host, wsPort)+'/ws/_changes');
+          return esHttp.ws('/ws/_changes');
         }
       },
       constants: CONSTANTS
     };
-  }
 
-  var host = csSettings.data.plugins && csSettings.data.plugins.es ? csSettings.data.plugins.es.host : null;
-  var port = host ? csSettings.data.plugins.es.port : null;
-  var wsPort = host ? csSettings.data.plugins.es.wsPort : port;
+    _.keys(exports).forEach(function(key) {
+      that[key] = exports[key];
+    });
+  }
 
-  var service = factory('default', host, port, wsPort);
-  service.instance = factory;
-  return service;
+  return new Factory();
 })
 ;
diff --git a/www/plugins/es/templates/group/edit_group.html b/www/plugins/es/templates/group/edit_group.html
new file mode 100644
index 0000000000000000000000000000000000000000..7b0b089384d13986ad3b15ec742cf69c5d1e7709
--- /dev/null
+++ b/www/plugins/es/templates/group/edit_group.html
@@ -0,0 +1,111 @@
+<ion-view left-buttons="leftButtons">
+  <ion-nav-title>
+    <span class="visible-xs" ng-if="id" ng-bind-html="formData.title"></span>
+    <span class="visible-xs" ng-if="!loading && !id" translate>GROUP.EDIT.TITLE_NEW</span>
+  </ion-nav-title>
+
+  <ion-nav-buttons side="secondary">
+      <button class="button button-icon button-clear visible-xs visible-sm"
+              ng-class="{'ion-android-send':!id, 'ion-android-done': id}"
+              ng-click="save()">
+      </button>
+  </ion-nav-buttons>
+
+  <ion-content scroll="true">
+      <div class="row no-padding">
+
+        <div class="col col-20 hidden-xs hidden-sm">&nbsp;</div>
+
+        <div class="col">
+          <!-- loading -->
+          <div class="center padding" ng-if="loading">
+            <ion-spinner icon="android"></ion-spinner>
+          </div>
+
+          <form name="recordForm" novalidate="" ng-submit="save()">
+
+            <!--  -->
+            <div class="list"
+                 ng-class="motion.ionListClass"
+                 ng-init="setForm(recordForm)">
+
+              <div class="item hidden-xs">
+                <h1 ng-if="id" ng-bind-html="formData.title"></h1>
+                <h1 ng-if="!id" translate>GROUP.EDIT.TITLE_NEW</h1>
+                <h2 class="balanced" ng-if="!id">
+                  <i class="icon ion-android-people"></i>
+                  <i class="icon ion-android-lock" ng-if="formData.type=='managed'"></i>
+                  {{'GROUP.TYPE.ENUM.'+formData.type|upper|translate}}
+                </h2>
+              </div>
+              <div class="item" ng-if="id">
+                <h4 class="gray">
+                  <i class="icon ion-calendar"></i>
+                  {{'COMMON.LAST_MODIFICATION_DATE'|translate}}&nbsp;{{formData.time | formatDate}}
+                </h4>
+                <div class="badge badge-balanced badge-editable" ng-click="showRecordTypeModal()">
+                  {{'GROUP.TYPE.ENUM.'+formData.type|upper|translate}}
+                </div>
+              </div>
+
+              <!-- pictures -->
+              <ng-include src="'plugins/es/templates/common/edit_pictures.html'"></ng-include>
+
+              <div class="item item-divider" translate>GROUP.GENERAL_DIVIDER</div>
+
+              <!-- title -->
+              <div class="item item-input item-floating-label"
+                   ng-class="{'item-input-error': form.$submitted && form.title.$invalid}">
+                <span class="input-label" translate>GROUP.EDIT.RECORD_TITLE</span>
+                <input type="text" placeholder="{{'GROUP.EDIT.RECORD_TITLE_HELP'|translate}}"
+                       name="title"
+                       id="group-record-title"
+                       ng-model="formData.title"
+                       ng-minlength="3"
+                       ng-required="true"/>
+              </div>
+              <div class="form-errors"
+                   ng-if="form.$submitted && form.title.$error"
+                   ng-messages="form.title.$error">
+                <div class="form-error" ng-message="required">
+                  <span translate="ERROR.FIELD_REQUIRED"></span>
+                </div>
+                <div class="form-error" ng-message="minlength">
+                  <span translate="ERROR.FIELD_TOO_SHORT"></span>
+                </div>
+              </div>
+
+              <!-- description -->
+              <div class="item item-input item-floating-label">
+                <span class="input-label" translate>GROUP.EDIT.RECORD_DESCRIPTION</span>
+                <textarea placeholder="{{'GROUP.EDIT.RECORD_DESCRIPTION_HELP'|translate}}"
+                          ng-model="formData.description"
+                          rows="8" cols="10">
+                </textarea>
+              </div>
+
+              <!-- social networks -->
+              <ng-include src="'plugins/es/templates/common/edit_socials.html'"></ng-include>
+
+            </div>
+
+            <div class="padding hidden-xs hidden-sm text-right">
+              <button class="button button-clear button-dark ink" ng-click="cancel()" type="button" translate>
+                COMMON.BTN_CANCEL
+              </button>
+              <button class="button button-positive button-raised ink" type="submit" ng-if="!id" translate>
+                COMMON.BTN_PUBLISH
+              </button>
+              <button class="button button-assertive button-raised ink" type="submit" ng-if="id" translate>
+                COMMON.BTN_SAVE
+              </button>
+            </div>
+          </form>
+        </div>
+
+        <div class="col col-20 hidden-xs hidden-sm">&nbsp;</div>
+
+      </div>
+    </div>
+  </ion-content>
+</ion-view>
diff --git a/www/plugins/es/templates/group/item_group.html b/www/plugins/es/templates/group/item_group.html
new file mode 100644
index 0000000000000000000000000000000000000000..313f7bc9a72644cac38ee0c4d2d94e5c401fc475
--- /dev/null
+++ b/www/plugins/es/templates/group/item_group.html
@@ -0,0 +1,27 @@
+<a name="group-{{:rebind:group.hash}}"></a>
+<ion-item id="group-{{:rebind:block.hash}}"
+          class="item item-icon-left item-group {{ionItemClass}}"
+          ng-click="select(group)">
+
+  <i class="icon ion-cube stable" ng-if=":rebind:(!group.empty && !group.avatar)"></i>
+  <i class="avatar" ng-if=":rebind:!group.empty && group.avatar" style="background-image: url('{{:rebind:block.avatar.src}}')"></i>
+
+  <div class="row no-padding">
+    <div class="col">
+      <h4 class="dark">
+        <i class="ion-clock"></i>
+        {{:rebind:group.creationTime|formatDate}}
+      </h4>
+      <h4>
+        <!-- membersCount -->
+        <i class="dark ion-person"></i>
+        <span class="dark" ng-if=":rebind:group.membersCount">+{{:rebind:group.membersCount}}</span>
+      </h4>
+    </div>
+
+    <div class="col col-33 positive hidden-md">
+      <h4><i class="ion-person"></i> <span ng-bind-html=":rebind:group.title"></span></h4>
+    </div>
+
+  </div>
+</ion-item>
diff --git a/www/plugins/es/templates/group/items_groups.html b/www/plugins/es/templates/group/items_groups.html
new file mode 100644
index 0000000000000000000000000000000000000000..b3df8b122220a96d25b98bc294dfa3476dfd5379
--- /dev/null
+++ b/www/plugins/es/templates/group/items_groups.html
@@ -0,0 +1,29 @@
+
+
+<div class="item row row-header hidden-xs hidden-sm" ng-if="expertMode">
+
+  <a class="no-padding dark col col-header"
+     ng-click="toggleSort('medianTime')">
+    <cs-sort-icon asc="search.asc" sort="search.sort" toggle="'medianTime'"></cs-sort-icon>
+    {{'GROUP.LOOKUP.HEADER_CREATION_TIME' | translate}}
+  </a>
+  <a class="no-padding dark col col-header"
+     ng-click="toggleSort('issuer')">
+    <cs-sort-icon asc="search.asc" sort="search.sort" toggle="'issuer'"></cs-sort-icon>
+    {{'GROUP.LOOKUP.HEADER_ISSUER' | translate}}
+  </a>
+  <div class="col col-20">&nbsp;
+  </div>
+  <a class="no-padding dark col col-20 col-header" ng-click="toggleSort('number')">
+    <cs-sort-icon asc="search.asc" sort="search.sort" toggle="'number'"></cs-sort-icon>
+    {{'GROUP.LOOKUP.HEADER_NAME' | translate}}
+  </a>
+</div>
+
+<div class="padding gray" ng-if=":rebind:!search.loading && !search.results.length" translate>
+  COMMON.SEARCH_NO_RESULT
+</div>
+
+<ng-repeat ng-repeat="group in :rebind:search.results"
+           ng-include="'plugins/es/templates/group/item_group.html'">
+</ng-repeat>
diff --git a/www/plugins/es/templates/group/list.html b/www/plugins/es/templates/group/list.html
new file mode 100644
index 0000000000000000000000000000000000000000..95e49b6a9d35fa7230b1f97337c1f0ff7ff11b84
--- /dev/null
+++ b/www/plugins/es/templates/group/list.html
@@ -0,0 +1,25 @@
+<ion-list class="{{::motion.ionListClass}}">
+
+  <ion-item
+    ng-repeat="notification in search.results"
+    class="item-border-large item-text-wrap ink item-avatar"
+    ng-class="{'unread': !notification.read}"
+    ng-click="select(notification)">
+
+    <i ng-if="!notification.avatar" class="item-image icon {{::notification.avatarIcon}}"></i>
+    <i ng-if="notification.avatar" class="item-image avatar" style="background-image: url({{::notification.avatar.src}})"></i>
+
+    <h3 trust-as-html="notification.message | translate:notification"></h3>
+    <h4>
+      <i class="icon {{notification.icon}}"></i>&thinsp;<span class="dark">{{notification.time|formatFromNow}}</span>
+      <span class="gray">| {{notification.time|formatDate}}</span>
+    </h4>
+  </ion-item>
+</ion-list>
+
+<ion-infinite-scroll
+  ng-if="!search.loading && search.hasMore"
+  spinner="android"
+  on-infinite="showMore()"
+  distance="1%">
+</ion-infinite-scroll>
diff --git a/www/plugins/es/templates/group/lookup.html b/www/plugins/es/templates/group/lookup.html
new file mode 100644
index 0000000000000000000000000000000000000000..46e60cd58d92aefc7174ce49bcec3685941ff244
--- /dev/null
+++ b/www/plugins/es/templates/group/lookup.html
@@ -0,0 +1,16 @@
+<ion-view class="view-group">
+  <ion-nav-title>
+    <span translate>GROUP.LOOKUP.TITLE</span>
+  </ion-nav-title>
+
+  <ion-nav-buttons side="secondary">
+
+    <button class="button button-icon button-clear icon ion-android-more-vertical visible-xs visible-sm" ng-click="showActionsPopover($event)">
+    </button>
+
+  </ion-nav-buttons>
+
+  <ion-content class="padding no-padding-xs" scroll="true">
+    <ng-include src="'plugins/es/templates/group/lookup_form.html'"></ng-include>
+  </ion-content>
+</ion-view>
diff --git a/www/plugins/es/templates/group/lookup_form.html b/www/plugins/es/templates/group/lookup_form.html
new file mode 100644
index 0000000000000000000000000000000000000000..0c6b9ae22d1edbb5c773dcddd1df07dc9cfd34c8
--- /dev/null
+++ b/www/plugins/es/templates/group/lookup_form.html
@@ -0,0 +1,86 @@
+<div class="lookupForm">
+
+  <button class="button button-small button-positive button-clear ink pull-right padding-right hidden-sm hidden-xs"
+          ng-click="showNewRecordModal()">
+    <i class="icon ion-plus"></i>
+    {{'GROUP.LOOKUP.BTN_NEW' | translate}}
+  </button>
+
+  <!-- search text-->
+  <label class="item item-input">
+    <i class="icon ion-search placeholder-icon"></i>
+    <input type="text"
+           class="visible-xs visible-sm"
+           placeholder="{{'GROUP.LOOKUP.SEARCH_HELP'|translate}}"
+           ng-model="search.text"
+           ng-model-options="{ debounce: 650 }"
+           ng-change="doSearchText()">
+    <input type="text"
+           class="hidden-xs hidden-sm"
+           id="{{searchTextId}}" placeholder="{{'GROUP.LOOKUP.SEARCH_HELP'|translate}}"
+           ng-model="search.text"
+           on-return="doSearchText()">
+    <div class="helptip-anchor-center">
+      <a id="helptip-group-search-text"></a>
+    </div>
+
+  </label>
+
+  <div class="padding-top padding-xs" style="display: block; height: 60px;">
+    <div class="pull-left">
+      <h4
+        ng-if="search.type=='open'" translate>
+        GROUP.LOOKUP.OPEN_RESULTS_LIST
+      </h4>
+      <h4
+        ng-if="search.type=='last'" translate>
+        GROUP.LOOKUP.LAST_RESULTS_LIST
+      </h4>
+      <h4
+        ng-if="search.type=='managed'" translate>
+        GROUP.LOOKUP.MANAGED_RESULTS_LIST
+      </h4>
+      <h4 ng-if="search.type=='text'">
+        {{'COMMON.RESULTS_LIST'|translate}}
+      </h4>
+      <h5 class="dark" ng-if="!search.loading && search.total">
+        <span translate="COMMON.RESULTS_COUNT" translate-values="{count: search.total}"></span>
+        <small class="gray" ng-if=":rebind:search.took && expertMode">
+          - {{:rebind:'COMMON.EXECUTION_TIME'|translate: {duration: search.took} }}
+        </small>
+      </h5>
+      <h5 class="gray" ng-if="search.loading" >
+        <ion-spinner class="icon ion-spinner-small" icon="android"></ion-spinner>
+        <span translate>COMMON.SEARCHING</span>
+        <br/>
+      </h5>
+    </div>
+
+    <div class=" pull-right hidden-xs hidden-sm">
+      <a ng-if="enableFilter"
+         class="button button-text button-small ink icon ion-clock"
+         ng-class="{'button-text-positive': search.type=='last'}"
+         ng-click="doSearchLast()">
+        {{'GROUP.LOOKUP.BTN_LAST' | translate}}
+      </a>
+      &nbsp;
+      <button class="button button-small button-stable ink"
+              ng-click="doSearchText()">
+        {{'COMMON.BTN_SEARCH' | translate:search}}
+      </button>
+    </div>
+  </div>
+
+  <ion-list class="list {{ionListClass}}">
+
+    <ng-include src="'plugins/es/templates/group/items_groups.html'"></ng-include>
+
+  </ion-list>
+
+  <ion-infinite-scroll
+    ng-if="search.hasMore"
+    spinner="android"
+    on-infinite="showMore()"
+    distance="1%">
+  </ion-infinite-scroll>
+
diff --git a/www/plugins/es/templates/group/modal_record_type.html b/www/plugins/es/templates/group/modal_record_type.html
new file mode 100644
index 0000000000000000000000000000000000000000..a64a96bba8c0330f17c64bd5aedfc562ead07c8a
--- /dev/null
+++ b/www/plugins/es/templates/group/modal_record_type.html
@@ -0,0 +1,35 @@
+<ion-modal-view>
+  <ion-header-bar class="bar-positive">
+      <button class="button button-clear" ng-click="closeModal()" translate>COMMON.BTN_CANCEL</button>
+      <h1 class="title" translate>GROUP.TYPE.TITLE</h1>
+  </ion-header-bar>
+
+    <ion-content class="lookupForm padding">
+      <h3 translate>GROUP.TYPE.SELECT_TYPE</h3>
+
+    	<div class="list">
+
+        <!-- open group -->
+        <div class="item item-complex card stable-bg item-icon-left ink"
+             ng-click="closeModal('open')">
+          <div class="item-content item-text-wrap">
+            <i class="item-image icon ion-android-people dark"></i>
+            <h2 translate>GROUP.TYPE.OPEN_GROUP</h2>
+            <h4 class="gray" translate>GROUP.TYPE.OPEN_GROUP_HELP</h4>
+          </div>
+        </div>
+
+        <!-- managed group -->
+        <div class="item item-complex card stable-bg item-icon-left ink"
+             ng-click="closeModal('managed')">
+          <div class="item-content item-text-wrap">
+            <i class="item-image icon ion-android-people dark"></i>
+            <i class="icon-secondary ion-android-lock dark" style="left: 10px; top: -8px;"></i>
+            <h2 translate>GROUP.TYPE.MANAGED_GROUP</h2>
+            <h4 class="gray" translate>GROUP.TYPE.MANAGED_GROUP_HELP</h4>
+          </div>
+        </div>
+
+      </div>
+</ion-content>
+</ion-modal-view>
diff --git a/www/plugins/es/templates/group/popover_group.html b/www/plugins/es/templates/group/popover_group.html
new file mode 100644
index 0000000000000000000000000000000000000000..dfb323b0c858e1d40946954720fc1935d978f911
--- /dev/null
+++ b/www/plugins/es/templates/group/popover_group.html
@@ -0,0 +1,41 @@
+<ion-popover-view class="fit hidden-xs hidden-sm popover-notification"
+                  ng-controller="PopoverGroupCtrl">
+  <ion-header-bar class="stable-bg block">
+    <div class="title" translate>GROUP.NOTIFICATIONS.TITLE</div>
+
+    <div class="pull-right">
+      <a class="positive"
+         ng-click="markAllAsRead()"
+         translate>COMMON.NOTIFICATIONS.MARK_ALL_AS_READ</a>
+    </div>
+  </ion-header-bar>
+  <ion-content scroll="true">
+    <div class="center" ng-if="search.loading">
+      <ion-spinner icon="android"></ion-spinner>
+    </div>
+    <div class="padding gray" ng-if="!search.loading && !search.results.length" translate>
+      COMMON.NOTIFICATIONS.NO_RESULT
+    </div>
+
+    <ng-include src="'plugins/es/templates/group/list_group.html'"></ng-include>
+
+  </ion-content>
+
+  <ion-footer-bar class="stable-bg block">
+    <!-- settings -->
+    <div class="pull-left">
+      <a class="positive"
+         ui-sref="app.es_settings"
+         ng-click="closePopover()"
+         translate>COMMON.NOTIFICATIONS.SETTINGS</a>
+    </div>
+
+    <!-- show all -->
+    <div class="pull-right">
+      <a class="positive"
+         ui-sref="app.view_notifications"
+         ng-click="closePopover()"
+         translate>COMMON.NOTIFICATIONS.SHOW_ALL</a>
+    </div>
+  </ion-footer-bar>
+</ion-popover-view>
diff --git a/www/plugins/es/templates/group/view_record.html b/www/plugins/es/templates/group/view_record.html
new file mode 100644
index 0000000000000000000000000000000000000000..ad3bab2c365b5054a7367b94e8786c4f0f89c947
--- /dev/null
+++ b/www/plugins/es/templates/group/view_record.html
@@ -0,0 +1,160 @@
+<ion-view left-buttons="leftButtons">
+  <ion-nav-title>
+
+  </ion-nav-title>
+
+  <ion-nav-buttons side="secondary">
+    <button class="button button-bar button-icon button-clear visible-xs visible-sm" ng-click="edit()" ng-if="canEdit">
+      <i class="icon ion-android-create"></i>
+    </button>
+    <button class="button button-bar button-icon button-clear icon ion-android-more-vertical visible-xs visible-sm"
+            ng-click="showActionsPopover($event)">
+    </button>
+  </ion-nav-buttons>
+
+  <ion-content scroll="true">
+    <div class="positive-900-bg hero">
+      <div class="content" ng-if="!loading">
+        <i class="avatar cion-registry-{{formData.type}}" ng-if="!formData.thumbnail"></i>
+        <i class="avatar" style="background-image: url({{::formData.thumbnail.src}})" ng-if="formData.thumbnail"></i>
+        <h3 ng-bind-html="formData.title"></h3>
+        <h4>&nbsp;</h4>
+      </div>
+      <h4 class="content light" ng-if="loading">
+        <ion-spinner icon="android"></ion-spinner>
+      </h4>
+    </div>
+
+    <div class="row no-padding-xs">
+      <div class="col col-20 hidden-xs hidden-sm">&nbsp;
+      </div>
+
+      <div class="col list item-text-wrap no-padding-xs" ng-class="motion.ionListClass">
+
+        <div class="item">
+          <h2 class="gray">
+            <a ng-if="formData.city" ui-sref="app.groups({location:formData.city})">
+              <i class="icon ion-location"></i>
+              <span ng-bind-html="formData.city"></span>
+            </a>
+            <span ng-if="formData.city && formData.type">&nbsp;|&nbsp;</span>
+            <a ng-if="formData.type" ui-sref="app.groups({type:formData.type})">
+              <i class="icon ion-flag"></i>
+              {{'GROUP.TYPE.ENUM.'+formData.type|upper|translate}}
+            </a>
+          </h2>
+          <h4>
+            <i class="icon ion-clock" ng-if="formData.time"></i>
+            <span translate>COMMON.SUBMIT_BY</span>
+            <a ng-class="{'positive': issuer.uid, 'gray': !issuer.uid}"
+               ui-sref="app.wot_identity({pubkey:issuer.pubkey, uid: issuer.name||issuer.uid})">
+              <ng-if ng-if="issuer.uid">
+                <i class="icon ion-person"></i>
+                {{::issuer.name||issuer.uid}}
+              </ng-if>
+              <span ng-if="!issuer.uid">
+                <i class="icon ion-key"></i>
+                {{issuer.pubkey|formatPubkey}}
+              </span>
+            </a>
+            <span >
+                {{formData.time|formatFromNow}}
+                <h4 class="gray hidden-xs">|
+                  {{formData.time | formatDate}}
+                </h4>
+              </span>
+          </h4>
+        </div>
+
+        <!-- Buttons bar-->
+        <div class="item large-button-bar hidden-xs hidden-sm">
+          <button class="button button-stable button-small-padding icon ion-android-share-alt"
+                  ng-click="showSharePopover($event)">
+          </button>
+          <button class="button button-calm ink-dark"
+                  ng-if="formData.pubkey && !isUserPubkey(formData.pubkey)"
+                  ng-click="showTransferModal({pubkey:formData.pubkey, uid: formData.title})">
+            {{'COMMON.BTN_SEND_MONEY' | translate}}
+          </button>
+          <button class="button button-stable icon-left ink-dark"
+                  ng-if="canEdit"
+                  ng-click="delete()">
+            <i class="icon ion-trash-a assertive"></i>
+            <span class="assertive"> {{'COMMON.BTN_DELETE' | translate}}</span>
+          </button>
+          <button class="button button-calm icon-left ion-android-create ink"
+                  ng-if="canEdit"
+                  ng-click="edit()">
+            {{'COMMON.BTN_EDIT' | translate}}
+          </button>
+        </div>
+
+        <ion-item>
+            <h2>
+              <span class="text-keep-lines" ng-bind-html="formData.description"></span>
+            </h2>
+        </ion-item>
+
+        <ion-item>
+          <h4 ng-if="formData.address">
+            <span class="gray" translate>REGISTRY.VIEW.LOCATION</span>
+            <a class="positive" target='_blank' href="https://www.google.fr/maps/?q={{formData.address}},%20{{formData.city}}">
+              <span ng-bind-html="formData.address"></span>
+              <span ng-if="formData.city"> - </span>
+              <span ng-bind-html="formData.city"></span>
+            </a>
+          </h4>
+        </ion-item>
+
+        <!-- Socials networks -->
+        <ng-if ng-if="formData.socials && formData.socials.length>0">
+          <ion-item class="item-icon-left"
+                    type="no-padding item-text-wrap"
+                    ng-repeat="social in formData.socials track by social.url"
+                    id="social-{{social.url|formatSlug}}">
+            <i class="icon ion-social-{{social.type}}"
+               ng-class="{'ion-bookmark': social.type == 'other', 'ion-link': social.type == 'web', 'ion-email': social.type == 'email'}"></i>
+            <p ng-if="social.type && social.type != 'web'">{{social.type}}</p>
+            <h2>
+              <a href="{{social.url}}" ng-if="social.type != 'email'" target="_blank">{{social.url}}</a>
+              <a href="mailto:{{social.url}}" ng-if="social.type == 'email'">{{social.url}}</a>
+            </h2>
+          </ion-item>
+        </ng-if>
+
+        <div class="lazy-load">
+
+          <!-- pictures -->
+          <ng-include src="'plugins/es/templates/common/view_pictures.html'"></ng-include>
+
+
+          <span class="item item-divider" ng-if="formData.pubkey">
+            <span translate>REGISTRY.TECHNICAL_DIVIDER</span>
+          </span>
+
+          <!-- pubkey -->
+          <div class="item item-icon-left item-text-wrap ink"
+                    ng-if="formData.pubkey"
+                    copy-on-click="{{::formData.pubkey}}">
+            <i class="icon ion-key"></i>
+            <span translate>REGISTRY.EDIT.RECORD_PUBKEY</span>
+            <h4 class="dark">{{::formData.pubkey}}</h4>
+          </div>
+
+          <!-- comments -->
+          <ng-include src="'plugins/es/templates/common/view_comments.html'"></ng-include>
+        </div>
+      </div>
+
+      <div class="col col-20 hidden-xs hidden-sm">&nbsp;
+      </div>
+    </div>
+  </ion-content>
+
+  <button class="button button-fab button-fab-bottom-right button-assertive icon ion-android-send visible-xs visible-sm"
+          ng-if="formData.pubkey && !isUserPubkey(formData.pubkey)"
+          ng-click="showTransferModal({pubkey: formData.pubkey, uid: formData.title})">
+  </button>
+
+
+</ion-view>
diff --git a/www/plugins/es/templates/registry/edit_record.html b/www/plugins/es/templates/registry/edit_record.html
index 883c4f81095ff83c6442894b51e02301b8af83a7..be055364d8b0086db3de68cf67c2b376ddd015bc 100644
--- a/www/plugins/es/templates/registry/edit_record.html
+++ b/www/plugins/es/templates/registry/edit_record.html
@@ -23,7 +23,9 @@
           </div>
 
           <form name="recordForm" novalidate="" ng-submit="save()">
-            <div class="list animate-ripple" ng-init="setForm(recordForm)">
+            <div class="list"
+                 ng-class="motion.ionListClass"
+                 ng-init="setForm(recordForm)">
 
               <div class="item hidden-xs">
                 <h1 ng-if="id" ng-bind-html="formData.title"></h1>
diff --git a/www/plugins/es/templates/user/edit_profile.html b/www/plugins/es/templates/user/edit_profile.html
index d4ca46695e817477c9c474405f1f636dff0defe6..ffb5f08e642818b9b1f8707e2f6fb71be5d80a83 100644
--- a/www/plugins/es/templates/user/edit_profile.html
+++ b/www/plugins/es/templates/user/edit_profile.html
@@ -13,7 +13,7 @@
     <div class="positive-900-bg hero">
       <div class="content">
         <i class="avatar"
-           style="background-image: url('{{avatar.src}}')"
+           ng-style="avatarStyle"
            ng-class="{'avatar-wallet': !loading && !avatar && walletData && !walletData.isMember, 'avatar-member': !loading && !avatar && walletData.isMember}">
           <button class="button button-positive button-large button-clear flat icon ion-camera visible-xs visible-sm"
                   style="display: inline-block;"
@@ -21,17 +21,14 @@
           <button class="button button-positive button-large button-clear icon ion-camera hidden-xs hidden-sm"
                   ng-click="showAvatarModal()"></button>
         </i>
-        <div ng-if="!loading">
-          <h3 class="light" ng-if="!formData.title && walletData && walletData.isMember">{{walletData.uid}}</h3>
-          <h3 class="light" ng-if="!formData.title && walletData && !walletData.isMember">{{::walletData.pubkey | formatPubkey}}</h3>
-          <h3 class="light">{{formData.title}}</h3>
-          <h4 class="light">&nbsp;</h4>
-        </div>
-        <div ng-if="loading">
-          <h4 class="light">
-            <ion-spinner icon="android"></ion-spinner>
-          </h4>
-        </div>
+        <h3 class="light">
+          <ng-if ng-if="!loading && !formData.title && walletData && walletData.isMember">{{walletData.uid}}</ng-if>
+          <ng-if ng-if="!loading && !formData.title && walletData && !walletData.isMember">{{::walletData.pubkey | formatPubkey}}</ng-if>
+          <ng-if ng-if="!loading && formData.title">{{formData.title}}</ng-if>
+        </h3>
+        <h4 class="light">
+          <ion-spinner ng-if="loading" icon="android"></ion-spinner>
+        </h4>
       </div>
     </div>
 
diff --git a/www/plugins/es/templates/wallet/view_wallet_extend.html b/www/plugins/es/templates/wallet/view_wallet_extend.html
index 7d069c1ef7be51ea478a3ec45a4900697affe2cd..2ecdef9fe2618917fefd2867c05ec6a00b33e132 100644
--- a/www/plugins/es/templates/wallet/view_wallet_extend.html
+++ b/www/plugins/es/templates/wallet/view_wallet_extend.html
@@ -1,11 +1,12 @@
 
 <!-- Buttons section -->
 <ng-if ng-if="enable && extensionPoint === 'buttons'">
-  <button class="button button-small-padding"
+  <!--<button class="button button-small-padding"
           ui-sref="app.user_edit_profile"
           title="{{'PROFILE.BTN_EDIT' | translate}}">
-    <i class="icon ion-edit"></i>
-  </button>
+    <i class="icon ion-person"></i>
+    <b class="icon-secondary ion-edit" style="left:31px; top: -6px"></b>
+  </button>-->
 </ng-if>
 
 <!-- General section -->
diff --git a/www/templates/settings/settings.html b/www/templates/settings/settings.html
index e7da4e24d10b6097a98f5ef7089db7162c6661c4..a07236ed855fa524aece7b146341c6ef477dfabd 100644
--- a/www/templates/settings/settings.html
+++ b/www/templates/settings/settings.html
@@ -72,6 +72,17 @@
           </div>
         </label>
       </div>
+
+     <!-- <div class="item item-toggle dark item-text-wrap">
+        <div class="input-label" ng-bind-html="'SETTINGS.ENABLE_UI_EFFECTS' | translate">
+        </div>
+        <label class="toggle toggle-royal">
+          <input type="checkbox" ng-model="formData.enableUuiEffects" >
+          <div class="track">
+            <div class="handle"></div>
+          </div>
+        </label>
+      </div>-->
       <!-- Allow extension here -->
       <cs-extension-point name="common"></cs-extension-point>
 
diff --git a/www/templates/wot/view_certifications.html b/www/templates/wot/view_certifications.html
index c3767f6b8455d067cfb5a7fec4fb0e2dfaedba4f..effd05feaf988eb55268519bfa36b8e5789e646c 100644
--- a/www/templates/wot/view_certifications.html
+++ b/www/templates/wot/view_certifications.html
@@ -13,10 +13,6 @@
 
     <ion-content class="certifications certifications-lg">
 
-      <div class="center padding" ng-if="loading">
-        <ion-spinner icon="android"></ion-spinner>
-      </div>
-
       <!-- Buttons bar -->
       <div class="hidden-xs hidden-sm text-center padding"
            ng-if="canCertify || canSelectAndCertify">
@@ -39,6 +35,10 @@
         </button>
       </div>
 
+      <div class="center padding" ng-if="loading">
+        <ion-spinner icon="android"></ion-spinner>
+      </div>
+
       <!-- certifications tables -->
       <div class="row responsive-sm responsive-md responsive-lg">
         <!-- Received certifications -->
@@ -55,7 +55,7 @@
             </div>
             <div class="col text-center no-padding">
               <a style="text-decoration: none;"
-                ui-sref="app.wot_identity({pubkey: formData.pubkey, uid: formData.name||formData.uid})">
+                ui-sref="app.wot_identity({pubkey: formData.pubkey, uid: formData.uid})">
                 <i class="avatar avatar-large"
                    ng-if="!formData.avatar"
                    ng-class="{'avatar-wallet': !formData.isMember, 'avatar-member': formData.isMember}"></i>