diff --git a/platforms/desktop b/platforms/desktop
index e0512d76784980a51651bbdcba636404c552c3e8..b7a2318bcbe6a53487267e8efea75252ab81d851 160000
--- a/platforms/desktop
+++ b/platforms/desktop
@@ -1 +1 @@
-Subproject commit e0512d76784980a51651bbdcba636404c552c3e8
+Subproject commit b7a2318bcbe6a53487267e8efea75252ab81d851
diff --git a/www/css/style.css b/www/css/style.css
index b04d50500cffd20010460c370c08a90647acf80c..98eb8b00530660834077a1ff7c2c6005e5c8ea6d 100644
--- a/www/css/style.css
+++ b/www/css/style.css
@@ -181,6 +181,18 @@
   padding-right: 6px;
 }
 
+.list .item-peer.compacted {
+  padding-top: 0px;
+  padding-bottom: 0px;
+  min-height: 3px !important;
+  max-height: 3px !important;
+  border-bottom: double 1px #ddd !important;
+}
+.list .item-peer.compacted > * {
+  display: none;
+}
+
+
 /**********
    Block items
 **********/
diff --git a/www/i18n/locale-en-GB.json b/www/i18n/locale-en-GB.json
index 3a59b03c090aefbcbe1c7c168241348e0eb526f9..681aabdd04c6d26081c68b65d440ab5af70fc51d 100644
--- a/www/i18n/locale-en-GB.json
+++ b/www/i18n/locale-en-GB.json
@@ -47,7 +47,6 @@
     "CHOOSE_FILE": "Drag your file<br/>or click to select",
     "DAYS": "days",
     "NO_ACCOUNT_QUESTION": "Not a member yet? Register now!",
-    "HAVE_ACCOUNT_QUESTION": "Ya tienes una cuenta?",
     "SEARCH_NO_RESULT": "No result found",
     "LOADING": "Loading...",
     "LOADING_WAIT": "Loading...<br/><small>(Waiting for node availability)</small>",
@@ -302,15 +301,18 @@
     "PEERS": "Peers",
     "SIGNED_ON_BLOCK": "Signed on block",
     "MIRROR": "mirror",
-    "MIRRORS": "Mirror peers",
+    "MIRRORS": "Mirrors",
+    "MIRROR_PEERS": "Mirror peers",
     "PEER_LIST" : "Peer's list",
-    "MEMBERS" : "Member peers",
+    "MEMBERS" : "Members",
+    "MEMBER_PEERS" : "Member peers",
     "ALL_PEERS" : "All peers",
     "DIFFICULTY" : "Difficulty",
     "API" : "API",
     "CURRENT_BLOCK" : "Block #",
     "POPOVER_FILTER_TITLE": "Filter",
