diff --git a/www/i18n/locale-fr-FR.json b/www/i18n/locale-fr-FR.json
index 7307bce2e45a457b9ea7bb2003fa7b419d51250e..bded45dd5fcf17f2cceed05f108ce6a90ec58e01 100644
--- a/www/i18n/locale-fr-FR.json
+++ b/www/i18n/locale-fr-FR.json
@@ -49,7 +49,7 @@
     "NO_ACCOUNT_QUESTION": "Pas encore de compte ? Créez-en un gratuitement !",
     "SEARCH_NO_RESULT": "Aucun résultat trouvé",
     "LOADING": "Veuillez patienter...",
-    "LOADING_WAIT": "Veuillez patienter...<br/><small>(Attente de disponibilité du noeud)</small>",
+    "LOADING_WAIT": "Veuillez patienter...<br/><small>(Cesium interroge le nœud Duniter)</small>",
     "SEARCHING": "Recherche en cours...",
     "FROM": "De",
     "TO": "À",
diff --git a/www/index.html b/www/index.html
index 9fc89e5064e20f438c6104aecb449c4a1bae6c40..25c8d97c5256bef69ccc411fc9edb1dec469b74e 100644
--- a/www/index.html
+++ b/www/index.html
@@ -228,6 +228,7 @@
     <script src="dist/dist_js/plugins/es/js/controllers/invitation-controllers.js"></script>
     <script src="dist/dist_js/plugins/es/js/controllers/subscription-controllers.js"></script>
     <script src="dist/dist_js/plugins/es/js/controllers/document-controllers.js"></script>
+    <script src="dist/dist_js/plugins/es/js/controllers/like-controllers.js"></script>
 
     <!-- Graph plugin -->
     <script src="dist/dist_js/plugins/graph/js/plugin.js"></script>