-    "OFFLINE": "Offline peers",
+    "OFFLINE": "Offline",
+    "OFFLINE_PEERS": "Offline peers",
     "BTN_SHOW_PEER": "Show peer",
     "VIEW": {
       "TITLE": "Peer",
@@ -410,6 +412,7 @@
     "PASSWORD_HELP": "Password",
     "PUBKEY_HELP": "Public key or pseudonym",
     "NO_ACCOUNT_QUESTION": "Don't have an account yet?",
+    "HAVE_ACCOUNT_QUESTION": "Already have an account ?",
     "CREATE_ACCOUNT": "Create an account",
     "CREATE_FREE_ACCOUNT": "Create a free account",
     "FORGOTTEN_ID": "Forgot password?",
diff --git a/www/i18n/locale-en.json b/www/i18n/locale-en.json
index 58187ba52267837ac3626854e84199690380de77..a9cc920ff94e084c9c4942abb81b1f593e9826f3 100644
--- a/www/i18n/locale-en.json
+++ b/www/i18n/locale-en.json
@@ -301,15 +301,18 @@
     "PEERS": "Peers",
     "SIGNED_ON_BLOCK": "Signed on block",
     "MIRROR": "mirror",
-    "MIRRORS": "Mirror peers",
+    "MIRRORS": "Mirrors",
+    "MIRROR_PEERS": "Mirror peers",
     "PEER_LIST" : "Peer's list",
-    "MEMBERS" : "Member peers",
+    "MEMBERS" : "Members",
+    "MEMBER_PEERS" : "Member peers",
     "ALL_PEERS" : "All peers",
     "DIFFICULTY" : "Difficulty",
     "API" : "API",
     "CURRENT_BLOCK" : "Block #",
     "POPOVER_FILTER_TITLE": "Filter",
-    "OFFLINE": "Offline peers",
+    "OFFLINE": "Offline",
+    "OFFLINE_PEERS": "Offline peers",
     "BTN_SHOW_PEER": "Show peer",
     "VIEW": {
       "TITLE": "Peer",
diff --git a/www/i18n/locale-es-ES.json b/www/i18n/locale-es-ES.json
index 97b26b6a83a1214bb50dc21d36ef6ca75ab2b2db..863ef935ab7acd89ad9ab4472d545f64e3cf6571 100644
--- a/www/i18n/locale-es-ES.json
+++ b/www/i18n/locale-es-ES.json
@@ -291,15 +291,18 @@
     "PEERS": "Nodos",
     "SIGNED_ON_BLOCK": "Firmado sobre el bloque",
     "MIRROR": "espejo",
-    "MIRRORS": "Nodos espejo",
+    "MIRRORS": "Espejo",
+    "MIRROR_PEERS": "Nodos espejo",
     "PEER_LIST": "Lista de nodos",
-    "MEMBERS": "Nodos miembro",
+    "MEMBERS": "Miembro",
+    "MEMBER_PEERS": "Nodos miembro",
     "ALL_PEERS": "Todos los nodos",
     "DIFFICULTY": "Dificultad",
     "API": "API",
     "CURRENT_BLOCK": "Bloque #",
     "POPOVER_FILTER_TITLE": "Filtro",
-    "OFFLINE": "Nodos fuera de línea",
+    "OFFLINE": "Fuera de línea",
+    "OFFLINE_PEERS": "Nodos fuera de línea",
     "BTN_SHOW_PEER": "Ver nodo",
     "VIEW": {
       "TITLE": "Nodo",
diff --git a/www/i18n/locale-fr-FR.json b/www/i18n/locale-fr-FR.json
index 789ab385b6dfef40654075aa93d5d2fb96349af3..77dd988a3c6aa76412e6e99e3ecf9c82b15ad1b4 100644
--- a/www/i18n/locale-fr-FR.json
+++ b/www/i18n/locale-fr-FR.json
@@ -301,15 +301,18 @@
     "PEERS": "Nœuds",
     "SIGNED_ON_BLOCK": "Signé sur le bloc",
     "MIRROR": "miroir",
-    "MIRRORS": "Nœuds miroirs",
+    "MIRRORS": "Miroirs",
+    "MIRROR_PEERS": "Nœuds miroirs",
     "PEER_LIST" : "Liste des nœuds",
-    "MEMBERS" : "Nœuds membres",
+    "MEMBERS" : "Membres",
+    "MEMBER_PEERS" : "Nœuds membres",
     "ALL_PEERS" : "Tous les nœuds",
     "DIFFICULTY" : "Difficulté",
     "API" : "API",
     "CURRENT_BLOCK" : "Bloc #",
     "POPOVER_FILTER_TITLE": "Filtre",
-    "OFFLINE": "Nœuds hors ligne",
+    "OFFLINE": "Hors ligne",
+    "OFFLINE_PEERS": "Nœuds hors ligne",
     "BTN_SHOW_PEER": "Voir le nœud",
     "VIEW": {
       "TITLE": "Nœud",
diff --git a/www/i18n/locale-it-IT.json b/www/i18n/locale-it-IT.json
index 20f1c75d7ca338e3dff2a15b223295f8d6c0c71c..c8e5f1466442de8806b8f69f59a14e17ef6e0116 100644
--- a/www/i18n/locale-it-IT.json
+++ b/www/i18n/locale-it-IT.json
@@ -287,15 +287,18 @@
      "PEERS": "Peers-Nodi",
      "SIGNED_ON_BLOCK": "Firmato nel blocco",
      "MIRROR": "Specchio",
-     "MIRRORS": "Peers specchio",
+     "MIRRORS": "Specchio",
+     "MIRROR_PEERS": "Peers specchio",
      "PEER_LIST" : "Lista dei peers",
-     "MEMBERS" : "Peers membri",
+     "MEMBERS" : "Membri",
+     "MEMBER_PEERS" : "Peers membri",
      "ALL_PEERS" : "Tutti i peers",
      "DIFFICULTY" : "Difficoltà",
      "API" : "API",
      "CURRENT_BLOCK" : "Blocco #",
      "POPOVER_FILTER_TITLE": "Filtro",
-     "OFFLINE": "Peers sconessi",
+     "OFFLINE": "Sconessi",
+     "OFFLINE_PEERS": "Peers sconessi",
      "BTN_SHOW_PEER": "Mostrare peer",
      "VIEW": {
        "TITLE": "Peer",
diff --git a/www/js/controllers/blockchain-controllers.js b/www/js/controllers/blockchain-controllers.js
index fd32c6cb0a2b8d27a0e7756884a0d834d744b257..4c71d672ca136739daee52a5929083c49326725e 100644
--- a/www/js/controllers/blockchain-controllers.js
+++ b/www/js/controllers/blockchain-controllers.js
@@ -135,10 +135,9 @@ function BlockLookupController($scope, $timeout, $focus, $filter, $state, $ancho
           useTor: useTor
         };
         var serverParts = state.stateParams.server.split(':');
-        if (serverParts.length == 2 || serverParts.length == 3) {
+        if (serverParts.length == 2) {
           node.host = serverParts[0];
           node.port = serverParts[1];
-          node.wsPort = serverParts[2] || serverParts[1];
         }
 
         if (BMA.node.same(node.host, node.port)) {
@@ -147,8 +146,8 @@ function BlockLookupController($scope, $timeout, $focus, $filter, $state, $ancho
         else {
           $scope.node = useTor ?
               // For TOR, use a web2tor to access the endpoint
-              BMA.instance(node.host + ".to", 443, 443, true/*ssl*/, 600000 /*long timeout*/) :
-              BMA.instance(node.host, node.port, node.wsPort, node.useSsl);
+              BMA.instance(node.host + ".to", 443, true/*ssl*/, 600000 /*long timeout*/) :
+              BMA.instance(node.host, node.port, node.useSsl);
           return $scope.node.blockchain.parameters()
             .then(function(json) {
               $scope.currency = json.currency;
@@ -530,10 +529,9 @@ function BlockViewController($scope, $ionicPopover, $state, UIUtils, BMA, csCurr
           useTor: useTor
         };
         var serverParts = state.stateParams.server.split(':');
-        if (serverParts.length == 2 || serverParts.length == 3) {
+        if (serverParts.length == 2) {
           node.host = serverParts[0];
           node.port = serverParts[1];
-          node.wsPort = serverParts[2] || serverParts[1];
         }
 
         if (BMA.node.same(node.host, node.port)) {
@@ -542,8 +540,8 @@ function BlockViewController($scope, $ionicPopover, $state, UIUtils, BMA, csCurr
         else {
           $scope.node = useTor ?
             // For TOR, use a web2tor to access the endpoint
-            BMA.instance(node.host + ".to", 443, 443, true/*ssl*/, 600000 /*long timeout*/) :
-            BMA.instance(node.host, node.port, node.wsPort, node.useSsl);
+            BMA.instance(node.host + ".to", 443, true/*ssl*/, 600000 /*long timeout*/) :
+            BMA.instance(node.host, node.port, node.useSsl);
           return $scope.node.blockchain.parameters()
             .then(function (json) {
               $scope.currency = json.currency;
diff --git a/www/js/controllers/network-controllers.js b/www/js/controllers/network-controllers.js
index 77b9d80a73ab8c83397a968104667f92f2e38857..cfc96a8855583d4854515e3859fd25e3f33bd88e 100644
--- a/www/js/controllers/network-controllers.js
+++ b/www/js/controllers/network-controllers.js
@@ -60,13 +60,17 @@ function NetworkLookupController($scope,  $state, $location, $ionicPopover, $win
     loading: true,
     type: undefined,
     results: [],
-    endpointFilter: null,
+    endpoint: null,
+    bma: undefined,
+    ssl: undefined,
+    ws2p: undefined,
     sort : undefined,
     asc: true
   };
+  $scope.compactMode = true;
   $scope.listeners = [];
   $scope.helptipPrefix = 'helptip-network';
-  $scope.eanbleLocationHref = true; // can be overrided by sub-controler (e.g. popup)
+  $scope.enableLocationHref = true; // can be overrided by sub-controler (e.g. popup)
 
   $scope.removeListeners = function() {
     if ($scope.listeners.length) {
@@ -89,7 +93,7 @@ function NetworkLookupController($scope,  $state, $location, $ionicPopover, $win
       .then(function (currency) {
         if (currency) {
           $scope.node = !BMA.node.same(currency.node.host, currency.node.port) ?
-            BMA.instance(currency.node.host, currency.node.port, currency.node.wsPort) : BMA;
+            BMA.instance(currency.node.host, currency.node.port) : BMA;
           if (state && state.stateParams) {
             if (state.stateParams.type && ['mirror', 'member', 'offline'].indexOf(state.stateParams.type) != -1) {
               $scope.search.type = state.stateParams.type;
@@ -128,7 +132,10 @@ function NetworkLookupController($scope,  $state, $location, $ionicPopover, $win
       filter: {
         member: (!$scope.search.type || $scope.search.type === 'member'),
         mirror: (!$scope.search.type || $scope.search.type === 'mirror'),
-        endpointFilter : (angular.isDefined($scope.search.endpointFilter) ? $scope.search.endpointFilter : null),
+        endpoint : (angular.isDefined($scope.search.endpoint) ? $scope.search.endpoint : null),
+        bma : $scope.search.bma,
+        ssl : $scope.search.ssl,
+        ws2p : $scope.search.ws2p,
         online: !($scope.search.type && $scope.search.type === 'offline')
       },
       sort: {
@@ -145,24 +152,25 @@ function NetworkLookupController($scope,  $state, $location, $ionicPopover, $win
   $scope.load = function() {
 
     if ($scope.search.loading){
+      // Start network scan
       csNetwork.start($scope.node, $scope.computeOptions());
 
       // Catch event on new peers
       $scope.refreshing = false;
       $scope.listeners.push(
         csNetwork.api.data.on.changed($scope, function(data){
-        if (!$scope.refreshing) {
-          $scope.refreshing = true;
-          csWot.extendAll(data.peers)
-            .then(function() {
-              // Avoid to refresh if view has been leaving
-              if ($scope.networkStarted) {
-                $scope.updateView(data);
-              }
-              $scope.refreshing = false;
-            });
-        }
-      }));
+          if (!$scope.refreshing) {
+            $scope.refreshing = true;
+            csWot.extendAll(data.peers)
+              .then(function() {
+                // Avoid to refresh if view has been leaving
+                if ($scope.networkStarted) {
+                  $scope.updateView(data);
+                }
+                $scope.refreshing = false;
+              });
+          }
+        }));
     }
 
     // Show help tip
@@ -217,11 +225,11 @@ function NetworkLookupController($scope,  $state, $location, $ionicPopover, $win
 
   $scope.toggleSearchEndpoint = function(endpoint){
     $scope.hideActionsPopover();
-    if ($scope.search.endpointFilter === endpoint || endpoint === null) {
-      $scope.search.endpointFilter = null;
+    if ($scope.search.endpoint === endpoint || endpoint === null) {
+      $scope.search.endpoint = null;
     }
     else {
-      $scope.search.endpointFilter = endpoint;
+      $scope.search.endpoint = endpoint;
     }
     $scope.sort();
   };
@@ -238,9 +246,19 @@ function NetworkLookupController($scope,  $state, $location, $ionicPopover, $win
     $scope.sort();
   };
 
+  $scope.toggleCompactMode = function() {
+    $scope.compactMode = !$scope.compactMode;
+    $scope.$broadcast('$$rebind::' + 'rebind'); // force data binding
+  };
+
   $scope.selectPeer = function(peer) {
-    // Skipp offline or WS2P node
-    if (!peer.online || peer.isWs2p()) return;
+    if (peer.compacted && $scope.compactMode) {
+      $scope.toggleCompactMode();
+      return;
+    }
+
+    // Skipp offline or not a BMA peer
+    if (!peer.online || !peer.hasBma()) return;
 
     var stateParams = {server: peer.getServer()};
     if (peer.isSsl()) {
@@ -253,7 +271,7 @@ function NetworkLookupController($scope,  $state, $location, $ionicPopover, $win
   };
 
   $scope.$on('csView.action.refresh', function(event, context) {
-    if (context == 'peers') {
+    if (context === 'peers') {
       $scope.refresh();
     }
   });
@@ -288,8 +306,8 @@ function NetworkLookupController($scope,  $state, $location, $ionicPopover, $win
     }
   };
 
-  $scope.showEndpointsPopover = function($event, peer, endpointFilter) {
-    var endpoints = peer.getEndpoints(endpointFilter);
+  $scope.showEndpointsPopover = function($event, peer, endpoint) {
+    var endpoints = peer.getEndpoints(endpoint);
     endpoints = (endpoints||[]).reduce(function(res, ep) {
         var bma = BMA.node.parseEndPoint(ep);
         return res.concat({
@@ -302,7 +320,7 @@ function NetworkLookupController($scope,  $state, $location, $ionicPopover, $win
     UIUtils.popover.show($event, {
       templateUrl: 'templates/network/popover_endpoints.html',
       bindings: {
-        titleKey: 'NETWORK.VIEW.ENDPOINTS.' + endpointFilter,
+        titleKey: 'NETWORK.VIEW.ENDPOINTS.' + endpoint,
         items: endpoints
       }
     });
@@ -374,7 +392,10 @@ function NetworkLookupModalController($scope, $controller, parameters) {
   parameters = parameters || {};
   $scope.enableFilter = angular.isDefined(parameters.enableFilter) ? parameters.enableFilter : true;
   $scope.search.type = angular.isDefined(parameters.type) ? parameters.type : $scope.search.type;
-  $scope.search.endpointFilter = angular.isDefined(parameters.endpointFilter) ? parameters.endpointFilter : $scope.search.endpointFilter;
+  $scope.search.endpoint = angular.isDefined(parameters.endpoint) ? parameters.endpoint : $scope.search.endpoint;
+  $scope.search.bma = angular.isDefined(parameters.bma) ? parameters.bma : $scope.search.bma;
+  $scope.search.ssl = angular.isDefined(parameters.ssl) ? parameters.ssl : $scope.search.ssl;
+  $scope.search.ws2p = angular.isDefined(parameters.ws2p) ? parameters.ws2p : $scope.search.ws2p;
   $scope.expertMode = angular.isDefined(parameters.expertMode) ? parameters.expertMode : $scope.expertMode;
   $scope.ionItemClass = parameters.ionItemClass || 'item-border-large';
   $scope.enableLocationHref = false;
@@ -406,7 +427,7 @@ function NetworkLookupPopoverController($scope, $controller) {
   var parameters = parameters || {};
   $scope.enableFilter = angular.isDefined(parameters.enableFilter) ? parameters.enableFilter : true;
   $scope.search.type = angular.isDefined(parameters.type) ? parameters.type : $scope.search.type;
-  $scope.search.endpointFilter = angular.isDefined(parameters.endpointFilter) ? parameters.endpointFilter : $scope.search.endpointFilter;
+  $scope.search.endpoint = angular.isDefined(parameters.endpoint) ? parameters.endpoint : $scope.search.endpoint;
   $scope.expertMode = angular.isDefined(parameters.expertMode) ? parameters.expertMode : $scope.expertMode;
   $scope.ionItemClass = parameters.ionItemClass || 'item-border-large';
   $scope.helptipPrefix = '';
@@ -552,14 +573,13 @@ function PeerViewController($scope, $q, $window, $state, UIUtils, csWot, BMA) {
     if (serverParts.length == 2) {
       node.host = serverParts[0];
       node.port = serverParts[1];
-      node.wsPort = serverParts[2] || serverParts[1];
     }
 
     angular.merge($scope.node,
       useTor ?
         // For TOR, use a web2tor to access the endpoint
-        BMA.lightInstance(node.host + ".to", 443, 443, true/*ssl*/, 60000 /*long timeout*/) :
-        BMA.lightInstance(node.host, node.port, node.wsPort,  node.useSsl),
+        BMA.lightInstance(node.host + ".to", 443, true/*ssl*/, 60000 /*long timeout*/) :
+        BMA.lightInstance(node.host, node.port, node.useSsl),
       node);
 
     $scope.isReachable = !$scope.isHttps || useSsl;
@@ -612,8 +632,9 @@ function PeerViewController($scope, $q, $window, $state, UIUtils, csWot, BMA) {
         .then(function(json) {
           var peers = json.peers.map(function (p) {
             var peer = new Peer(p);
-            peer.online = p.status == 'UP';
-            peer.blockNumber = peer.block.replace(/-.+$/, '');
+            peer.online = p.status === 'UP';
+            peer.buid = peer.block;
+            peer.blockNumber = peer.buid && peer.buid.split('-')[0];
             peer.dns = peer.getDns();
             peer.id = peer.keyID();
             peer.server = peer.getServer();
diff --git a/www/js/controllers/settings-controllers.js b/www/js/controllers/settings-controllers.js
index eac077076c402df1efc9ff928b5ae208780dc07f..56f53743ca3f7906865234e8eef6af5bf9cb10b9 100644
--- a/www/js/controllers/settings-controllers.js
+++ b/www/js/controllers/settings-controllers.js
@@ -20,8 +20,8 @@ angular.module('cesium.settings.controllers', ['cesium.services', 'cesium.curren
   .controller('SettingsCtrl', SettingsController)
 ;
 
-function SettingsController($scope, $q, $ionicHistory, $ionicPopup, $timeout, $translate, $ionicPopover,
-                            UIUtils, Modals, BMA, csHttp, csCurrency, csSettings, csPlatform) {
+function SettingsController($scope, $q, $window, $ionicHistory, $ionicPopup, $timeout, $translate, $ionicPopover,
+                            UIUtils, Modals, BMA, csHttp, csConfig, csCurrency, csSettings, csPlatform) {
   'ngInject';
 
   $scope.formData = angular.copy(csSettings.data);
@@ -193,8 +193,16 @@ function SettingsController($scope, $q, $ionicHistory, $ionicPopup, $timeout, $t
   };
 
   $scope.showNodeList = function() {
+    // Check if need a filter on SSL node
+    var forceUseSsl = (csConfig.httpsMode === 'true' || csConfig.httpsMode === true || csConfig.httpsMode === 'force') ||
+    ($window.location && $window.location.protocol === 'https:') ? true : false;
+
     $ionicPopup._popupStack[0].responseDeferred.promise.close();
-    return Modals.showNetworkLookup({enableFilter: true, type: 'member'})
+    return Modals.showNetworkLookup({
+      enableFilter: true, // enable filter button
+      bma: true, // only BMA node
+      ssl: forceUseSsl ? true : undefined
+    })
       .then(function (peer) {
         if (peer) {
           var bma = peer.getBMA();
@@ -202,7 +210,7 @@ function SettingsController($scope, $q, $ionicHistory, $ionicPopup, $timeout, $t
             host: (bma.dns ? bma.dns :
                    (peer.hasValid4(bma) ? bma.ipv4 : bma.ipv6)),
             port: bma.port || 80,
-            useSsl: bma.useSsl
+            useSsl: bma.useSsl || bma.port == 443
           };
         }
       })
diff --git a/www/js/entities/peer.js b/www/js/entities/peer.js
index cd9649e3caab64f1112122bd1e7834b83c6c3110..7bd9491e648f0acd4144d88734f65910262b820e 100644
--- a/www/js/entities/peer.js
+++ b/www/js/entities/peer.js
@@ -101,7 +101,6 @@ Peer.prototype.hasEndpoint = function(endpoint){
   var endpoints = this.getEndpoints(regExp);
   if (!endpoints.length) return false;
   else return true;
-
 };
 
 Peer.prototype.getDns = function() {
@@ -166,7 +165,6 @@ Peer.prototype.isTor = function() {
   return bma.useTor;
 };
 
-
 Peer.prototype.isWs2p = function() {
   var bma = this.bma || this.getBMA();
   return bma.useWs2p;
@@ -176,3 +174,7 @@ Peer.prototype.isBma = function() {
   var bma = this.bma || this.getBMA();
   return !bma.useWs2p && !bma.useTor;
 };
+
+Peer.prototype.hasBma = function() {
+  return this.hasEndpoint('(BASIC_MERKLE_API|BMAS|BMATOR)');
+};
diff --git a/www/js/services/bma-services.js b/www/js/services/bma-services.js
index 3a8f2aa6cf363b6801e4cfa753b62a9ee21f491c..658a619ec652a8db6aebee46e4e89e2a6b2deb2b 100644
--- a/www/js/services/bma-services.js
+++ b/www/js/services/bma-services.js
@@ -20,21 +20,28 @@ angular.module('cesium.bma.services', ['ngApi', 'cesium.http.services', 'cesium.
       OUTPUT_OBJ = 'OBJ\\(([0-9]+)\\)',
       OUTPUT_OBJ_OPERATOR = OUTPUT_OBJ + '[ ]*' + OUTPUT_OPERATOR + '[ ]*' + OUTPUT_OBJ,
       REGEX_ENDPOINT_PARAMS = "( ([a-z_][a-z0-9-_.ÄŸÄž]*))?( ([0-9.]+))?( ([0-9a-f:]+))?( ([0-9]+))( (.+))?",
+      api = {
+        BMA: 'BASIC_MERKLED_API',
+        BMAS: 'BMAS',
+        WS2P: 'WS2P',
+        BMATOR: 'BMATOR',
+        WS2PTOR: 'WS2PTOR'
+      },
       regexp = {
         USER_ID: "[A-Za-z0-9_-]+",
         CURRENCY: "[A-Za-z0-9_-]+",
         PUBKEY: pubkey,
-        PUBKEY_WITH_CHECKSUM: "(" + pubkey +")" + ":([123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{3})",
+        PUBKEY_WITH_CHECKSUM: "(" + pubkey +"):([123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz]{3})",
         COMMENT: "[ a-zA-Z0-9-_:/;*\\[\\]()?!^\\+=@&~#{}|\\\\<>%.]*",
         INVALID_COMMENT_CHARS: "[^ a-zA-Z0-9-_:/;*\\[\\]()?!^\\+=@&~#{}|\\\\<>%.]*",
         // duniter://[uid]:[pubkey]@[host]:[port]
         URI_WITH_AT: "duniter://(?:([A-Za-z0-9_-]+):)?("+pubkey+"@([a-zA-Z0-9-.]+.[ a-zA-Z0-9-_:/;*?!^\\+=@&~#|<>%.]+)",
         URI_WITH_PATH: "duniter://([a-zA-Z0-9-.]+.[a-zA-Z0-9-_:.]+)/("+pubkey+")(?:/([A-Za-z0-9_-]+))?",
         BMA_ENDPOINT: "BASIC_MERKLED_API" + REGEX_ENDPOINT_PARAMS,
-        BMAS_ENDPOINT: "BMAS" + REGEX_ENDPOINT_PARAMS,
-        WS2P_ENDPOINT: "WS2P ([a-f0-9]{8})"+ REGEX_ENDPOINT_PARAMS,
-        BMATOR_ENDPOINT: "BMATOR ([a-z0-9-_.]*|[0-9.]+|[0-9a-f:]+.onion)(?: ([0-9]+))?",
-        WS2PTOR_ENDPOINT: "WS2PTOR ([a-f0-9]{8}) ([a-z0-9-_.]*|[0-9.]+|[0-9a-f:]+.onion)(?: ([0-9]+))?(?: (.+))?"
+        BMAS_ENDPOINT: api.BMAS + REGEX_ENDPOINT_PARAMS,
+        WS2P_ENDPOINT: api.WS2P + " ([a-f0-9]{8})"+ REGEX_ENDPOINT_PARAMS,
+        BMATOR_ENDPOINT: api.BMATOR + " ([a-z0-9-_.]*|[0-9.]+|[0-9a-f:]+.onion)(?: ([0-9]+))?",
+        WS2PTOR_ENDPOINT: api.WS2PTOR + " ([a-f0-9]{8}) ([a-z0-9-_.]*|[0-9.]+|[0-9a-f:]+.onion)(?: ([0-9]+))?(?: (.+))?"
       },
       errorCodes = {
         REVOCATION_ALREADY_REGISTERED: 1002,
@@ -58,8 +65,8 @@ angular.module('cesium.bma.services', ['ngApi', 'cesium.http.services', 'cesium.
         ROOT_BLOCK_HASH: 'E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855',
         LIMIT_REQUEST_COUNT: 5, // simultaneous async request to a Duniter node
         LIMIT_REQUEST_DELAY: 1000, // time (in second) to wait between to call of a rest request
-        regex: regexp, // deprecated
-        regexp: regexp
+        regexp: regexp,
+        api: api
       },
       listeners,
       that = this;
@@ -921,9 +928,7 @@ angular.module('cesium.bma.services', ['ngApi', 'cesium.http.services', 'cesium.
   var service = new BMA(undefined, undefined, undefined, true);
 
   service.instance = function(host, port, useSsl, useCache) {
-    var bma = new BMA();
-    bma.init(host, port, useSsl, useCache);
-    return bma;
+    return new BMA(host, port, useSsl, useCache);
   };
 
   service.lightInstance = function(host, port, useSsl, timeout) {
diff --git a/www/js/services/http-services.js b/www/js/services/http-services.js
index a871ce92c0ed85cb51ccfb97234f0c4a76bb3e23..25974e689047533df16f26a601a8240b2e01511a 100644
--- a/www/js/services/http-services.js
+++ b/www/js/services/http-services.js
@@ -166,11 +166,11 @@ angular.module('cesium.http.services', ['cesium.cache.services'])
       }
 
       if (self.waitDuration >= timeout) {
-        console.debug("[http] Will retry opening websocket later...");
-        self.waitRetryDelay = 2000; // 2 seconds
+        self.waitRetryDelay = self.waitRetryDelay && Math.min(self.waitRetryDelay + 2000, 30000) || 2000; // add 2 seconds, until 30s)
+        console.debug("[http] Will retry websocket [{0}] in {1}s...".format(self.path, Math.round(self.waitRetryDelay/1000)));
       }
-      else {
-        console.debug('[http] Waiting websocket ['+self.path+'] opening...');
+      else if (Math.round(self.waitDuration / 1000) % 10 === 0){
+        console.debug('[http] Waiting websocket ['+self.path+']...');
       }
 
       return $timeout(function(){
@@ -202,7 +202,7 @@ angular.module('cesium.http.services', ['cesium.cache.services'])
             sockets.push(self);
             self.delegate.openTime = Date.now();
           };
-          self.delegate.onclose = function() {
+          self.delegate.onclose = function(closeEvent) {
 
             // Remove from sockets arrays
             var index = _.findIndex(sockets, function(socket){return socket.path === self.path;});
@@ -217,12 +217,24 @@ angular.module('cesium.http.services', ['cesium.cache.services'])
 
             // If unexpected close event, reopen the socket (fix #535)
             else {
-              console.debug('[http] Unexpected close of websocket ['+path+'] (open '+ (Date.now() - self.delegate.openTime) +'ms ago): re-opening...');
+              if (self.delegate.openTime) {
+                console.debug('[http] Unexpected close of websocket [{0}] (open {1} ms ago): re-opening...', path, (Date.now() - self.delegate.openTime));
 
-              self.delegate = null;
+                // Force new connection
+                self.delegate = null;
+
+                // Loop, but without the already registered callback
+                _open(self, null, params);
+              }
+              else if (closeEvent) {
+                console.debug('[http] TODO -- Unexpected close of websocket [{0}]: error code: '.format(path), closeEvent);
+
+                // Force new connection
+                self.delegate = null;
 
-              // Loop, but without the already registered callback
-              _open(self, null, params);
+                // Loop, but without the already registered callback
+                _open(self, null, params);
+              }
             }
           };
         });
@@ -472,8 +484,7 @@ angular.module('cesium.http.services', ['cesium.cache.services'])
   }
 
   function isVersionCompatible(minVersion, actualVersion) {
-    // TODO: add implementation
-    console.debug('[http] TODO: implement check version [{0}] compatible with [{1}]'.format(actualVersion, minVersion));
+    console.debug('[http] Checking actual version [{0}] is compatible with min expected version [{1}]'.format(actualVersion, minVersion));
     return compareVersionNumbers(minVersion, actualVersion) <= 0;
   }
 
diff --git a/www/js/services/network-services.js b/www/js/services/network-services.js
index 1484840cdbc45ef36f6e13ca2839a66943571b7e..00c5945e0f397dbbbd4de4dd113e32c28470f714 100644
--- a/www/js/services/network-services.js
+++ b/www/js/services/network-services.js
@@ -1,15 +1,16 @@
 
-angular.module('cesium.network.services', ['ngApi', 'cesium.bma.services', 'cesium.http.services'])
+angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', 'cesium.http.services'])
 
-.factory('csNetwork', function($rootScope, $q, $interval, $timeout, $window, csConfig, BMA, csHttp, Api) {
+.factory('csNetwork', function($rootScope, $q, $interval, $timeout, $window, csConfig, BMA, csHttp, csCurrency, Api) {
   'ngInject';
 
-  factory = function(id) {
+  function csNetwork(id) {
 
     var
       interval,
       constants = {
-        UNKNOWN_BUID: -1
+        UNKNOWN_BUID: -1,
+        MAX_BLOCK_OFFSET: 1000
       },
       isHttpsMode = $window.location.protocol === 'https:',
       api = new Api(this, "csNetwork-" + id),
@@ -22,18 +23,22 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.bma.services', 'cesi
         filter: {
           member: true,
           mirror: true,
-          endpointFilter: null,
+          endpoint: null,
           online: false,
+          bma: false,
           ssl: undefined,
           tor: undefined
         },
         sort:{
           type: null,
-          asc: true
+          asc: true,
+          compact: true
         },
+        groupBy: 'pubkey',
         expertMode: false,
         knownBlocks: [],
         mainBlock: null,
+        minOnlineBlockNumber: 0,
         uidsByPubkeys: null,
         searchingPeersOnNetwork: false,
         difficulties: null,
@@ -53,17 +58,22 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.bma.services', 'cesi
         data.filter = {
           member: true,
           mirror: true,
-          endpointFilter: null,
-          online: true
+          endpoint: null,
+          online: true,
+          bma: false,
+          ssl: undefined,
+          tor: undefined
         };
         data.sort = {
           type: null,
           asc: true
         };
+        data.groupBy = 'pubkey';
         data.expertMode = false;
         data.memberPeersCount = 0;
         data.knownBlocks = [];
         data.mainBlock = null;
+        data.minOnlineBlockNumber = 0;
         data.uidsByPubkeys = {};
         data.loading = true;
         data.searchingPeersOnNetwork = false;
@@ -172,8 +182,8 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.bma.services', 'cesi
               data.uidsByPubkeys = uids;
             })
             .catch(function(err) {
+              console.error(err);
               data.uidsByPubkeys = {};
-              //throw err;
             }),
 
           // Load WS2P heads
@@ -186,99 +196,96 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.bma.services', 'cesi
         }
 
         return $q.all(initJobs)
-          .then(function(){
-            // online nodes
+          .then(function() {
+            return data.bma.network.peers();
+          })
+          .then(function(res){
+            if (!res || !res.peers || !res.peers.length) return;
+
+            // If filter online peers
             if (data.filter.online) {
-              /*return data.bma.network.peering.peers({leaves: true})
-                .then(function(res){
-                  return $q.all(res.leaves.map(function(leaf) {
-                    return data.bma.network.peering.peers({ leaf: leaf })
-                      .then(function(subres){
-                        return addOrRefreshPeerFromJson(subres.leaf.value, newPeers);
-                      });
-                  }));
-                });*/
-              return data.bma.network.peers()
-                .then(function(res){
-                  var jobs = [];
-                  _.forEach(res.peers, function(json) {
-                    if (json.status == 'UP') {
-                      jobs.push(addOrRefreshPeerFromJson(json, newPeers));
-
-                      // Mark WS2P
-                      _.forEach(json.endpoints||[], function(ep) {
-                        if (ep.startsWith('WS2P')) {
-                          var key = json.pubkey + '-' + ep.split(' ')[1];
-                          if (data.ws2pHeads[key]) {
-                            data.ws2pHeads[key].hasEndpoint = true;
-                          }
-                        }
-                      });
-                    }
-                  });
-
-                  // Add private WS2P endpoints
-                  var privateWs2pHeads = _.values(data.ws2pHeads);
-                  if (privateWs2pHeads && privateWs2pHeads.length) {
-                    var privateEPCount = 0;
-                    //console.debug("[http] Found WS2P endpoints without endpoint:", data.ws2pHeads);
-                    _.forEach(privateWs2pHeads, function(head) {
-                      if (!head.hasEndPoint) {
-                        var peer = new Peer({
-                          buid: head.buid,
-                          currentNumber: head.buid && head.buid.split('-')[0],
-                          pubkey: head.pubkey,
-                          version: head.version,
-                          powPrefix: head.powPrefix,
-                          online: true,
-                          uid: data.uidsByPubkeys[head.pubkey],
-                          bma: {
-                            useWs2p: true,
-                            private: true,
-                            ws2pid: head.ws2pid
-                          },
-                          endpoints: [
-                            // fake endpoint
-                            'WS2P ' + head.ws2pid
-                          ]
-                        });
-                        peer.id = peer.keyID();
-                        if (peer.uid && data.expertMode && data.difficulties) {
-                          peer.difficulty = data.difficulties[peer.uid];
-                        }
-                        if (applyPeerFilter(peer)) {
-                          newPeers.push(peer);
-                          privateEPCount++;
-                        }
-                      }
-                    });
+              var jobs = [];
+              _.forEach(res.peers, function(json) {
+                // Exclude, if not UP or on a too old block
+                if (json.status !== 'UP') return;
+                json.blockNumber = json.block && parseInt(json.block.split('-')[0]);
+                if (json.blockNumber && json.blockNumber < data.minOnlineBlockNumber) {
+                  console.debug("[network] Exclude a too old peer document, on pubkey {0}".format(json.pubkey.substring(0,6)));
+                  return;
+                }
 
-                    if (privateEPCount) {
-                      console.debug("[http] Found {0} WS2P endpoints without endpoint (private ?)".format(privateEPCount));
+                jobs.push(addOrRefreshPeerFromJson(json, newPeers));
+
+                // Mark WS2P
+                _.forEach(json.endpoints||[], function(ep) {
+                  if (ep.startsWith('WS2P')) {
+                    var key = json.pubkey + '-' + ep.split(' ')[1];
+                    if (data.ws2pHeads[key]) {
+                      data.ws2pHeads[key].hasEndpoint = true;
                     }
                   }
-
-                  if (jobs.length) return $q.all(jobs);
-                })
-                .catch(function(err) {
-                  // Log and continue
-                  console.error(err);
                 });
-            }
+              });
 
-            // offline nodes
-            return data.bma.network.peers()
-              .then(function(res){
-                var jobs = [];
-                _.forEach(res.peers, function(json) {
-                  if (json.status !== 'UP') {
-                    jobs.push(addOrRefreshPeerFromJson(json, newPeers));
+              // Add private WS2P endpoints
+              var privateWs2pHeads = _.values(data.ws2pHeads);
+              if (privateWs2pHeads && privateWs2pHeads.length) {
+                var privateEPCount = 0;
+                //console.debug("[http] Found WS2P endpoints without endpoint:", data.ws2pHeads);
+                _.forEach(privateWs2pHeads, function(head) {
+
+                  if (!head.hasEndPoint) {
+                    var currentNumber = head.buid && parseInt(head.buid.split('-')[0]);
+                    // Exclude if on a too old block
+                    if (currentNumber && currentNumber < data.minOnlineBlockNumber) {
+                      console.debug("[network] Exclude a too old WS2P message, on pubkey {0}".format(head.pubkey.substring(0,6)));
+                      return;
+                    }
+
+                    var peer = new Peer({
+                      buid: head.buid,
+                      currentNumber: currentNumber,
+                      pubkey: head.pubkey,
+                      version: head.version,
+                      powPrefix: head.powPrefix,
+                      online: true,
+                      uid: data.uidsByPubkeys[head.pubkey],
+                      bma: {
+                        useWs2p: true,
+                        private: true,
+                        ws2pid: head.ws2pid
+                      },
+                      endpoints: [
+                        // fake endpoint
+                        'WS2P ' + head.ws2pid
+                      ]
+                    });
+                    peer.id = peer.keyID();
+                    if (peer.uid && data.expertMode && data.difficulties) {
+                      peer.difficulty = data.difficulties[peer.uid];
+                    }
+                    if (applyPeerFilter(peer)) {
+                      newPeers.push(peer);
+                      privateEPCount++;
+                    }
                   }
                 });
-                if (jobs.length) return $q.all(jobs);
-              });
-          })
 
+                if (privateEPCount) {
+                  console.debug("[http] Found {0} WS2P endpoints without endpoint (private ?)".format(privateEPCount));
+                }
+              }
+
+              if (jobs.length) return $q.all(jobs);
+            }
+
+            // If filter offline peers
+            else {
+              return $q.all(_(res && res.peers || []).reduce(function(res, json) {
+                return res.concat(addOrRefreshPeerFromJson(json, newPeers));
+              }, []));
+            }
+          })
           .then(function(){
             data.searchingPeersOnNetwork = false;
           })
@@ -301,13 +308,23 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.bma.services', 'cesi
           return false;
         }
 
-        // Filter on endpoints
-        if (data.filter.endpointFilter && !peer.hasEndpoint(data.filter.endpointFilter)) {
+        // Filter on endpoint
+        if (data.filter.endpoint && !peer.hasEndpoint(data.filter.endpoint)) {
           return false;
         }
 
         // Filter on status
-        if (!data.filter.online && peer.status == 'UP') {
+        if ((data.filter.online && peer.status !== 'UP' && peer.oldBlock) || (!data.filter.online && peer.status === 'UP' && !peer.oldBlock)) {
+          return false;
+        }
+
+        // Filter on bma
+        if (angular.isDefined(data.filter.bma) && peer.isBma() != data.filter.bma) {
+          return false;
+        }
+
+        // Filter on ws2p
+        if (angular.isDefined(data.filter.ws2p) && peer.isWs2p() != data.filter.ws2p) {
           return false;
         }
 
@@ -327,6 +344,10 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.bma.services', 'cesi
       addOrRefreshPeerFromJson = function(json, list) {
         list = list || data.newPeers;
 
+        // Analyze the peer document, and exclude using the online filter
+        json.blockNumber = json.block && parseInt(json.block.split('-')[0]);
+        json.oldBlock = (json.status === 'UP' && json.blockNumber && json.blockNumber < data.minOnlineBlockNumber);
+
         var peers = createPeerEntities(json);
         var hasUpdates = false;
 
@@ -410,7 +431,8 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.bma.services', 'cesi
         peer.bma = ep;
         peer.server = peer.getServer();
         peer.dns = peer.getDns();
-        peer.blockNumber = peer.block && peer.block.replace(/-.+$/, '');
+        peer.buid = peer.buid || peer.block;
+        peer.blockNumber = peer.buid && parseInt(peer.buid.split('-')[0]);
         peer.uid = peer.pubkey && data.uidsByPubkeys[peer.pubkey];
         peer.id = peer.keyID();
         return [peer];
@@ -421,7 +443,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.bma.services', 'cesi
         // Apply filter
         if (!applyPeerFilter(peer)) return $q.when();
 
-        if (!data.filter.online || (data.filter.online === 'all' && peer.status === 'DOWN') || !peer.getHost() /*fix #537*/) {
+        if (!data.filter.online || (!data.filter.online && peer.status === 'DOWN') || !peer.getHost() /*fix #537*/) {
           peer.online = false;
           return $q.when(peer);
         }
@@ -432,7 +454,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.bma.services', 'cesi
           delete data.ws2pHeads[ws2pHeadKey];
           if (head) {
             peer.buid = head.buid;
-            peer.currentNumber=peer.buid && peer.buid.split('-')[0];
+            peer.currentNumber=head.buid && parseInt(head.buid.split('-')[0]);
             peer.version = head.version;
             peer.powPrefix = head.powPrefix;
           }
@@ -460,7 +482,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.bma.services', 'cesi
 
         // Do not try to access TOR or WS2P endpoints
         if (peer.bma.useTor || peer.bma.useWs2p) {
-          peer.online = (peer.status == 'UP');
+          peer.online = (peer.status === 'UP');
           peer.buid = constants.UNKNOWN_BUID;
           delete peer.version;
 
@@ -494,7 +516,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.bma.services', 'cesi
             }
             if (!peer.secondTry) {
               var bma = peer.bma || peer.getBMA();
-              if (bma.dns && peer.server.indexOf(bma.dns) == -1) {
+              if (bma.dns && peer.server.indexOf(bma.dns) === -1) {
                 // try again, using DNS instead of IPv4 / IPV6
                 peer.secondTry = true;
                 peer.api = BMA.lightInstance(bma.dns, bma.port, bma.useSsl);
@@ -502,9 +524,10 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.bma.services', 'cesi
               }
             }
 
-            peer.online=false;
-            peer.currentNumber = null;
             peer.buid = null;
+            peer.blockNumber = null;
+            peer.currentNumber = null;
+            peer.online=false;
             peer.uid = data.uidsByPubkeys[peer.pubkey];
             return peer;
           })
@@ -588,8 +611,8 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.bma.services', 'cesi
             if (!buid || !buid.medianTime) {
               buid = {
                 buid: peer.buid,
-                count: 0,
-                medianTime: peer.medianTime
+                medianTime: peer.medianTime,
+                count: 0
               };
               buids[peer.buid] = buid;
             }
@@ -597,42 +620,44 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.bma.services', 'cesi
             else if (!buid.medianTime && peer.medianTime) {
               buid.medianTime = peer.medianTime;
             }
-            if (buid.buid != constants.UNKNOWN_BUID) {
+            if (buid.buid !== constants.UNKNOWN_BUID) {
               buid.count++;
             }
           }
           data.memberPeersCount += peer.uid ? 1 : 0;
         });
-        // Compute pct of use, per buid
-        _.forEach(_.values(buids), function(buid) {
-          buid.pct = buid.count * 100 / data.peers.length;
-        });
-        var mainBlock = _.max(buids, function(obj) {
-          return obj.count;
-        });
-        _.forEach(data.peers, function(peer){
-          peer.hasMainConsensusBlock = peer.buid == mainBlock.buid;
-          peer.hasConsensusBlock = peer.buid && !peer.hasMainConsensusBlock && buids[peer.buid].count > 1;
-          if (peer.hasConsensusBlock) {
-            peer.consensusBlockDelta = buids[peer.buid].medianTime - mainBlock.medianTime;
-          }
-        });
+        var mainBlock = data.mainBlock;
+        if (data.filter.online) {
+          // Compute pct of use, per buid
+          _.forEach(_.values(buids), function(buid) {
+            buid.pct = buid.count * 100 / data.peers.length;
+          });
+          mainBlock = _.max(buids, function(obj) {
+            return obj.count;
+          });
+          _.forEach(data.peers, function(peer){
+            peer.hasMainConsensusBlock = peer.buid === mainBlock.buid;
+            peer.hasConsensusBlock = peer.buid && !peer.hasMainConsensusBlock && buids[peer.buid].count > 1;
+            if (peer.hasConsensusBlock) {
+              peer.consensusBlockDelta = buids[peer.buid].medianTime - mainBlock.medianTime;
+            }
+          });
+        }
         data.peers = _.uniq(data.peers, false, function(peer) {
           return peer.id;
         });
         data.peers = _.sortBy(data.peers, function(peer) {
           var score = 0;
           if (data.sort.type) {
-            var sortScore = 0;
-            sortScore += (data.sort.type == 'uid' ? computeScoreAlphaValue(peer.uid||peer.pubkey, 3, data.sort.asc) : 0);
-            sortScore += (data.sort.type == 'api') &&
+            score += (data.sort.type === 'uid' ? computeScoreAlphaValue(peer.uid||peer.pubkey, 3, data.sort.asc) : 0);
+            score += (data.sort.type === 'api') &&
               ((peer.isWs2p() && (data.sort.asc ? 1 : -1) || 0) +
               (peer.hasEndpoint('ES_USER_API') && (data.sort.asc ? 0.01 : -0.01) || 0) +
               (peer.isSsl() && (data.sort.asc ? 0.75 : -0.75) || 0)) || 0;
-            sortScore += (data.sort.type == 'difficulty' ? (peer.difficulty ? (data.sort.asc ? (10000-peer.difficulty) : peer.difficulty): 0) : 0);
-            sortScore += (data.sort.type == 'current_block' ? (peer.currentNumber ? (data.sort.asc ? (1000000000 - peer.currentNumber) : peer.currentNumber) : 0) : 0);
-            score += (10000000000 * sortScore);
+            score += (data.sort.type === 'difficulty' ? (peer.difficulty ? (data.sort.asc ? (10000-peer.difficulty) : peer.difficulty): 0) : 0);
+            score += (data.sort.type === 'current_block' ? (peer.currentNumber ? (data.sort.asc ? (1000000000 - peer.currentNumber) : peer.currentNumber) : 0) : 0);
           }
+          score =  (10000000000 * score);
           score += (1000000000 * (peer.online ? 1 : 0));
           score += (100000000  * (peer.hasMainConsensusBlock ? 1 : 0));
           score += (1000000    * (peer.hasConsensusBlock ? buids[peer.buid].pct : 0));
@@ -644,9 +669,18 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.bma.services', 'cesi
             score += (100     * (peer.uid ? computeScoreAlphaValue(peer.uid, 2, true) : 0));
             score += (1       * (!peer.uid ? computeScoreAlphaValue(peer.pubkey, 2, true) : 0));
           }
+          score += (peer.isBma() ? (peer.isSsl() ? 0.01 : 0.001) :0); // If many endpoints: BMAS first, then BMA
           return -score;
         });
 
+        if (data.groupBy) {
+          var previousPeer;
+          data.peers.forEach(function(peer) {
+            peer.compacted = (previousPeer && peer[data.groupBy] && peer[data.groupBy] === previousPeer[data.groupBy]);
+            previousPeer = peer;
+          });
+        }
+
         // Raise event on new main block
         if (updateMainBuid && mainBlock.buid && (!data.mainBlock || data.mainBlock.buid !== mainBlock.buid)) {
           data.mainBlock = mainBlock;
@@ -718,11 +752,23 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.bma.services', 'cesi
         return BMA.ready()
           .then(function() {
             close();
+
             data.bma = bma ? bma : BMA;
             data.filter = options.filter ? angular.merge(data.filter, options.filter) : data.filter;
             data.sort = options.sort ? angular.merge(data.sort, options.sort) : data.sort;
             data.expertMode = angular.isDefined(options.expertMode) ? options.expertMode : data.expertMode;
             data.timeout = angular.isDefined(options.timeout) ? options.timeout : csConfig.timeout;
+
+            // Init a min block number
+            data.minOnlineBlockNumber = data.mainBlock && data.mainBlock.buid && (parseInt(data.mainBlock.buid.split('-')[0]) - constants.MAX_BLOCK_OFFSET) || undefined;
+            if (data.minOnlineBlockNumber === undefined) {
+              return csCurrency.blockchain.current(true/*use cache*/)
+                .then(function(current) {
+                  data.minOnlineBlockNumber = current.number - constants.MAX_BLOCK_OFFSET;
+                });
+            }
+          })
+          .then(function() {
             console.info('[network] Starting network from [{0}]'.format(bma.server));
             var now = Date.now();
 
@@ -799,8 +845,11 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.bma.services', 'cesi
     };
   };
 
-  var service = factory('default');
+  var service = csNetwork('default');
+
+  service.instance = function(id) {
+    return new csNetwork(id);
+  };
 
-  service.instance = factory;
   return service;
 });
diff --git a/www/plugins/es/js/controllers/settings-controllers.js b/www/plugins/es/js/controllers/settings-controllers.js
index 6723dd30a311c3c21a7f7a2e750907dc29634ba5..fbc14baad5a970bbef788f171d0032bdf843c82c 100644
--- a/www/plugins/es/js/controllers/settings-controllers.js
+++ b/www/plugins/es/js/controllers/settings-controllers.js
@@ -48,7 +48,8 @@ function ESExtendSettingsController ($scope, PluginService) {
 /*
  * Settings extend controller
  */
-function ESPluginSettingsController ($scope, $q,  $translate, $ionicPopup, UIUtils, Modals, csHttp, csSettings, esHttp, esSettings) {
+function ESPluginSettingsController ($scope, $window, $q,  $translate, $ionicPopup,
+                                     UIUtils, Modals, csHttp, csConfig, csSettings, esHttp, esSettings) {
   'ngInject';
 
   $scope.formData = {};
@@ -93,7 +94,6 @@ function ESPluginSettingsController ($scope, $q,  $translate, $ionicPopup, UIUti
     node = node || {
         host: $scope.formData.host,
         port: $scope.formData.port && $scope.formData.port != 80 && $scope.formData.port != 443 ? $scope.formData.port : undefined,
-        wsPort: $scope.formData.wsPort && $scope.formData.wsPort != $scope.formData.port ? $scope.formData.wsPort : undefined,
         useSsl: angular.isDefined($scope.formData.useSsl) ?
           $scope.formData.useSsl :
           ($scope.formData.port == 443)
@@ -103,14 +103,13 @@ function ESPluginSettingsController ($scope, $q,  $translate, $ionicPopup, UIUti
     .then(function(newNode) {
       if (newNode.host == $scope.formData.host &&
         newNode.port == $scope.formData.port &&
-        newNode.wsPort == $scope.formData.wsPort &&
         newNode.useSsl == $scope.formData.useSsl) {
         UIUtils.loading.hide();
         return; // same node = nothing to do
       }
       UIUtils.loading.show();
 
-      var newEsNode = esHttp.instance(newNode.host, newNode.port, newNode.wsPort, newNode.useSsl);
+      var newEsNode = esHttp.instance(newNode.host, newNode.port, newNode.useSsl);
       return newEsNode.isAlive() // ping the node
         .then(function(alive) {
           if (!alive) {
@@ -123,7 +122,6 @@ function ESPluginSettingsController ($scope, $q,  $translate, $ionicPopup, UIUti
 
           $scope.formData.host = newEsNode.host;
           $scope.formData.port = newEsNode.port;
-          $scope.formData.wsPort = newEsNode.wsPort;
           $scope.formData.useSsl = newEsNode.useSsl;
 
           return esHttp.copy(newEsNode);
@@ -138,13 +136,10 @@ function ESPluginSettingsController ($scope, $q,  $translate, $ionicPopup, UIUti
 
   // Show node popup
   $scope.showNodePopup = function(node) {
+
     return $q(function(resolve, reject) {
       var parts = [node.host];
-      if (node.wsPort && node.wsPort != (node.port||80)) {
-        parts.push(node.port||80);
-        parts.push(node.wsPort);
-      }
-      else if (node.port && node.port != 80) {
+      if (node.port && node.port != 80) {
         parts.push(node.port);
       }
       $scope.popupData.newNode = parts.join(':');
@@ -187,7 +182,6 @@ function ESPluginSettingsController ($scope, $q,  $translate, $ionicPopup, UIUti
             resolve({
               host: parts[0],
               port: parts[1] || (useSsl ? 443 : 80),
-              wsPort: parts[2] || parts[1] || (useSsl ? 443 : 80),
               useSsl: useSsl
             });
           });
@@ -196,10 +190,15 @@ function ESPluginSettingsController ($scope, $q,  $translate, $ionicPopup, UIUti
   };
 
   $scope.showNodeList = function() {
+    // Check if need a filter on SSL node
+    var forceUseSsl = (csConfig.httpsMode === 'true' || csConfig.httpsMode === true || csConfig.httpsMode === 'force') ||
+    ($window.location && $window.location.protocol === 'https:') ? true : false;
+
     $ionicPopup._popupStack[0].responseDeferred.promise.close();
     return Modals.showNetworkLookup({
       enableFilter: true,
-      endpointFilter: esHttp.constants.ES_USER_API_ENDPOINT
+      endpoint: esHttp.constants.ES_USER_API_ENDPOINT,
+      ssl: forceUseSsl ? true: undefined
     })
       .then(function (peer) {
         if (!peer) return;
@@ -212,14 +211,11 @@ function ESPluginSettingsController ($scope, $q,  $translate, $ionicPopup, UIUti
           return {
             host: (ep.dns ? ep.dns :
                    (peer.hasValid4(ep) ? ep.ipv4 : ep.ipv6)),
-            port: ep.port || 80
+            port: ep.port || 80,
+            useSsl: ep.useSsl || ep.port == 443
           };
       })
       .then(function(newEsNode) {
-        if (!newEsNode) {
-          UIUtils.alert.error('ERROR.INVALID_NODE_SUMMARY');
-          return;
-        }
         $scope.changeEsNode(newEsNode);
       });
   };
@@ -257,7 +253,6 @@ function ESPluginSettingsController ($scope, $q,  $translate, $ionicPopup, UIUti
   $scope.getServer = function(node) {
     node = node || $scope.formData;
     if (!node.host) return undefined;
-    var server = csHttp.getServer(node.host, node.port);
-    return server + (node.wsPort && node.wsPort != node.port ? ':' + node.wsPort : '');
+    return csHttp.getServer(node.host, node.port);
   };
 }
diff --git a/www/plugins/es/js/controllers/subscription-controllers.js b/www/plugins/es/js/controllers/subscription-controllers.js
index cbb63ace42febf5462ffbf748fae559c01142b26..397bf94c6bd327739ca67e1b0851ccfe90ae2163 100644
--- a/www/plugins/es/js/controllers/subscription-controllers.js
+++ b/www/plugins/es/js/controllers/subscription-controllers.js
@@ -324,7 +324,8 @@ function ModalEmailSubscriptionsController($scope, Modals, csSettings, esHttp, c
   $scope.showNetworkLookup = function() {
     return Modals.showNetworkLookup({
       enableFilter: true,
-      endpointFilter: esHttp.constants.ES_USER_API_ENDPOINT
+      endpoint: esHttp.constants.ES_USER_API_ENDPOINT,
+      bma: false
     })
       .then(function (peer) {
         if (peer) {
diff --git a/www/plugins/es/js/services/http-services.js b/www/plugins/es/js/services/http-services.js
index 371c129e60c6a1d4e3f229e81c04c8ddadeb883d..8bdd9b6b3578bdddec9f03e7faa07446f890396a 100644
--- a/www/plugins/es/js/services/http-services.js
+++ b/www/plugins/es/js/services/http-services.js
@@ -14,7 +14,7 @@ angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.servic
     console.debug('[ES] [https] Enable SSL (forced by config or detected in URL)');
   }
 
-  function EsHttp(host, port, wsPort, useSsl) {
+  function EsHttp(host, port, useSsl) {
 
     var
       that = this,
@@ -42,21 +42,19 @@ angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.servic
     that.started = false;
     that.init = init;
 
-    init(host, port, wsPort, useSsl);
+    init(host, port, useSsl);
 
-    function init(host, port, wsPort, useSsl) {
+    function init(host, port, useSsl) {
       // Use settings as default
       if (!host && csSettings.data) {
         host = host || (csSettings.data.plugins && csSettings.data.plugins.es ? csSettings.data.plugins.es.host : null);
         port = port || (host ? csSettings.data.plugins.es.port : null);
-        wsPort = wsPort || (host ? csSettings.data.plugins.es.wsPort : null);
         useSsl = angular.isDefined(useSsl) ? useSsl : (port == 443 || csSettings.data.plugins.es.useSsl || forceUseSsl);
       }
 
       that.alive = false;
       that.host = host;
       that.port = port || ((useSsl || forceUseSsl) ? 443 : 80);
-      that.wsPort = wsPort || that.port;
       that.useSsl = angular.isDefined(useSsl) ? useSsl : (that.port == 443 || forceUseSsl);
       that.server = csHttp.getServer(host, port);
     }
@@ -68,15 +66,13 @@ angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.servic
       var host = data.plugins.es.host;
       var useSsl = data.plugins.es.port == 443 || data.plugins.es.useSsl || forceUseSsl;
       var port = data.plugins.es.port || (useSsl ? 443 : 80);
-      var wsPort = data.plugins.es.wsPort || port;
 
-      return isSameNode(host, port, wsPort, useSsl);
+      return isSameNode(host, port, useSsl);
     }
 
-    function isSameNode(host, port, wsPort, useSsl) {
+    function isSameNode(host, port, useSsl) {
       return (that.host == host) &&
         (that.port == port) &&
-        (!wsPort || that.wsPort == wsPort) &&
         (angular.isUndefined(useSsl) || useSsl == that.useSsl);
     }
 
@@ -134,7 +130,7 @@ angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.servic
 
     that.copy = function(otherNode) {
       if (that.started) that.stop();
-      that.init(otherNode.host, otherNode.port, otherNode.wsPort, otherNode.useSsl || otherNode.port == 443);
+      that.init(otherNode.host, otherNode.port, otherNode.useSsl || otherNode.port == 443);
       that.data.isTemporary = false; // reset temporary flag
       return that.start(true /*skipInit*/);
     };
@@ -198,7 +194,7 @@ angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.servic
       return function() {
         var sock = that.cache.wsByPath[path];
         if (!sock) {
-          sock =  csHttp.ws(that.host, that.wsPort, path, that.useSsl);
+          sock =  csHttp.ws(that.host, that.port, path, that.useSsl);
           that.cache.wsByPath[path] = sock;
         }
         return sock;
@@ -233,8 +229,7 @@ angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.servic
       // Remember the default node
       defaultSettingsNode = defaultSettingsNode || {
         host: settings.host,
-        port: settings.port,
-        wsPort: settings.wsPort
+        port: settings.port
       };
 
       var fallbackNode = settings.fallbackNodes && fallbackNodeIndex < settings.fallbackNodes.length && settings.fallbackNodes[fallbackNodeIndex++];
@@ -252,7 +247,7 @@ angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.servic
 
           that.cleanCache();
 
-          that.init(fallbackNode.host, fallbackNode.port, fallbackNode.wsPort, fallbackNode.useSsl || fallbackNode.port == 443);
+          that.init(fallbackNode.host, fallbackNode.port, fallbackNode.useSsl || fallbackNode.port == 443);
 
           // check is alive then loop
           return that.isAlive().then(that.checkNodeAlive);
@@ -606,11 +601,13 @@ angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.servic
     function parseEndPoint(endpoint) {
       var matches = regexp.ES_USER_API_ENDPOINT.exec(endpoint);
       if (!matches) return;
+      var port = matches[8] || 80;
       return {
         "dns": matches[2] || '',
         "ipv4": matches[4] || '',
         "ipv6": matches[6] || '',
-        "port": matches[8] || 80
+        "port": port,
+        "useSsl": port == 80 ? false : (port == 443)
       };
     }
 
@@ -680,8 +677,8 @@ angular.module('cesium.es.http.services', ['ngResource', 'ngApi', 'cesium.servic
 
   var service = new EsHttp();
 
-  service.instance = function(host, port, wsPort, useSsl) {
-    return new EsHttp(host, port, wsPort, useSsl);
+  service.instance = function(host, port, useSsl) {
+    return new EsHttp(host, port, useSsl)
   };
 
   return service;
diff --git a/www/plugins/es/js/services/settings-services.js b/www/plugins/es/js/services/settings-services.js
index b12ed7533b30fa67ff1a9e8b50290957fc436e79..d3ee7102e2904781a55ef7f16f06300a30a8c7ad 100644
--- a/www/plugins/es/js/services/settings-services.js
+++ b/www/plugins/es/js/services/settings-services.js
@@ -20,7 +20,7 @@ angular.module('cesium.es.settings.services', ['cesium.services', 'cesium.es.htt
       excludes: ['timeout', 'cacheTimeMs', 'time', 'login', 'build'],
       plugins: {
         es: {
-          excludes: ['enable', 'host', 'port', 'wsPort', 'fallbackNodes', 'enableGoogleApi', 'googleApiKey'],
+          excludes: ['enable', 'host', 'port', 'fallbackNodes', 'enableGoogleApi', 'googleApiKey'],
           notifications: {
           }
         }
diff --git a/www/templates/blockchain/list_blocks.html b/www/templates/blockchain/list_blocks.html
index 4a5032ca9f2b417aa8d6889c31601fdc18889f76..5721ec6695adbd646e8b009a4a7491abf6a520f4 100644
--- a/www/templates/blockchain/list_blocks.html
+++ b/www/templates/blockchain/list_blocks.html
@@ -3,7 +3,7 @@
     <ion-spinner icon="android"></ion-spinner>
   </div>
 
-  <ion-list class="padding padding-xs list-blocks" ng-class="::motion.ionListClass">
+  <ion-list class="padding padding-xs list-blocks {{::motion.ionListClass}}">
     <div class="padding gray" ng-if="!search.loading && !search.results.length" translate>
       BLOCKCHAIN.LOOKUP.NO_BLOCK
     </div>
diff --git a/www/templates/blockchain/list_blocks_lg.html b/www/templates/blockchain/list_blocks_lg.html
index 3b7f2accab58a926d6475e26c25dc50faee514b9..8c9d6763eb79ca78be12c950a51146d46b8f5c8a 100644
--- a/www/templates/blockchain/list_blocks_lg.html
+++ b/www/templates/blockchain/list_blocks_lg.html
@@ -17,7 +17,7 @@
     <ion-spinner icon="android"></ion-spinner>
   </div>
 
-  <ion-list class="padding padding-xs list-blocks" ng-class="::motion.ionListClass">
+  <ion-list class="padding padding-xs list-blocks {{::motion.ionListClass}}">
     <div class="padding gray" ng-if="!search.loading && !search.results.length" translate>
       BLOCKCHAIN.LOOKUP.NO_BLOCK
     </div>
diff --git a/www/templates/network/item_content_peer.html b/www/templates/network/item_content_peer.html
index deffb8e8506eb3942d19f81fc72ab2ea580c101c..54a79865e2bddd1a92c7bbbf82efeea84b719b0d 100644
--- a/www/templates/network/item_content_peer.html
+++ b/www/templates/network/item_content_peer.html
@@ -19,11 +19,11 @@
           <span class="positive" ng-if=":rebind:peer.uid">
             <i class="ion-person"></i> {{:rebind:peer.name || peer.uid}}
           </span>
-          <span class="gray">{{:rebind:peer.dns && (' | ' + peer.server) + (peer.bma.path||'') }}</span>
+          <span class="gray" ng-if=":rebind:!compactMode">{{:rebind:peer.dns && (' | ' + peer.server) + (peer.bma.path||'') }}</span>
         </h4>
       </div>
       <div class="col col-15 no-padding text-center hidden-xs hidden-sm" ng-if="::expertMode">
-        <div style="min-width: 50px; padding-top: 5px;" >
+        <div style="min-width: 50px; padding-top: 5px;" ng-if=":rebind:!compactMode">
           <span ng-if=":rebind:peer.isSsl()" title="SSL">
             <i class="ion-locked"></i><small class="hidden-md"> SSL</small>
           </span>
@@ -60,9 +60,9 @@
       </div>
       <div class="col col-20 no-padding text-center">
         <span id="{{$index === 0 ? helptipPrefix + '-peer-0-block' : ''}}"
-            class="badge" ng-class=":rebind:{'badge-balanced': peer.hasMainConsensusBlock, 'badge-energized': peer.hasConsensusBlock, 'ng-hide': !peer.currentNumber }">
+            class="badge" ng-class=":rebind:{'badge-balanced': peer.hasMainConsensusBlock, 'badge-energized': peer.hasConsensusBlock, 'ng-hide': !peer.currentNumber && !peer.blockNumber }">
           {{::!expertMode ? ('COMMON.BLOCK'|translate) : '' }}
-          {{:rebind:peer.currentNumber|formatInteger}}</span>
+          {{:rebind:(peer.currentNumber || peer.blockNumber) | formatInteger}}</span>
         <span class="badge badge-secondary" ng-if=":rebind:peer.consensusBlockDelta && expertMode">
           <i class="ion-clock"></i>&nbsp;
           {{:rebind:peer.consensusBlockDelta|formatDurationTime}}</span>
diff --git a/www/templates/network/items_peers.html b/www/templates/network/items_peers.html
index 7db8f233d536afd3908fe7b8be7034a14c4b0746..2a0cd95147d7662cdb1d449de9e90adcb4d138aa 100644
--- a/www/templates/network/items_peers.html
+++ b/www/templates/network/items_peers.html
@@ -1,4 +1,4 @@
-<div ng-class="::motion.ionListClass" class="no-padding">
+<div class="no-padding {{::motion.ionListClass}}">
 
   <div class="item item-text-wrap no-border done in gray no-padding-top no-padding-bottom inline text-italic" ng-if="::isHttps && expertMode">
     <small><i class="icon ion-alert-circled"></i> {{'NETWORK.INFO.ONLY_SSL_PEERS'|translate}}</small>
@@ -26,11 +26,11 @@
   </div>
 
   <div ng-repeat="peer in :rebind:search.results track by peer.id"
-       class="item item-peer item-icon-left ink"
-       ng-class="::ionItemClass"
-       id="{{helptipPrefix}}-peer-{{$index}}"
+       class="item item-peer item-icon-left ink {{::ionItemClass}}"
+       ng-class=":rebind:{'compacted': peer.compacted && compactMode}"
+       id="{{::helptipPrefix}}-peer-{{::$index}}"
        ng-click="selectPeer(peer)"
-       ng-include="'templates/network/item_content_peer.html'">
+       ng-include="::'templates/network/item_content_peer.html'">
   </div>
 
 </div>
diff --git a/www/templates/network/modal_network.html b/www/templates/network/modal_network.html
index 6392bdbbd52465e15e1a23189cfe87bf760ffcd6..c627cd7029478fc41467868d23cfdd8bd27e9ae4 100644
--- a/www/templates/network/modal_network.html
+++ b/www/templates/network/modal_network.html
@@ -19,14 +19,11 @@
       <div class="padding padding-xs" style="display: block; height: 60px;">
 
         <div class="pull-left">
-          <h4 ng-if="enableFilter && search.type=='member'">
-            {{'PEER.MEMBERS' | translate}} <span ng-if="!search.loading">({{search.results.length}})</span>
-          </h4>
-          <h4 ng-if="enableFilter && search.type=='mirror'">
-            {{'PEER.MIRRORS' | translate}} <span ng-if="!search.loading">({{search.results.length}})</span>
-          </h4>
-          <h4 ng-if="!enableFilter || !search.type">
-            {{'PEER.ALL_PEERS' | translate}} <span ng-if="!search.loading">({{search.results.length}})</span>
+          <h4>
+            <span ng-if="enableFilter && search.type=='member'" translate>PEER.MEMBER_PEERS</span>
+            <span ng-if="enableFilter && search.type=='mirror'" translate>PEER.MIRROR_PEERS</span>
+            <span ng-if="!enableFilter || !search.type" translate>PEER.ALL_PEERS</span>
+            <span ng-if="!search.loading">({{search.results.length}})</span>
           </h4>
         </div>
 
diff --git a/www/templates/network/view_network.html b/www/templates/network/view_network.html
index cebf843e8b28a848a3ef7153bc6027b853511736..3a87285b371f5c41037419b4991ce4dc42c112f3 100644
--- a/www/templates/network/view_network.html
+++ b/www/templates/network/view_network.html
@@ -16,9 +16,9 @@
         <div class="padding padding-xs" style="display: block; height: 60px;">
           <div class="pull-left">
             <h4>
-              <span ng-if="enableFilter && search.type=='member'" translate>PEER.MEMBERS</span>
-              <span ng-if="enableFilter && search.type=='mirror'" translate>PEER.MIRRORS</span>
-              <span ng-if="enableFilter && search.type=='offline'" translate>PEER.OFFLINE</span>
+              <span ng-if="enableFilter && search.type=='member'" translate>PEER.MEMBER_PEERS</span>
+              <span ng-if="enableFilter && search.type=='mirror'" translate>PEER.MIRROR_PEERS</span>
+              <span ng-if="enableFilter && search.type=='offline'" translate>PEER.OFFLINE_PEERS</span>
               <span ng-if="!enableFilter || !search.type" translate>PEER.ALL_PEERS</span>
               <span ng-if="search.results.length">({{search.results.length}})</span>
               <ion-spinner ng-if="search.loading" class="icon ion-spinner-small" icon="android"></ion-spinner>
@@ -28,6 +28,15 @@
           <div class="pull-right">
 
             <div class="pull-right" ng-if="enableFilter">
+              <a class="button button-text button-small hidden-xs hidden-sm ink"
+                 ng-class="{'button-text-positive': compactMode}"
+                 ng-click="toggleCompactMode()">
+                <i class="icon ion-navicon"></i>
+                <b class="ion-arrow-down-b" style="position: absolute; top: -2px; left: 4px; font-size: 8px;"></b>
+                <b class="ion-arrow-up-b" style="position: absolute; top: 10px; left: 4px; font-size: 8px;"></b>
+                <span>{{'BLOCKCHAIN.LOOKUP.BTN_COMPACT'|translate}}</span>
+              </a>
+
               <a class="button button-text button-small hidden-xs hidden-sm ink"
                  ng-class="{'button-text-positive': search.type=='member'}"
                  ng-click="toggleSearchType('member')">
diff --git a/www/templates/network/view_peer.html b/www/templates/network/view_peer.html
index 29b0646489ea32847999853555d62434d1ae8cf0..c90092d4e8d187a99eefd26c5ae85ef086d48262 100644
--- a/www/templates/network/view_peer.html
+++ b/www/templates/network/view_peer.html
@@ -121,8 +121,7 @@
         <div class="list no-padding {{::motion.ionListClass}}" ng-if="isReachable">
 
           <div ng-repeat="peer in :rebind:peers track by peer.id"
-               class="item item-peer item-icon-left ink"
-               ng-class="::ionItemClass"
+               class="item item-peer item-icon-left ink {{::ionItemClass}}"
                ng-click="selectPeer(peer)"
                ng-include="'templates/network/item_content_peer.html'">
           </div>
diff --git a/www/templates/wallet/view_wallet.html b/www/templates/wallet/view_wallet.html
index 37d12228d82cdba041b6622bf214a7da6107bc09..623c5b0d289d256f00079641ff4cbb2723f77a69 100644
--- a/www/templates/wallet/view_wallet.html
+++ b/www/templates/wallet/view_wallet.html
@@ -106,7 +106,7 @@
 
       <div class="col">
 
-        <div class="list" ng-class="::motion.ionListClass" ng-hide="loading">
+        <div class="list {{::motion.ionListClass}}" ng-hide="loading">
 
           <span class="item item-divider" translate>WOT.GENERAL_DIVIDER</span>
 
diff --git a/www/templates/wallet/view_wallet_tx.html b/www/templates/wallet/view_wallet_tx.html
index 1c893d53579557d85d7bdf585b52f92e4359fa6e..a088fd357176303c200cb8eb9ea1a9e2f5764c9c 100644
--- a/www/templates/wallet/view_wallet_tx.html
+++ b/www/templates/wallet/view_wallet_tx.html
@@ -85,9 +85,7 @@
 
       <div class="col">
 
-
-
-        <div class="list" ng-class="::motion.ionListClass">
+        <div class="list {{::motion.ionListClass}}">
 
           <!-- Errors transactions-->
           <a class="item item-icon-left item-icon-right ink" ng-if="formData.tx.errors && formData.tx.errors.length"
diff --git a/www/templates/wallet/view_wallet_tx_error.html b/www/templates/wallet/view_wallet_tx_error.html
index 588c7adf20377edb5b40c4284fd8f18a33dea883..9fd9439338c16be727adefe65ea78d55ef0cd118 100644
--- a/www/templates/wallet/view_wallet_tx_error.html
+++ b/www/templates/wallet/view_wallet_tx_error.html
@@ -29,7 +29,7 @@
       <div class="col col-20 hidden-xs hidden-sm">&nbsp;
       </div>
 
-      <div class="col list" ng-class="::motion.ionListClass">
+      <div class="col list {{::motion.ionListClass}}">
 
         <!-- Pending received TX -->
         <div class="item item-divider" >
diff --git a/www/templates/wot/view_identity.html b/www/templates/wot/view_identity.html
index 411bb4c86dedea409099ad132f15aec18841ad4d..6ec59ae588a06aaf384f28fd79c8cbcf81b089f4 100644
--- a/www/templates/wot/view_identity.html
+++ b/www/templates/wot/view_identity.html
@@ -78,7 +78,7 @@
     <div class="row no-padding" >
       <div class="col col-20 hidden-xs hidden-sm">&nbsp;</div>
 
-      <div class="col list" ng-class="::motion.ionListClass" bind-notifier="{ rebind:loading}">
+      <div class="col list {{::motion.ionListClass}}" bind-notifier="{ rebind:loading}">
 
         <span class="item item-divider" translate>WOT.GENERAL_DIVIDER</span>