diff --git a/www/js/config.js b/www/js/config.js
index a543dbff15a6721818d5189dcb4bf5f8786e9c80..9aae1c6387bdc4dacf2344d1fbd5d65030fcad67 100644
--- a/www/js/config.js
+++ b/www/js/config.js
@@ -10,46 +10,37 @@ angular.module("cesium.config", [])
 
 .constant("csConfig", {
 	"cacheTimeMs": 300000,
-	"fallbackLanguage": "en",
+	"fallbackLanguage": "fr-FR",
+	"defaultLanguage": "fr-FR",
 	"rememberMe": true,
 	"showUDHistory": true,
-	"timeout": 40000,
+	"timeout": 300000,
 	"timeWarningExpireMembership": 5184000,
 	"timeWarningExpire": 7776000,
-	"keepAuthIdle": 600,
 	"useLocalStorage": true,
-	"useRelative": false,
-	"expertMode": false,
+	"useRelative": true,
+	"expertMode": true,
 	"decimalCount": 2,
-	"httpsMode": false,
-	"shareBaseUrl": "https://g1.duniter.fr",
 	"helptip": {
-		"enable": true,
+		"enable": false,
 		"installDocUrl": {
 			"fr-FR": "https://duniter.org/fr/wiki/duniter/installer/",
 			"en": "https://duniter.org/en/wiki/duniter/install/"
 		}
 	},
 	"license": {
-		"en": "license/license_g1-en",
 		"fr-FR": "license/license_g1-fr-FR",
-		"es-ES": "license/license_g1-es-ES"
+		"en": "license/license_g1-en",
+		"es-ES": "license/license_g1-es-ES",
+		"eo-EO": "license/license_g1-eo-EO"
 	},
 	"node": {
-		"host": "g1.duniter.org",
+		"host": "g1.duniter.fr",
 		"port": 443
 	},
 	"fallbackNodes": [
 		{
-			"host": "g1.cgeek.fr",
-			"port": 443
-		},
-		{
-			"host": "g1.monnaielibreoccitanie.org",
-			"port": 443
-		},
-		{
-			"host": "g1.le-sou.org",
+			"host": "g1.duniter.org",
 			"port": 443
 		},
 		{
@@ -57,19 +48,13 @@ angular.module("cesium.config", [])
 			"port": 443
 		}
 	],
-	"developers": [
-		{
-			"name": "Benoit Lavenier",
-			"pubkey": "38MEAZN68Pz1DTvT3tqgxx4yQP6snJCQhPqEFxbDk4aE"
-		}
-	],
 	"plugins": {
 		"es": {
-			"enable": true,
-			"askEnable": true,
-			"useRemoteStorage": true,
-			"host": "g1.data.duniter.fr",
-			"port": 443,
+			"enable": false,
+			"askEnable": false,
+			"host": "localhost",
+			"port": 9200,
+			"wsPort": 9400,
 			"fallbackNodes": [
 				{
 					"host": "g1.data.le-sou.org",
@@ -87,10 +72,19 @@ angular.module("cesium.config", [])
 				"certReceived": true
 			},
 			"defaultCountry": "France"
+		},
+		"graph": {
+			"enable": true
+		},
+		"neo4j": {
+			"enable": true
+		},
+		"rml9": {
+			"enable": true
 		}
 	},
 	"version": "1.5.3",
-	"build": "2020-01-27T13:59:40.149Z",
+	"build": "2020-03-02T09:05:39.852Z",
 	"newIssueUrl": "https://git.duniter.org/clients/cesium-grp/cesium/issues/new"
 })
 
diff --git a/www/js/services/bma-services.js b/www/js/services/bma-services.js
index cd80ab65ea1214b60214291286fbd3a533171d87..5bd2decd1513c85025a5ab60445c7e2b78cc2ad3 100644
--- a/www/js/services/bma-services.js
+++ b/www/js/services/bma-services.js
@@ -416,8 +416,8 @@ angular.module('cesium.bma.services', ['ngApi', 'cesium.http.services', 'cesium.
       },
       wot: {
         lookup: get('/wot/lookup/:search'),
-        certifiedBy: get('/wot/certified-by/:pubkey'),
-        certifiersOf: get('/wot/certifiers-of/:pubkey'),
+        certifiedBy: get('/wot/certified-by/:pubkey', csHttp.cache.SHORT),
+        certifiersOf: get('/wot/certifiers-of/:pubkey', csHttp.cache.SHORT),
         member: {
           all: get('/wot/members', csHttp.cache.LONG),
           pending: get('/wot/pending', csHttp.cache.SHORT)
diff --git a/www/js/services/wallet-services.js b/www/js/services/wallet-services.js
index ba6f8cf37583550548737b0c86edc59fd8abeb5f..e182342a3b6d06105f89404fbd7b07d8f0d6725b 100644
--- a/www/js/services/wallet-services.js
+++ b/www/js/services/wallet-services.js
@@ -695,20 +695,20 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
       return data;
     },
 
-    loadRequirements = function(withCache) {
+    loadRequirements = function(withCache, secondTry) {
       // Clean existing events
       cleanEventsByContext('requirements');
 
-      var finished = false;
-
-      $timeout(function() {
-        if (!finished) UIUtils.loading.update({template: "COMMON.LOADING_WAIT"});
-      }, 2000);
       // Get requirements
       return csWot.loadRequirements(data, withCache)
-        .then(function(res) {
-          finished = true;
-          return res;
+        .catch(function(err) {
+          // Retry once (can be a timeout, because Duniter node are long to response)
+          if (!secondTry) {
+            console.error("[wallet] Unable to load requirements: Will retrying... ", err);
+            UIUtils.loading.update({template: "COMMON.LOADING_WAIT"});
+            return loadRequirements(withCache, true);
+          }
+          throw err;
         });
     },
 
diff --git a/www/js/services/wot-services.js b/www/js/services/wot-services.js
index 98511b3e13a2c1b35fdcf8e2e1aa415d0f8327a8..2bc6e8d4282278c15ccb4006cbdf541d59f37372 100644
--- a/www/js/services/wot-services.js
+++ b/www/js/services/wot-services.js
@@ -188,8 +188,12 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c
       }
       data = {pubkey: inputData.pubkey, uid: inputData.uid};
 
-      var now = Date.now();
+      // Alert user, when request is too long (> 2s)
+      $timeout(function() {
+        if (!data.requirements.loaded) UIUtils.loading.update({template: "COMMON.LOADING_WAIT"});
+      }, 2000);
 
+      var now = Date.now();
       return $q.all([
         // Get currency
         csCurrency.get(),
@@ -265,6 +269,7 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c
           return inputData;
         })
         .catch(function(err) {
+          data.requirements = {loaded: true}; // Mark has loaded - need by the previous $timeout
           _resetRequirements(inputData);
           // If not a member: continue
           if (!!err &&
@@ -277,8 +282,6 @@ angular.module('cesium.wot.services', ['ngApi', 'cesium.bma.services', 'cesium.c
         });
     },
 
-
-
     loadIdentityByLookup = function(pubkey, uid) {
       var data = {
         pubkey: pubkey,
diff --git a/www/plugins/es/js/controllers/common-controllers.js b/www/plugins/es/js/controllers/common-controllers.js
index 0d96ac610014a2192b4cca46e0c219ec26aefacd..415602f8936985b7cc5fdee8a45431b3e9fd0fde 100644
--- a/www/plugins/es/js/controllers/common-controllers.js
+++ b/www/plugins/es/js/controllers/common-controllers.js
@@ -21,8 +21,6 @@ angular.module('cesium.es.common.controllers', ['ngResource', 'cesium.es.service
   .controller('ESSearchPositionItemCtrl', ESSearchPositionItemController)
 
   .controller('ESSearchPositionModalCtrl', ESSearchPositionModalController)
-
-  .controller('ESLikesCtrl', ESLikesController)
 ;
 
 
@@ -975,282 +973,3 @@ function ESSearchPositionModalController($scope, $q, $translate, esGeo, paramete
   };
 
 }
-
-
-function ESLikesController($scope, $q, $timeout, $translate, $ionicPopup, UIUtils, Modals, csWallet, esHttp, esLike) {
-  'ngInject';
-
-  $scope.entered = false;
-  $scope.abuseData = {};
-  $scope.abuseLevels = [
-    {value: 1, label: 'LOW'},
-    {value: 2, label: 'LOW'}
-  ];
-  $scope.staring = false;
-  $scope.options = $scope.options || {};
-  $scope.options.like = $scope.options.like || {
-    kinds: esLike.constants.KINDS,
-    index: undefined,
-    type: undefined,
-    id: undefined
-  };
-
-  $scope.$on('$recordView.enter', function(e, state) {
-    // First enter
-    if (!$scope.entered) {
-      $scope.entered = false;
-      // Nothing to do: main controller will trigger '$recordView.load' event
-    }
-    // second call (e.g. if cache)
-    else if ($scope.id) {
-      $scope.loadLikes($scope.id);
-    }
-  });
-
-  $scope.$on('$recordView.load', function(event, id) {
-    $scope.id = id || $scope.id;
-    if ($scope.id) {
-      $scope.loadLikes($scope.id);
-    }
-  });
-
-  // Init Like service
-  $scope.initLikes = function() {
-    if (!$scope.likeData) {
-      throw new Error("Missing 'likeData' in scope. Cannot load likes counter");
-    }
-    if (!$scope.options.like.service) {
-      if (!$scope.options.like.index || !$scope.options.like.type) {
-        throw new Error("Missing 'options.like.index' or 'options.like.type' in scope. Cannot load likes counter");
-      }
-      $scope.options.like.service = esLike.instance($scope.options.like.index, $scope.options.like.type);
-    }
-    if (!$scope.options.like.kinds) {
-      // Get scope's kinds (e.g. defined in the parent scope)
-      $scope.options.like.kinds = _.filter(esLike.constants.KINDS, function (kind) {
-        var key = kind.toLowerCase() + 's';
-        return angular.isDefined($scope.likeData[key]);
-      });
-    }
-  };
-
-  $scope.loadLikes = function(id) {
-    if ($scope.likeData.loading) return;// Skip
-
-    id = id || $scope.likeData.id;
-    $scope.initLikes();
-
-    var kinds = $scope.options.like.kinds || [];
-    if (!kinds.length) return; // skip
-
-    $scope.likeData.loading = true;
-    var now = Date.now();
-    console.debug("[ES] Loading counter of {0}... ({1})".format(id.substring(0,8), kinds));
-
-    var issuers = csWallet.isLogin() ? csWallet.pubkeys() : undefined;
-
-    return $q.all(_.map(kinds, function(kind) {
-      var key = kind.toLowerCase() + 's';
-      return $scope.options.like.service.count(id, {issuers: issuers, kind: kind})
-        .then(function (res) {
-          // Store result to scope
-          if ($scope.likeData[key]) {
-            angular.merge($scope.likeData[key], res);
-          }
-        });
-    }))
-      .then(function () {
-        $scope.likeData.id = id;
-        console.debug("[ES] Loading counter of {0} [OK] in {1}ms".format(id.substring(0,8), Date.now()-now));
-
-        if (_.contains(kinds, 'VIEW') && !$scope.canEdit) {
-          $scope.markAsView();
-        }
-
-        // Publish to parent scope (to be able to use it in action popover's buttons)
-        if ($scope.$parent) {
-          console.debug("[ES] [likes] Adding data and functions to parent scope");
-          $scope.$parent.toggleLike = $scope.toggleLike;
-          $scope.$parent.reportAbuse = $scope.reportAbuse;
-        }
-
-        $scope.likeData.loading = false;
-      })
-      .catch(function (err) {
-        console.error(err && err.message || err);
-        $scope.likeData.loading = false;
-      });
-  };
-
-  $scope.toggleLike = function(event, options) {
-    $scope.initLikes();
-    if (!$scope.likeData.id) throw new Error("Missing 'likeData.id' in scope. Cannot apply toggle");
-
-    options = options || {};
-    options.kind = options.kind && options.kind.toUpperCase() || 'LIKE';
-    var key = options.kind.toLowerCase() + 's';
-
-    $scope.likeData[key] = $scope.likeData[key] || {};
-
-    // Avoid too many call
-    if ($scope.likeData[key].loading === true || $scope.likeData.loading) {
-      event.preventDefault();
-      return $q.reject();
-    }
-
-    // Select the wallet, if many
-    if (!options.pubkey) {
-      return (csWallet.children.count() ? Modals.showSelectWallet({displayBalance: false}) : $q.when(csWallet))
-        .then(function(wallet) {
-          if (!wallet) throw 'CANCELLED';
-          options.pubkey = wallet.data.pubkey;
-          return $scope.toggleLike(event, options); // Loop
-        });
-    }
-
-    var wallet = csWallet.getByPubkey(options.pubkey);
-    if (!wallet) return;
-
-    $scope.likeData[key].loading = true;
-    return wallet.auth({minData: true})
-      .then(function(walletData) {
-        if (!walletData) {
-          UIUtils.loading.hide();
-          return;
-        }
-
-        // Check if member account
-        if (!walletData.isMember) {
-          // TODO: enable this
-          //throw {message: "ERROR.ONLY_MEMBER_CAN_EXECUTE_THIS_ACTION"};
-        }
-
-        // Apply like
-        options.id = $scope.likeData.id;
-        return $scope.options.like.service.toggle($scope.likeData.id, options);
-      })
-      .then(function(delta) {
-        UIUtils.loading.hide();
-        if (delta !== 0) {
-          $scope.likeData[key].total = ($scope.likeData[key].total || 0) + delta;
-          $scope.likeData[key].wasHitByPubkey = $scope.likeData[key].wasHitByPubkey || {};
-          $scope.likeData[key].wasHitByPubkey[options.pubkey] = delta > 0;
-          $scope.likeData[key].wasHitCount += delta;
-        }
-        $timeout(function() {
-          $scope.likeData[key].loading = false;
-          $scope.$broadcast('$$rebind::like'); // notify binder
-        }, 1000);
-      })
-      .catch(function(err) {
-        $scope.likeData[key].loading = false;
-        if (err === 'CANCELLED') return; // User cancelled
-        console.error(err);
-        UIUtils.onError('LIKE.ERROR.FAILED_TOGGLE_LIKE')(err);
-        event.preventDefault();
-      });
-  };
-
-  $scope.setAbuseForm = function(form) {
-    $scope.abuseForm = form;
-  };
-
-  $scope.showAbuseCommentPopover = function(event) {
-    return $translate(['COMMON.REPORT_ABUSE.TITLE', 'COMMON.REPORT_ABUSE.SUB_TITLE','COMMON.BTN_SEND', 'COMMON.BTN_CANCEL'])
-      .then(function(translations) {
-
-        UIUtils.loading.hide();
-
-        return $ionicPopup.show({
-          templateUrl: 'plugins/es/templates/common/popup_report_abuse.html',
-          title: translations['COMMON.REPORT_ABUSE.TITLE'],
-          subTitle: translations['COMMON.REPORT_ABUSE.SUB_TITLE'],
-          cssClass: 'popup-report-abuse',
-          scope: $scope,
-          buttons: [
-            {
-              text: translations['COMMON.BTN_CANCEL'],
-              type: 'button-stable button-clear gray'
-            },
-            {
-              text: translations['COMMON.BTN_SEND'],
-              type: 'button button-positive  ink',
-              onTap: function(e) {
-                $scope.abuseForm.$submitted=true;
-                if(!$scope.abuseForm.$valid || !$scope.abuseData.comment) {
-                  //don't allow the user to close unless he enters a uid
-                  e.preventDefault();
-                } else {
-                  return $scope.abuseData;
-                }
-              }
-            }
-          ]
-        });
-      })
-      .then(function(res) {
-        $scope.abuseData = {};
-        if (!res || !res.comment) { // user cancel
-          UIUtils.loading.hide();
-          return undefined;
-        }
-        return res;
-      });
-  };
-
-  $scope.reportAbuse = function(event, options) {
-    if (!$scope.likeData || !$scope.likeData.abuses || $scope.likeData.abuses.wasHitCount) return; // skip
-    if ($scope.likeData.abuses.wasHitCount) return; // already report
-
-    options = options || {};
-
-    // Select the wallet, if many
-    if (!options.pubkey) {
-      return (csWallet.children.count() ? Modals.showSelectWallet({displayBalance: false}) : $q.when(csWallet))
-        .then(function(wallet) {
-          if (!wallet) throw 'CANCELLED';
-          options.pubkey = wallet.data.pubkey;
-          return $scope.reportAbuse(event, options); // Loop
-        });
-    }
-
-    var wallet = csWallet.getByPubkey(options.pubkey);
-
-    // Check if member account
-    if (!wallet || !wallet.isMember()) {
-      UIUtils.alert.info("ERROR.ONLY_MEMBER_CAN_EXECUTE_THIS_ACTION");
-      return;
-    }
-
-    if (!options.comment) {
-      // Ask a comment
-      return $scope.showAbuseCommentPopover(event)
-        // Loop, with options.comment filled
-        .then(function(res) {
-          if (!res || !res.comment) return; // Empty comment: skip
-          options.comment = res.comment;
-          options.level = res.level || (res.delete && 5) || undefined;
-          return $scope.reportAbuse(event, options) // Loop, with the comment
-        });
-    }
-
-    // Send abuse report
-    options.kind = 'ABUSE';
-    return $scope.toggleLike(event, options)
-      .then(function() {
-        UIUtils.toast.show('COMMON.REPORT_ABUSE.CONFIRM.SENT')
-      })
-  };
-
-  csWallet.api.data.on.reset($scope, function() {
-    _.forEach($scope.options.like.kinds||[], function(kind) {
-      var key = kind.toLowerCase() + 's';
-      if ($scope.likeData[key]) {
-        $scope.likeData[key].wasHitByPubkey = {};
-        $scope.likeData[key].wasHitCount = 0;
-      }
-    })
-    $scope.$broadcast('$$rebind::like'); // notify binder
-  }, this);
-
-}
diff --git a/www/plugins/es/js/controllers/like-controllers.js b/www/plugins/es/js/controllers/like-controllers.js
new file mode 100644
index 0000000000000000000000000000000000000000..dec175e3e766321e753ae250394fcf157ed5439b
--- /dev/null
+++ b/www/plugins/es/js/controllers/like-controllers.js
@@ -0,0 +1,282 @@
+angular.module('cesium.es.like.controllers', ['ngResource', 'cesium.es.services'])
+
+  .controller('ESLikesCtrl', ESLikesController)
+;
+
+function ESLikesController($scope, $q, $timeout, $translate, $ionicPopup, UIUtils, Modals, csWallet, esHttp, esLike) {
+  'ngInject';
+
+  $scope.entered = false;
+  $scope.abuseData = {};
+  $scope.abuseLevels = [
+    {value: 1, label: 'LOW'},
+    {value: 2, label: 'LOW'}
+  ];
+  $scope.staring = false;
+  $scope.options = $scope.options || {};
+  $scope.options.like = $scope.options.like || {
+    kinds: esLike.constants.KINDS,
+    index: undefined,
+    type: undefined,
+    id: undefined
+  };
+
+  $scope.$on('$recordView.enter', function(e, state) {
+    // First enter
+    if (!$scope.entered) {
+      $scope.entered = false;
+      // Nothing to do: main controller will trigger '$recordView.load' event
+    }
+    // second call (e.g. if cache)
+    else if ($scope.id) {
+      $scope.loadLikes($scope.id);
+    }
+  });
+
+  $scope.$on('$recordView.load', function(event, id) {
+    $scope.id = id || $scope.id;
+    if ($scope.id) {
+      $scope.loadLikes($scope.id);
+    }
+  });
+
+  // Init Like service
+  $scope.initLikes = function() {
+    if (!$scope.likeData) {
+      throw new Error("Missing 'likeData' in scope. Cannot load likes counter");
+    }
+    if (!$scope.options.like.service) {
+      if (!$scope.options.like.index || !$scope.options.like.type) {
+        throw new Error("Missing 'options.like.index' or 'options.like.type' in scope. Cannot load likes counter");
+      }
+      $scope.options.like.service = esLike.instance($scope.options.like.index, $scope.options.like.type);
+    }
+    if (!$scope.options.like.kinds) {
+      // Get scope's kinds (e.g. defined in the parent scope)
+      $scope.options.like.kinds = _.filter(esLike.constants.KINDS, function (kind) {
+        var key = kind.toLowerCase() + 's';
+        return angular.isDefined($scope.likeData[key]);
+      });
+    }
+  };
+
+  $scope.loadLikes = function(id) {
+    if ($scope.likeData.loading) return;// Skip
+
+    id = id || $scope.likeData.id;
+    $scope.initLikes();
+
+    var kinds = $scope.options.like.kinds || [];
+    if (!kinds.length) return; // skip
+
+    $scope.likeData.loading = true;
+    var now = Date.now();
+    console.debug("[ES] Loading counter of {0}... ({1})".format(id.substring(0,8), kinds));
+
+    var issuers = csWallet.isLogin() ? csWallet.pubkeys() : undefined;
+
+    return $q.all(_.map(kinds, function(kind) {
+      var key = kind.toLowerCase() + 's';
+      return $scope.options.like.service.count(id, {issuers: issuers, kind: kind})
+        .then(function (res) {
+          // Store result to scope
+          if ($scope.likeData[key]) {
+            angular.merge($scope.likeData[key], res);
+          }
+        });
+    }))
+      .then(function () {
+        $scope.likeData.id = id;
+        console.debug("[ES] Loading counter of {0} [OK] in {1}ms".format(id.substring(0,8), Date.now()-now));
+
+        if (_.contains(kinds, 'VIEW') && !$scope.canEdit) {
+          $scope.markAsView();
+        }
+
+        // Publish to parent scope (to be able to use it in action popover's buttons)
+        if ($scope.$parent) {
+          console.debug("[ES] [likes] Adding data and functions to parent scope");
+          $scope.$parent.toggleLike = $scope.toggleLike;
+          $scope.$parent.reportAbuse = $scope.reportAbuse;
+        }
+
+        $scope.likeData.loading = false;
+      })
+      .catch(function (err) {
+        console.error(err && err.message || err);
+        $scope.likeData.loading = false;
+      });
+  };
+
+  $scope.toggleLike = function(event, options) {
+    $scope.initLikes();
+    if (!$scope.likeData.id) throw new Error("Missing 'likeData.id' in scope. Cannot apply toggle");
+
+    options = options || {};
+    options.kind = options.kind && options.kind.toUpperCase() || 'LIKE';
+    var key = options.kind.toLowerCase() + 's';
+
+    $scope.likeData[key] = $scope.likeData[key] || {};
+
+    // Avoid too many call
+    if ($scope.likeData[key].loading === true || $scope.likeData.loading) {
+      event.preventDefault();
+      return $q.reject();
+    }
+
+    // Select the wallet, if many
+    if (!options.pubkey) {
+      return (csWallet.children.count() ? Modals.showSelectWallet({displayBalance: false}) : $q.when(csWallet))
+        .then(function(wallet) {
+          if (!wallet) throw 'CANCELLED';
+          options.pubkey = wallet.data.pubkey;
+          return $scope.toggleLike(event, options); // Loop
+        });
+    }
+
+    var wallet = csWallet.getByPubkey(options.pubkey);
+    if (!wallet) return;
+
+    $scope.likeData[key].loading = true;
+    return wallet.auth({minData: true})
+      .then(function(walletData) {
+        if (!walletData) {
+          UIUtils.loading.hide();
+          return;
+        }
+
+        // Check if member account
+        if (!walletData.isMember) {
+          // TODO: enable this
+          //throw {message: "ERROR.ONLY_MEMBER_CAN_EXECUTE_THIS_ACTION"};
+        }
+
+        // Apply like
+        options.id = $scope.likeData.id;
+        return $scope.options.like.service.toggle($scope.likeData.id, options);
+      })
+      .then(function(delta) {
+        UIUtils.loading.hide();
+        if (delta !== 0) {
+          $scope.likeData[key].total = ($scope.likeData[key].total || 0) + delta;
+          $scope.likeData[key].wasHitByPubkey = $scope.likeData[key].wasHitByPubkey || {};
+          $scope.likeData[key].wasHitByPubkey[options.pubkey] = delta > 0;
+          $scope.likeData[key].wasHitCount += delta;
+        }
+        $timeout(function() {
+          $scope.likeData[key].loading = false;
+          $scope.$broadcast('$$rebind::like'); // notify binder
+        }, 1000);
+      })
+      .catch(function(err) {
+        $scope.likeData[key].loading = false;
+        if (err === 'CANCELLED') return; // User cancelled
+        console.error(err);
+        UIUtils.onError('LIKE.ERROR.FAILED_TOGGLE_LIKE')(err);
+        event.preventDefault();
+      });
+  };
+
+  $scope.setAbuseForm = function(form) {
+    $scope.abuseForm = form;
+  };
+
+  $scope.showAbuseCommentPopover = function(event) {
+    return $translate(['COMMON.REPORT_ABUSE.TITLE', 'COMMON.REPORT_ABUSE.SUB_TITLE','COMMON.BTN_SEND', 'COMMON.BTN_CANCEL'])
+      .then(function(translations) {
+
+        UIUtils.loading.hide();
+
+        return $ionicPopup.show({
+          templateUrl: 'plugins/es/templates/common/popup_report_abuse.html',
+          title: translations['COMMON.REPORT_ABUSE.TITLE'],
+          subTitle: translations['COMMON.REPORT_ABUSE.SUB_TITLE'],
+          cssClass: 'popup-report-abuse',
+          scope: $scope,
+          buttons: [
+            {
+              text: translations['COMMON.BTN_CANCEL'],
+              type: 'button-stable button-clear gray'
+            },
+            {
+              text: translations['COMMON.BTN_SEND'],
+              type: 'button button-positive  ink',
+              onTap: function(e) {
+                $scope.abuseForm.$submitted=true;
+                if(!$scope.abuseForm.$valid || !$scope.abuseData.comment) {
+                  //don't allow the user to close unless he enters a uid
+                  e.preventDefault();
+                } else {
+                  return $scope.abuseData;
+                }
+              }
+            }
+          ]
+        });
+      })
+      .then(function(res) {
+        $scope.abuseData = {};
+        if (!res || !res.comment) { // user cancel
+          UIUtils.loading.hide();
+          return undefined;
+        }
+        return res;
+      });
+  };
+
+  $scope.reportAbuse = function(event, options) {
+    if (!$scope.likeData || !$scope.likeData.abuses || $scope.likeData.abuses.wasHitCount) return; // skip
+    if ($scope.likeData.abuses.wasHitCount) return; // already report
+
+    options = options || {};
+
+    // Select the wallet, if many
+    if (!options.pubkey) {
+      return (csWallet.children.count() ? Modals.showSelectWallet({displayBalance: false}) : $q.when(csWallet))
+        .then(function(wallet) {
+          if (!wallet) throw 'CANCELLED';
+          options.pubkey = wallet.data.pubkey;
+          return $scope.reportAbuse(event, options); // Loop
+        });
+    }
+
+    var wallet = csWallet.getByPubkey(options.pubkey);
+
+    // Check if member account
+    if (!wallet || !wallet.isMember()) {
+      UIUtils.alert.info("ERROR.ONLY_MEMBER_CAN_EXECUTE_THIS_ACTION");
+      return;
+    }
+
+    if (!options.comment) {
+      // Ask a comment
+      return $scope.showAbuseCommentPopover(event)
+        // Loop, with options.comment filled
+        .then(function(res) {
+          if (!res || !res.comment) return; // Empty comment: skip
+          options.comment = res.comment;
+          options.level = res.level || (res.delete && 5) || undefined;
+          return $scope.reportAbuse(event, options) // Loop, with the comment
+        });
+    }
+
+    // Send abuse report
+    options.kind = 'ABUSE';
+    return $scope.toggleLike(event, options)
+      .then(function() {
+        UIUtils.toast.show('COMMON.REPORT_ABUSE.CONFIRM.SENT')
+      })
+  };
+
+  csWallet.api.data.on.reset($scope, function() {
+    _.forEach($scope.options.like.kinds||[], function(kind) {
+      var key = kind.toLowerCase() + 's';
+      if ($scope.likeData[key]) {
+        $scope.likeData[key].wasHitByPubkey = {};
+        $scope.likeData[key].wasHitCount = 0;
+      }
+    })
+    $scope.$broadcast('$$rebind::like'); // notify binder
+  }, this);
+
+}
diff --git a/www/plugins/es/js/plugin.js b/www/plugins/es/js/plugin.js
index c1b1dc07f908a45d59bbaba51397066ab4d9366b..05a69ab169be59e629611595a90d1b7c20519d83 100644
--- a/www/plugins/es/js/plugin.js
+++ b/www/plugins/es/js/plugin.js
@@ -18,6 +18,7 @@ angular.module('cesium.es.plugin', [
     'cesium.es.group.controllers',
     'cesium.es.invitation.controllers',
     'cesium.es.subscription.controllers',
-    'cesium.es.document.controllers'
+    'cesium.es.document.controllers',
+    'cesium.es.like.controllers'
   ])
 ;