diff --git a/app/config.json b/app/config.json
index 89aeb8acb65b43b06113bc92cc1c9559c80b09d4..518d1d913c3a6f677276f75a420d4e088f6da931 100644
--- a/app/config.json
+++ b/app/config.json
@@ -108,7 +108,7 @@
     "fallbackLanguage": "fr",
     "rememberMe": true,
     "showUDHistory": true,
-    "timeout": 300000,
+    "timeout": 10000,
     "timeWarningExpireMembership": 5184000,
     "timeWarningExpire": 7776000,
     "keepAuthIdle": 600,
diff --git a/config.xml b/config.xml
index 7acfe0ce15f333d932701dd5fa6e69fb6e2ddd18..c4ea0139944874607c69bea9dfd8775382507b17 100644
--- a/config.xml
+++ b/config.xml
@@ -156,7 +156,9 @@
         <variable name="ANDROID_SUPPORT_V4_VERSION" value="28.+" />
     </plugin>
     <plugin name="cordova-plugin-customurlscheme" spec="^5.0.2">
-        <variable name="URL_SCHEME" value="june" />
+      <variable name="URL_SCHEME" value="june" />
+    </plugin>
+    <plugin name="cordova-plugin-network-information" spec="~3.0.0">
     </plugin>
     <plugin name="ionic-plugin-keyboard" spec="^2.2.1" />
     <plugin name="cordova-plugin-androidx" spec="^1.0.2" />
diff --git a/package.json b/package.json
index 3bcfc837ec157be981d49d4aac11203edc71174d..0559441fb21726ad773d2fe253fbfbc688172472 100644
--- a/package.json
+++ b/package.json
@@ -97,25 +97,26 @@
     "cordova": "^10.0.0",
     "cordova-android": "^9.0.0",
     "cordova-clipboard": "^1.3.0",
-    "cordova-ios": "^6.2.0",
+    "cordova-ios": "^6.3.0",
     "cordova-plugin-androidx": "^3.0.0",
     "cordova-plugin-androidx-adapter": "^1.1.3",
-    "cordova-plugin-camera": "^5.0.1",
+    "cordova-plugin-camera": "^5.0.3",
     "cordova-plugin-compat": "^1.2.0",
     "cordova-plugin-customurlscheme": "^5.0.2",
-    "cordova-plugin-device": "^2.0.3",
+    "cordova-plugin-device": "^2.1.0",
     "cordova-plugin-dialogs": "^2.0.2",
     "cordova-plugin-file": "^6.0.2",
     "cordova-plugin-ionic-keyboard": "^2.2.0",
     "cordova-plugin-ionic-webview": "^5.0.0",
     "cordova-plugin-minisodium": "git+https://github.com/duniter-cesium/cordova-plugin-minisodium.git#v1.0.2",
     "cordova-plugin-secure-storage-android10": "git+https://github.com/duniter-cesium/cordova-plugin-secure-storage-android10.git#6.0.4",
-    "cordova-plugin-splashscreen": "^6.0.0",
-    "cordova-plugin-statusbar": "^2.4.3",
+    "cordova-plugin-splashscreen": "^6.0.2",
+    "cordova-plugin-statusbar": "^3.0.0",
     "cordova-plugin-vibration": "^3.1.1",
     "cordova-plugin-websocket": "^0.12.2",
-    "cordova-plugin-whitelist": "^1.3.4",
-    "cordova-plugin-x-toast": "^2.7.2",
+    "cordova-plugin-whitelist": "^1.3.5",
+    "cordova-plugin-x-toast": "^2.7.3",
+    "cordova-plugin-network-information": "~3.0.0",
     "del": "^5.1.0",
     "delete-empty": "^0.1.3",
     "event-stream": "3.3.4",
@@ -212,15 +213,16 @@
         "ANDROID_PATHPREFIX": "/wallet"
       },
       "cordova-plugin-secure-storage-android10": {},
-      "cordova-plugin-minisodium": {}
+      "cordova-plugin-minisodium": {},
+      "cordova-plugin-network-information": {}
     },
     "platforms": [
-      "android",
-      "ios"
+      "ios",
+      "android"
     ]
   },
   "engines": {
-    "node": ">= 12.18.3",
+    "node": ">= 12.22.8",
     "yarn": ">= 1.22.0"
   }
 }
diff --git a/www/i18n/locale-de-DE.json b/www/i18n/locale-de-DE.json
index db1b67a358ed1cefe2c7fec59c82dac2be5be167..1257b9c9e95e14026e03db3f0cae9c09ac1ef9d4 100644
--- a/www/i18n/locale-de-DE.json
+++ b/www/i18n/locale-de-DE.json
@@ -49,7 +49,7 @@
     "NO_ACCOUNT_QUESTION": "Noch nicht Mitglied? Registriere dich jetzt!",
     "SEARCH_NO_RESULT": "Keine Ergebnisse gefunden",
     "LOADING": "Lade...",
-    "LOADING_WAIT": "Lade...<br/><small>(Warte auf Verfügbarkeit des Knotens)</small>",
+    "LOADING_WAIT": "Lade...<br/><small>(Cesium fragt den Duniter-Knoten ab)</small>",
     "SEARCHING": "Suche...",
     "FROM": "Von",
     "TO": "An",
@@ -148,6 +148,9 @@
     "PEER": "Duniter Knoten-Adresse",
     "PEER_SHORT": "Knoten-Adresse",
     "PEER_CHANGED_TEMPORARY": "Knotenadresse wird nur temporär gebraucht",
+    "PEER_SELECTED_AUTOMATICALLY": "Knoten wird beim Start automatisch ausgewählt",
+    "PEER_TIMEOUT": "Maximale Wartezeit (Timeout)",
+    "PEER_TIMEOUT_HELP": "Maximale Wartezeit um eine Antwort vom Knoten zu bekommen",
     "PERSIST_CACHE": "Navigationsdaten behalten (experimental)",
     "PERSIST_CACHE_HELP": "Erlaubt schnellere Navigation, durch Zwischenspeicherung der empfangenen Daten, von einer Sitzung zur nächsten.",
     "USE_LOCAL_STORAGE": "Lokales Speichern einschalten",
@@ -818,7 +821,8 @@
     "REVOCATION_SENT_WAITING_PROCESS": "Widerruf des Kontos <b>erfolgreich abgeschickt</b>. Bearbeitung steht noch aus.",
     "FEATURE_NOT_AVAILABLE_ON_DEMO": "Diese Funktion ist auf der Demo-Version nicht verfügbar.<br/>Aus <b>Sicherheitsgründen</b> empfehlen wir dir, eine Kopie der Software zu <b>installieren</b>.<br/>Besuche die Webseite <a href='https://cesium.app'>www.cesium.app</a> für Hilfe und weitere Informationen.",
     "FEATURES_NOT_IMPLEMENTED": "Diese Funktion ist noch nicht implementiert.<br/><br/>Warum nicht mitwirken, um sie schneller zu erhalten? ;)",
-    "EMPTY_TX_HISTORY": "Keine Tansaktionen für den Export vorhanden."
+    "EMPTY_TX_HISTORY": "Keine Tansaktionen für den Export vorhanden.",
+    "LOADING_PENDING_TX": "Lade...<br/><small>(Abrufen von ausstehenden Operationen)</small>"
   },
   "CONFIRM": {
     "CAN_CONTINUE": "<b>Bist du sicher</b>, dass du fortfahren möchtest?",
diff --git a/www/i18n/locale-en-GB.json b/www/i18n/locale-en-GB.json
index 36369fa5a22c9ce91f8d4c096ff07a73eae06578..5d6f64ae981c4580ab4d11e965374d468aef43aa 100644
--- a/www/i18n/locale-en-GB.json
+++ b/www/i18n/locale-en-GB.json
@@ -49,7 +49,7 @@
     "NO_ACCOUNT_QUESTION": "Not a member yet? Register now!",
     "SEARCH_NO_RESULT": "No result found",
     "LOADING": "Loading...",
-    "LOADING_WAIT": "Loading...<br/><small>(Waiting for node availability)</small>",
+    "LOADING_WAIT": "Loading...<br/><small>(Cesium is querying the Duniter peer)</small>",
     "SEARCHING": "Searching...",
     "FROM": "From",
     "TO": "To",
@@ -144,6 +144,9 @@
     "PEER": "Duniter peer address",
     "PEER_SHORT": "Peer address",
     "PEER_CHANGED_TEMPORARY": "Address used temporarily",
+    "PEER_SELECTED_AUTOMATICALLY": "Peer selected automatically at startup",
+    "PEER_TIMEOUT": "Maximum waiting time (timeout)",
+    "PEER_TIMEOUT_HELP": "Maximum waiting time to get a response from the peer",
     "PERSIST_CACHE": "Keep navigation data (experimental)",
     "PERSIST_CACHE_HELP": "Allows faster navigation, locally retaining the data received, for use from one session to another.",
     "USE_LOCAL_STORAGE": "Enable local storage",
@@ -821,7 +824,8 @@
     "REVOCATION_SENT": "Revocation sent successfully",
     "REVOCATION_SENT_WAITING_PROCESS": "Revocation <b>has been sent successfully</b>. It is awaiting processing.",
     "FEATURES_NOT_IMPLEMENTED": "This features is not implemented yet.<br/><br/>Why not to contribute to get it faster? ;)",
-    "EMPTY_TX_HISTORY": "No operations to export"
+    "EMPTY_TX_HISTORY": "No operations to export",
+    "LOADING_PENDING_TX": "Please wait...<br/><small>(Retrieving pending operations)</small>"
   },
   "CONFIRM": {
     "CAN_CONTINUE": "<b>Are you sure</b> you want to continue?",
diff --git a/www/i18n/locale-en.json b/www/i18n/locale-en.json
index 752bc04a172483eb7fcd5aa9d236c4be93e330c2..0ad081def4026ba6e88ac4f62088e8ec0504b472 100644
--- a/www/i18n/locale-en.json
+++ b/www/i18n/locale-en.json
@@ -49,7 +49,7 @@
     "NO_ACCOUNT_QUESTION": "Not a member yet? Register now!",
     "SEARCH_NO_RESULT": "No result found",
     "LOADING": "Loading...",
-    "LOADING_WAIT": "Loading...<br/><small>(Waiting for node availability)</small>",
+    "LOADING_WAIT": "Loading...<br/><small>(Cesium is querying the Duniter peer)</small>",
     "SEARCHING": "Searching...",
     "FROM": "From",
     "TO": "To",
@@ -144,6 +144,9 @@
     "PEER": "Duniter peer address",
     "PEER_SHORT": "Peer address",
     "PEER_CHANGED_TEMPORARY": "Address used temporarily",
+    "PEER_SELECTED_AUTOMATICALLY": "Peer selected automatically at startup",
+    "PEER_TIMEOUT": "Maximum waiting time (timeout)",
+    "PEER_TIMEOUT_HELP": "Maximum waiting time to get a response from the node",
     "PERSIST_CACHE": "Keep navigation data (experimental)",
     "PERSIST_CACHE_HELP": "Allows faster navigation, locally retaining the data received, for use from one session to another.",
     "USE_LOCAL_STORAGE": "Enable local storage",
@@ -821,7 +824,8 @@
     "REVOCATION_SENT": "Revocation sent successfully",
     "REVOCATION_SENT_WAITING_PROCESS": "Revocation <b>has been sent successfully</b>. It is awaiting processing.",
     "FEATURES_NOT_IMPLEMENTED": "This features is not implemented yet.<br/><br/>Why not to contribute to get it faster? ;)",
-    "EMPTY_TX_HISTORY": "No operations to export"
+    "EMPTY_TX_HISTORY": "No operations to export",
+    "LOADING_PENDING_TX": "Please wait...<br/><small>(Retrieving pending operations)</small>"
   },
   "CONFIRM": {
     "CAN_CONTINUE": "<b>Are you sure</b> you want to continue?",
diff --git a/www/i18n/locale-eo-EO.json b/www/i18n/locale-eo-EO.json
index c504731fc046063d6850d9ec5ce997392bfe98ea..4b121e8ddc172fcd2ad9445313cdee82061773f6 100644
--- a/www/i18n/locale-eo-EO.json
+++ b/www/i18n/locale-eo-EO.json
@@ -49,7 +49,7 @@
     "NO_ACCOUNT_QUESTION": "Ankoraŭ sen konto? Kreu ĝin senpage!",
     "SEARCH_NO_RESULT": "Neniu rezulto trovita",
     "LOADING": "Bonvolu pacienci...",
-    "LOADING_WAIT": "Bonvolu pacienci...<br/><small>(Atendado pri disponebleco de la nodo)</small>",
+    "LOADING_WAIT": "Bonvolu pacienci...<br/><small>(Cesium demandas la Duniter-nodon)</small>",
     "SEARCHING": "Serĉanta...",
     "FROM": "De",
     "TO": "Al",
@@ -144,6 +144,9 @@
     "PEER": "Adreso de la nodo Duniter",
     "PEER_SHORT": "Adreso de la nodo",
     "PEER_CHANGED_TEMPORARY": "Adreso provizore uzata",
+    "PEER_SELECTED_AUTOMATICALLY": "Nodo aŭtomate elektita ĉe starto",
+    "PEER_TIMEOUT": "Maksimuma atendotempo (eltempigo)",
+    "PEER_TIMEOUT_HELP": "Maksimuma atendotempo por ricevi respondon de la nodo",
     "PERSIST_CACHE": "Konservi la datenojn pri retumado (provaĵo)",
     "PERSIST_CACHE_HELP": "Ebligas pli rapidan retumadon, loke konservante la ricevitajn datenojn, por uzi ilin de seanco al alia.",
     "USE_LOCAL_STORAGE": "Aktivigi lokan stokadon",
@@ -802,7 +805,8 @@
     "REVOCATION_SENT": "Nuligo sendita",
     "REVOCATION_SENT_WAITING_PROCESS": "La <b>nuligo de tiu ĉi identeco</b> estis petita kaj atendas traktadon.",
     "FEATURES_NOT_IMPLEMENTED": "Tiu ĉi funkciaro ankoraŭ estas programiĝanta.<br/>Kial ne <b>kontribui al Cesium</b>, por ekhavi ĝin pli rapide? ;)",
-    "EMPTY_TX_HISTORY": "Neniu spezo elportota"
+    "EMPTY_TX_HISTORY": "Neniu spezo elportota",
+    "LOADING_PENDING_TX": "Bonvolu pacienci...<br/><small>(Prenado de pendaj operacioj)</small>"
   },
   "CONFIRM": {
     "CAN_CONTINUE": "<b>Ĉu vi certas</b>, ke vi volas daŭrigi?",
diff --git a/www/i18n/locale-es-CT.json b/www/i18n/locale-es-CT.json
index 816c02a84d3913c8e105d2cf43054a74dbd80e27..529a945c19c882b79615ecca6b036fcffdd3c321 100644
--- a/www/i18n/locale-es-CT.json
+++ b/www/i18n/locale-es-CT.json
@@ -49,7 +49,7 @@
     "NO_ACCOUNT_QUESTION": "Encara no ets membre? Fes-te un compte!",
     "SEARCH_NO_RESULT": "No s'ha trobat res",
     "LOADING": "Esperi si us plau...",
-    "LOADING_WAIT": "Esperi si us plau...<br/><small>(Esperant un node disponible)</small>",
+    "LOADING_WAIT": "Esperi si us plau...<br/><small>(Cesium està interrogant el node Duniter)</small>",
     "SEARCHING": "Cerca en procés...",
     "FROM": "De",
     "TO": "A",
@@ -137,6 +137,9 @@
     "NETWORK_SETTINGS": "Xarxa",
     "PEER": "Adreça del node Duniter",
     "PEER_CHANGED_TEMPORARY": "Adreça usada temporalment",
+    "PEER_SELECTED_AUTOMATICALLY": "Node seleccionat automàticament a l'inici",
+    "PEER_TIMEOUT": "Temps d'espera màxim (temps d'espera)",
+    "PEER_TIMEOUT_HELP": "Temps d'espera màxim per obtenir una resposta del node",
     "PEER_SHORT": "Node Duniter",
     "PERSIST_CACHE": "Conservar los datos de navegación (experimental)",
     "PERSIST_CACHE_HELP": "Permite una navegación más rápida, conservando localmente los datos recibidos, para usar de una sesión a otra.",
@@ -887,7 +890,8 @@
     "REVOCATION_SENT": "Revocación enviada",
     "REVOCATION_SENT_WAITING_PROCESS": "La <b>revocación de esta identidad</b> fue solicitada y está en espera de ser procesada.",
     "FEATURES_NOT_IMPLEMENTED": "Esta funcionalidad todavía está en proceso de desarrollo.<br/><br/>¿Por qué no <b>contribuir a Cesium</b>, para obtenerla más rápido? ;)",
-    "EMPTY_TX_HISTORY": "Ninguna operación a exportar"
+    "EMPTY_TX_HISTORY": "Ninguna operación a exportar",
+    "LOADING_PENDING_TX": "Esperi si us plau...<br/><small>(Recuperant operacions pendents)</small>"
   },
   "CONFIRM": {
     "EXIT_APP": "¿ Cerrar la aplicación ?",
diff --git a/www/i18n/locale-es-ES.json b/www/i18n/locale-es-ES.json
index 1bd2ed81d1c530fb72c0591f9ccc7c9123da47cd..d11aa14499cb512da7461e5eda076e03a328bd25 100644
--- a/www/i18n/locale-es-ES.json
+++ b/www/i18n/locale-es-ES.json
@@ -48,8 +48,8 @@
     "DAYS": "Días",
     "NO_ACCOUNT_QUESTION": "¿Todavía no es miembro? ¡Crear una cuenta!",
     "SEARCH_NO_RESULT": "Ningún resultado encontrado",
-    "LOADING": "Espere por favor…",
-    "LOADING_WAIT": "Espere por favor…<br/><small>(Esperando disponibilidad de nodo)</small>",
+    "LOADING": "Espere por favor...",
+    "LOADING_WAIT": "Espere por favor...<br/><small>(Cesium está consultando al nodo Duniter)</small>",
     "SEARCHING": "Búsqueda en proceso…",
     "FROM": "De",
     "TO": "A",
@@ -137,6 +137,9 @@
     "NETWORK_SETTINGS": "Red",
     "PEER": "Dirección del nodo Duniter",
     "PEER_CHANGED_TEMPORARY": "Dirección utilizada temporalmente",
+    "PEER_SELECTED_AUTOMATICALLY": "Nodo seleccionado automáticamente al inicio",
+    "PEER_TIMEOUT": "Tiempo de espera máximo (timeout)",
+    "PEER_TIMEOUT_HELP": "Tiempo de espera máximo para obtener una respuesta del nodo",
     "PEER_SHORT": "Nodo Duniter",
     "PERSIST_CACHE": "Conservar los datos de navegación (experimental)",
     "PERSIST_CACHE_HELP": "Permite una navegación más rápida, conservando localmente los datos recibidos, para usar de una sesión a otra.",
@@ -887,7 +890,8 @@
     "REVOCATION_SENT": "Revocación enviada",
     "REVOCATION_SENT_WAITING_PROCESS": "La <b>revocación de esta identidad</b> fue solicitada y está en espera de ser procesada.",
     "FEATURES_NOT_IMPLEMENTED": "Esta funcionalidad todavía está en proceso de desarrollo.<br/><br/>¿Por qué no <b>contribuir a Cesium</b>, para obtenerla más rápido? ;)",
-    "EMPTY_TX_HISTORY": "Ninguna operación a exportar"
+    "EMPTY_TX_HISTORY": "Ninguna operación a exportar",
+    "LOADING_PENDING_TX": "Espere por favor...<br/><small>(Recuperando operaciones pendientes)</small>"
   },
   "CONFIRM": {
     "CAN_CONTINUE": "¿Desea continuar?",
diff --git a/www/i18n/locale-fr-FR.json b/www/i18n/locale-fr-FR.json
index 141f513f440845ec3a7966fcfc186c255c9738ea..9b51b7050c8a91173734ebade0a73d06ee60ffdd 100644
--- a/www/i18n/locale-fr-FR.json
+++ b/www/i18n/locale-fr-FR.json
@@ -144,7 +144,9 @@
     "PEER": "NÅ“ud Duniter",
     "PEER_SHORT": "NÅ“ud Duniter",
     "PEER_CHANGED_TEMPORARY": "Adresse utilisée temporairement",
-    "PEER_SELECTED_AUTOMATICALLY": "Sélectionné automatiquement au démarrage",
+    "PEER_SELECTED_AUTOMATICALLY": "Sélection automatiquement du nœud au démarrage",
+    "PEER_TIMEOUT": "Délai d'attente maximal (timeout)",
+    "PEER_TIMEOUT_HELP": "Délai d'attente maximal pour obtenir la réponse du nœud",
     "PERSIST_CACHE": "Conserver les données de navigation (expérimental)",
     "PERSIST_CACHE_HELP": "Permet une navigation plus rapide, en conservant localement les données reçues, pour les utiliser d'une session à l'autre.",
     "USE_LOCAL_STORAGE": "Activer le stockage local",
@@ -704,6 +706,7 @@
     "UNKNOWN_URI_FORMAT": "Format d'URI inconnu",
     "PUBKEY_INVALID_CHECKSUM": "Clé publique invalide (bad checksum).",
     "POPUP_TITLE": "Erreur",
+    "TIMEOUT_REACHED": "Délai d'attente du nœud dépassé.<br/><br/><small>{{url}}</small>",
     "UNKNOWN_ERROR": "Erreur inconnue",
     "CRYPTO_UNKNOWN_ERROR": "Votre navigateur ne semble pas compatible avec les fonctionnalités de cryptographie.",
     "DOWNLOAD_KEYFILE_FAILED": "Échec de la génération du fichier de trousseau.",
@@ -825,7 +828,7 @@
     "REVOCATION_SENT_WAITING_PROCESS": "La <b>révocation de cette identité</b> a été demandée et est en attente de traitement.",
     "FEATURES_NOT_IMPLEMENTED": "Cette fonctionnalité est encore en cours de développement.<br/>Pourquoi ne pas <b>contribuer à Cesium</b>, pour l'obtenir plus rapidement ? ;)",
     "EMPTY_TX_HISTORY": "Aucune opération à exporter",
-    "LOADING_PENDING_TX": "Lecture des opérations en attente"
+    "LOADING_PENDING_TX": "Veuillez patienter...<br/><small>(Récupération des opérations en attente)</small>"
   },
   "CONFIRM": {
     "CAN_CONTINUE": "<b>Êtes-vous sûr</b> de vouloir continuer ?",
diff --git a/www/i18n/locale-it-IT.json b/www/i18n/locale-it-IT.json
index 31780da81c841d223a4a36e151a6b6cb9998253b..82fc249158fd6ebb89fd1d6d18a26d896981099b 100644
--- a/www/i18n/locale-it-IT.json
+++ b/www/i18n/locale-it-IT.json
@@ -144,6 +144,9 @@
     "PEER": "Indirizzo nodo Duniter",
     "PEER_SHORT": "Indirizzo nodo",
     "PEER_CHANGED_TEMPORARY": "Indirizzo usato temporaneamente",
+    "PEER_SELECTED_AUTOMATICALLY": "Nodo selezionato automaticamente all'avvio",
+    "PEER_TIMEOUT": "Tempo di attesa massimo (timeout)",
+    "PEER_TIMEOUT_HELP": "Tempo di attesa massimo per ottenere una risposta dal nodo",
     "PERSIST_CACHE": "Mantenere i dati di navigazione (sperimentale)",
     "PERSIST_CACHE_HELP": "per la fruizione da una sessione all'altra., Consente una navigazione più veloce, conservando localmente i dati ricevuti, per un utilizzo da una sessione all'altra.",
     "USE_LOCAL_STORAGE": "Permetti salvataggio locale",
@@ -803,7 +806,8 @@
     "REVOCATION_SENT": "Revoca dell'identità inviata",
     "REVOCATION_SENT_WAITING_PROCESS": "Cancellazione dell'identità <b>inviata con successo</b>. In attesa di validazione.",
     "FEATURES_NOT_IMPLEMENTED": "Questa funzionalità non è ancora disponibile.<br/><br/>Vuoi contribuire per velocizzarne la disponibilità? ;)",
-    "EMPTY_TX_HISTORY": "Nessuna operazione da esportare"
+    "EMPTY_TX_HISTORY": "Nessuna operazione da esportare",
+    "LOADING_PENDING_TX": "Caricando...<br/><small>(Recupero delle operazioni in sospeso)</small>"
   },
   "CONFIRM": {
     "CAN_CONTINUE": "<b>Sei sicuro/a</b> di voler procedere?",
diff --git a/www/i18n/locale-nl-NL.json b/www/i18n/locale-nl-NL.json
index 64517eb1038ee7ff0d556ce6ca5f6565f5184063..d4bf72e93ccedbc6b2e465ee28a60315751e0d05 100644
--- a/www/i18n/locale-nl-NL.json
+++ b/www/i18n/locale-nl-NL.json
@@ -45,6 +45,7 @@
     "NO_ACCOUNT_QUESTION": "Nog geen lid? Registreer nu!",
     "SEARCH_NO_RESULT": "Geen resultaten",
     "LOADING": "Even geduld...",
+    "LOADING_WAIT": "Even geduld...<br/><small>(Cesium vraagt de Duniter-node op)</small>",
     "SEARCHING": "Zoeken...",
     "FROM": "Van",
     "TO": "Aan",
@@ -120,6 +121,9 @@
     "NETWORK_SETTINGS": "Netwerk",
     "PEER": "Duniter knooppunt adres",
     "PEER_CHANGED_TEMPORARY": "Adres tijdelijk worden gebruikt",
+    "PEER_SELECTED_AUTOMATICALLY": "Node automatisch geselecteerd bij opstarten",
+    "PEER_TIMEOUT": "Maximale wachttijd (time-out)",
+    "PEER_TIMEOUT_HELP": "Maximale wachttijd om een reactie van de node te krijgen",
     "USE_LOCAL_STORAGE": "Lokale opslag inschakelen",
     "USE_LOCAL_STORAGE_HELP": "Laat je instellingen opslaan",
     "ENABLE_HELPTIP": "Contextgebonden hulp inschakelen",
@@ -490,7 +494,8 @@
     "REVOCATION_SENT": "Intrekking succesvol verzonden",
     "REVOCATION_SENT_WAITING_PROCESS": "Intrekking <b>is succesvol verzonden</b>. Het wacht op verwerking.",
     "FEATURE_NOT_AVAILABLE_ON_DEMO": "Functionaliteit niet beschikbaar op deze demonstratiesite.<br/>Om <b>veiligheidsredenen</b> raden we u aan uw kopie van de software te <b>installeren</b>.<br/>Bezoek de website <a href='https://cesium.app'>www.cesium.app</a> voor hulp.",
-    "EMPTY_TX_HISTORY": "Aucune operatie à exporteur"
+    "EMPTY_TX_HISTORY": "Aucune operatie à exporteur",
+    "LOADING_PENDING_TX": "Even geduld...<br/><small>(Ophalen van hangende operaties)</small>"
   },
   "CONFIRM": {
     "CAN_CONTINUE": "<b>Weet je zeker</b> dat je door wil gaan?",
diff --git a/www/i18n/locale-pt-PT.json b/www/i18n/locale-pt-PT.json
index cc360777b4bedc89d0249fc42414d9a1d6d4d5da..0e8fa38d2011a0b6d4efe79f46b57ab411e4fe79 100644
--- a/www/i18n/locale-pt-PT.json
+++ b/www/i18n/locale-pt-PT.json
@@ -48,8 +48,8 @@
     "DAYS": "Dias",
     "NO_ACCOUNT_QUESTION": "Ainda não é membro? Criar uma conta!",
     "SEARCH_NO_RESULT": "Não foi encontrado nenhum resultado",
-    "LOADING": "Aguarde por favor…",
-    "LOADING_WAIT": "Aguarde por favor…<br/><small>(Aguardando disponibilidade de nó)</small>",
+    "LOADING": "Aguarde por favor...",
+    "LOADING_WAIT": "Aguarde por favor...<br/><small>(Cesium está a questionar o nó Duniter)</small>",
     "SEARCHING": "Procurando…",
     "FROM": "De",
     "TO": "Para",
@@ -137,6 +137,9 @@
     "NETWORK_SETTINGS": "Rede",
     "PEER": "Endereço do nó Duniter",
     "PEER_CHANGED_TEMPORARY": "Endereço utilizado temporariamente",
+    "PEER_SELECTED_AUTOMATICALLY": "Nó selecionado automaticamente na inicialização",
+    "PEER_TIMEOUT": "Tempo máximo de espera (timeout)",
+    "PEER_TIMEOUT_HELP": "Tempo máximo de espera para obter uma resposta do nó",
     "PEER_SHORT": "Nó Duniter",
     "PERSIST_CACHE": "Conservar os dados de navegação (experimental)",
     "PERSIST_CACHE_HELP": "Permite uma navegação mais rápida, manter localmente os dados recebidos, para usar entre sessões.",
@@ -887,7 +890,8 @@
     "REVOCATION_SENT": "Revogação enviada",
     "REVOCATION_SENT_WAITING_PROCESS": "A <b>revogação desta identidade</b> foi solicitada e aguarda ser processada.",
     "FEATURES_NOT_IMPLEMENTED": "Esta funcionalidade encontra-se em desenvolvimento.<br/><br/>Porque não <b>contribuir com Cesium</b>, para obtê-la mais rápido? ;)",
-    "EMPTY_TX_HISTORY": "Nenhuma operação a exportar"
+    "EMPTY_TX_HISTORY": "Nenhuma operação a exportar",
+    "LOADING_PENDING_TX": "Aguarde por favor...<br/><small>(A recuperar operações pendentes)</small>"
   },
   "CONFIRM": {
     "CAN_CONTINUE": "<b>Deseja</b> continuar?",
diff --git a/www/js/app.js b/www/js/app.js
index 39116d84dc9b0fed4385924ce9c74712c3d768a2..a17fdcb4be73113ea3691df93052e66b3e94b323 100644
--- a/www/js/app.js
+++ b/www/js/app.js
@@ -61,23 +61,29 @@ angular.module('cesium', ['ionic', 'ionic-material', 'ngMessages', 'ngSanitize',
       if (nextParams.wallet && !wallet) {
         console.warn("[app] Unable to find the children wallet: " + nextParams.wallet);
       }
+      var goNextState = function() {
+        preventStateChange = false;
+        return $state.go(next.name, nextParams);
+      };
+      var processError = function(err) {
+        preventStateChange = false;
+        // If cancel, redirect to home, if no current state
+        if (err === 'CANCELLED' && !$state.current.name) {
+          return $state.go('app.home');
+        }
+        if (!options || !options.minData) UIUtils.loading.hide();
+        UIUtils.onError('ERROR.LOAD_WALLET_DATA_ERROR')(err);
+        throw err;
+      };
       // If state need auth
       if (next.data.auth && !wallet.isAuth()) {
         event.preventDefault();
         options = next.data.minData ? {minData: true} : undefined;
         preventStateChange = true;
+        console.debug("[app] State need auth...");
         return csWallet.auth(options)
-          .then(function() {
-            preventStateChange = false;
-            return $state.go(next.name, nextParams);
-          })
-          .catch(function(err) {
-            preventStateChange = false;
-            // If cancel, redirect to home, if no current state
-            if (err === 'CANCELLED' && !$state.current.name) {
-              return $state.go('app.home');
-            }
-          });
+          .then(goNextState)
+          .catch(processError);
       }
 
       // If state need login
@@ -85,18 +91,10 @@ angular.module('cesium', ['ionic', 'ionic-material', 'ngMessages', 'ngSanitize',
         event.preventDefault();
         options = next.data.minData ? {minData: true} : undefined;
         preventStateChange = true;
+        console.debug("[app] State need login...");
         return csWallet.login(options)
-          .then(function() {
-            preventStateChange = false;
-            return $state.go(next.name, nextParams);
-          })
-          .catch(function(err) {
-            preventStateChange = false;
-            // If cancel, redirect to home, if no current state
-            if (err === 'CANCELLED' && !$state.current.name) {
-              return $state.go('app.home');
-            }
-          });
+          .then(goNextState)
+          .catch(processError);
       }
 
       // If state need login or auth, make sure to load wallet data
@@ -106,11 +104,11 @@ angular.module('cesium', ['ionic', 'ionic-material', 'ngMessages', 'ngSanitize',
           event.preventDefault();
           // Show loading message, when full load
           if (!options || !options.minData) UIUtils.loading.show();
+
+          console.debug("[app] State load wallet data...");
           return wallet.loadData(options)
-            .then(function() {
-              preventStateChange = false;
-              return $state.go(next.name, nextParams);
-            });
+            .then(goNextState)
+            .catch(processError)
         }
       }
     });
diff --git a/www/js/config.js b/www/js/config.js
index b1356a9659873eb01f750bba6248c5afec2b24b9..0280009e207803da9de4c9a016ff99f675afb438 100644
--- a/www/js/config.js
+++ b/www/js/config.js
@@ -15,7 +15,7 @@ angular.module("cesium.config", [])
 	"fallbackLanguage": "en",
 	"rememberMe": true,
 	"showUDHistory": true,
-	"timeout": 40000,
+	"timeout": 10000,
 	"timeWarningExpireMembership": 5184000,
 	"timeWarningExpire": 7776000,
 	"keepAuthIdle": 600,
diff --git a/www/js/controllers/network-controllers.js b/www/js/controllers/network-controllers.js
index 35289842cbbbdd9faeada129d8c8c9e2bd12d40e..02c74a56a7701f611b7187dd4bd09fa2ad4c7987 100644
--- a/www/js/controllers/network-controllers.js
+++ b/www/js/controllers/network-controllers.js
@@ -54,6 +54,7 @@ function NetworkLookupController($scope,  $state, $location, $ionicPopover, $win
   $scope.networkStarted = false;
   $scope.ionItemClass = '';
   $scope.expertMode = csSettings.data.expertMode && !UIUtils.screen.isSmall();
+  $scope.timeout = csSettings.data.timeout;
   $scope.isHttps = ($window.location.protocol === 'https:');
   $scope.search = {
     text: '',
@@ -148,8 +149,7 @@ function NetworkLookupController($scope,  $state, $location, $ionicPopover, $win
         asc : $scope.search.asc
       },
       expertMode: $scope.expertMode,
-      // larger timeout when on expert mode
-      timeout: csConfig.timeout && ($scope.expertMode ? (csConfig.timeout / 10) : (csConfig.timeout / 100))
+      timeout: angular.isDefined($scope.timeout) ? $scope.timeout : Device.network.timeout()
     };
     return options;
   };
@@ -422,6 +422,7 @@ function NetworkLookupModalController($scope, $controller, parameters) {
   $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.timeout = angular.isDefined(parameters.timeout) ? parameters.timeout : $scope.timeout;
   $scope.ionItemClass = parameters.ionItemClass || 'item-border-large';
   $scope.enableLocationHref = false;
   $scope.helptipPrefix = '';
diff --git a/www/js/controllers/settings-controllers.js b/www/js/controllers/settings-controllers.js
index 9292608e7ce12b01fb72ede530ae668cb61cfa5a..6606308bec0878b5ca0e8ba853fd44bd80e462c2 100644
--- a/www/js/controllers/settings-controllers.js
+++ b/www/js/controllers/settings-controllers.js
@@ -34,7 +34,16 @@ function SettingsController($scope, $q, $window, $ionicHistory, $ionicPopup, $ti
     loading: !csPlatform.isStarted(),
     loadingMessage: 'COMMON.LOADING'
   };
-
+  // Fill timeout
+  $scope.timeouts = [
+    500,
+    1000,
+    5000,
+    10000,
+    30000,
+    60000,
+    300000
+  ];
   $scope.keepAuthIdleLabels = {
     /*0: {
       labelKey: 'SETTINGS.KEEP_AUTH_OPTION.NEVER'
@@ -122,6 +131,8 @@ function SettingsController($scope, $q, $window, $ionicHistory, $ionicPopup, $ti
     // Fill locales
     $scope.locales = angular.copy(csSettings.locales);
 
+
+
     // Apply settings
     angular.merge($scope.formData, csSettings.data);
 
@@ -152,7 +163,7 @@ function SettingsController($scope, $q, $window, $ionicHistory, $ionicPopup, $ti
   $scope.leave = function() {
     console.debug('[settings] Leaving page');
     $scope.removeListeners();
-  }
+  };
 
   $scope.reset = function() {
     if ($scope.actionsPopover) {
@@ -207,41 +218,41 @@ function SettingsController($scope, $q, $window, $ionicHistory, $ionicPopup, $ti
           ($scope.formData.node.port == 443)
       };
     $scope.showNodePopup(node)
-    .then(function(newNode) {
-      if (newNode.host === $scope.formData.node.host &&
-        newNode.port === $scope.formData.node.port &&
-        newNode.useSsl === $scope.formData.node.useSsl && !$scope.formData.node.temporary) {
-        return; // same node = nothing to do
-      }
+      .then(function(newNode) {
+        if (newNode.host === $scope.formData.node.host &&
+          newNode.port === $scope.formData.node.port &&
+          newNode.useSsl === $scope.formData.node.useSsl && !$scope.formData.node.temporary) {
+          return; // same node = nothing to do
+        }
 
-      // Change to expert mode
-      $scope.formData.expertMode = true;
+        // Change to expert mode
+        $scope.formData.expertMode = true;
 
-      UIUtils.loading.show();
+        UIUtils.loading.show();
 
-      BMA.isAlive(newNode)
-        .then(function(alive) {
-          if (!alive) {
+        BMA.isAlive(newNode)
+          .then(function(alive) {
+            if (!alive) {
+              UIUtils.loading.hide();
+              return UIUtils.alert.error('ERROR.INVALID_NODE_SUMMARY')
+                .then(function(){
+                  $scope.changeNode(newNode, true); // loop
+                });
+            }
             UIUtils.loading.hide();
-            return UIUtils.alert.error('ERROR.INVALID_NODE_SUMMARY')
-              .then(function(){
-                $scope.changeNode(newNode, true); // loop
-              });
-          }
-          UIUtils.loading.hide();
-          angular.merge($scope.formData.node, newNode);
-          delete $scope.formData.node.temporary;
-          BMA.stop();
-          BMA.copy(newNode);
-          $scope.bma = BMA;
-
-          // Restart platform (or start if not already started)
-          csPlatform.restart();
-
-          // Reset history cache
-          return $ionicHistory.clearCache();
-        });
-    });
+            angular.merge($scope.formData.node, newNode);
+            delete $scope.formData.node.temporary;
+            BMA.stop();
+            BMA.copy(newNode);
+            $scope.bma = BMA;
+
+            // Restart platform (or start if not already started)
+            csPlatform.restart();
+
+            // Reset history cache
+            return $ionicHistory.clearCache();
+          });
+      });
   };
 
   $scope.showNodeList = function() {
@@ -452,8 +463,6 @@ function SettingsController($scope, $q, $window, $ionicHistory, $ionicPopup, $ti
       });
   };
 
-
-
   $scope.removeListeners = function() {
     if ($scope.listeners.length) {
       console.debug('[settings] Closing listeners');
diff --git a/www/js/filters.js b/www/js/filters.js
index e4310f818661bdeef426bbb70d99d45dc73cc2a1..7f8367c77358643ccc7c33f770a595ac7af56572 100644
--- a/www/js/filters.js
+++ b/www/js/filters.js
@@ -291,11 +291,32 @@ angular.module('cesium.filters', ['cesium.config', 'cesium.platform', 'pascalpre
   // Display time in ms or seconds (see i18n label 'COMMON.EXECUTION_TIME')
   .filter('formatDurationMs', function() {
     return function(input) {
-      return input ? (
-        (input < 1000) ?
-          (input + 'ms') :
-          (input/1000 + 's')
-      ) : '';
+      if (!input) return '';
+
+      if (input < 1000) {
+        return  input + 'ms';
+      }
+
+      let result = '';
+      const hours = Math.floor(input / (1000 * 60 * 60));
+      const minutes = Math.floor((input % (1000 * 60 * 60)) / (1000 * 60));
+      const seconds = Math.floor((input % (1000 * 60)) / 1000);
+      const milliseconds = Math.floor(input % 1000);
+
+      if (hours > 0) {
+        result += hours + 'h ';
+      }
+      if (minutes > 0) {
+        result += minutes + 'min ';
+      }
+      if (seconds > 0) {
+        result += seconds + 's ';
+      }
+      if (milliseconds > 0) {
+        result += milliseconds + 'ms';
+      }
+
+      return result.trim();
     };
   })
 
diff --git a/www/js/platform.js b/www/js/platform.js
index 44720b39edda88d71b0028b41a4f1f97d820452c..1afcac3728b44894d1c4dab2123418b1e2363d4f 100644
--- a/www/js/platform.js
+++ b/www/js/platform.js
@@ -152,7 +152,9 @@ angular.module('cesium.platform', ['ngIdle', 'cesium.config', 'cesium.services']
       if (checkBmaNodeAliveCounter > 3)  throw 'ERROR.CHECK_NETWORK_CONNECTION'; // Avoid infinite loop
 
       api.start.raise.message('NETWORK.INFO.CONNECTING_TO_PEER');
-      return BMA.filterAliveNodes(csSettings.data.fallbackNodes, csConfig.timeout)
+
+      const timeout = csSettings.data.expertMode ? csSettings.data.timeout : Device.network.timeout(csConfig.timeout);
+      return BMA.filterAliveNodes(csSettings.data.fallbackNodes, timeout)
         .then(function (fallbackNodes) {
           if (!fallbackNodes.length) throw 'ERROR.CHECK_NETWORK_CONNECTION';
           return _.sample(fallbackNodes); // Random select
@@ -196,9 +198,7 @@ angular.module('cesium.platform', ['ngIdle', 'cesium.config', 'cesium.services']
 
       var askUserConfirmation = csSettings.data.expertMode;
 
-      return csNetwork.getSynchronizedBmaPeers(BMA, {
-          timeout:  Math.min(csConfig.timeout, 10000 /*10s max*/)
-        })
+      return csNetwork.getSynchronizedBmaPeers(BMA)
         .then(function(peers) {
 
           if (!peers.length) return; // No peer found: exit
@@ -242,11 +242,11 @@ angular.module('cesium.platform', ['ngIdle', 'cesium.config', 'cesium.services']
               }
 
               var randomPeer = _.sample(peers);
-              var synchronizedNode = {
+              var synchronizedNode = new Peer({
                 host: randomPeer.getHost(),
                 port: randomPeer.getPort(),
                 useSsl: randomPeer.isSsl()
-              };
+              });
 
               // If Expert mode: ask user to select a node
               if (askUserConfirmation) {
@@ -276,7 +276,8 @@ angular.module('cesium.platform', ['ngIdle', 'cesium.config', 'cesium.services']
 
     function askUseFallbackNode(fallbackNode) {
       // Ask user to confirm, before switching to fallback node
-      var confirmMsgParams = {old: BMA.server, new: fallbackNode.server};
+      var server = fallbackNode.server || (typeof fallbackNode.getServer === 'function' ? fallbackNode.getServer() : new Peer(fallbackNode).getServer())
+      var confirmMsgParams = {old: BMA.server, new: server};
 
       // Force to show port/ssl, if this is the only difference
       if (confirmMsgParams.old === confirmMsgParams.new) {
@@ -286,6 +287,9 @@ angular.module('cesium.platform', ['ngIdle', 'cesium.config', 'cesium.services']
           confirmMsgParams.new += ' (SSL)';
         }
       }
+      if (!server) {
+        console.warn('[] TODO Invalid peer ', fallbackNode);
+      }
 
       return $translate('CONFIRM.USE_FALLBACK_NODE', confirmMsgParams)
         .then(UIUtils.alert.confirm)
@@ -424,7 +428,7 @@ angular.module('cesium.platform', ['ngIdle', 'cesium.config', 'cesium.services']
           startPromise = null;
           started = false;
           api.start.raise.message(''); // Reset message
-          if($state.current.name !== $rootScope.errorState) {
+          if ($state.current.name !== $rootScope.errorState) {
             $state.go($rootScope.errorState, {error: 'peer'});
           }
           throw err;
diff --git a/www/js/services/bma-services.js b/www/js/services/bma-services.js
index a7de712bf2aa3d53a9b3af782b5a7d5612d72e9d..81d73a3fa7fc8baa04fe0d3b28bb1780e6bc2e75 100644
--- a/www/js/services/bma-services.js
+++ b/www/js/services/bma-services.js
@@ -5,7 +5,7 @@ angular.module('cesium.bma.services', ['ngApi', 'cesium.http.services', 'cesium.
 .factory('BMA', function($q, $window, $rootScope, $timeout, csCrypto, Api, Device, UIUtils, csConfig, csSettings, csCache, csHttp) {
   'ngInject';
 
-  function BMA(host, port, useSsl, useCache) {
+  function BMA(host, port, useSsl, useCache, timeout) {
 
     var
       id = (!host ? 'default' : '{0}:{1}'.format(host, (port || (useSsl ? '443' : '80')))), // Unique id of this instance
@@ -67,6 +67,14 @@ 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 ms) to wait between to call of a rest request
+        TIMEOUT: {
+          NONE: -1,
+          SHORT: 1000, // 1s
+          MEDIUM: 5000, // 5s
+          LONG: 10000, // 10s
+          DEFAULT: csConfig.timeout,
+          VERY_LONG: 60000
+        },
         regexp: regexp,
         api: api
       },
@@ -143,9 +151,10 @@ angular.module('cesium.bma.services', ['ngApi', 'cesium.http.services', 'cesium.
      that.raw.wsByPath = {};
    }
 
-   function get(path, cacheTime) {
+    function get(path, cacheTime, forcedTimeout) {
 
-      cacheTime = that.useCache && cacheTime || 0 /* no cache*/ ;
+      cacheTime = that.useCache && cacheTime || 0 /* no cache*/ ;
+      forcedTimeout = forcedTimeout || timeout;
       var requestKey = path + (cacheTime ? ('#'+cacheTime) : '');
 
       var getRequestFn = function(params) {
@@ -162,10 +171,10 @@ angular.module('cesium.bma.services', ['ngApi', 'cesium.http.services', 'cesium.
         var request = that.raw.getByPath[requestKey];
         if (!request) {
           if (cacheTime) {
-            request = csHttp.getWithCache(that.host, that.port, path, that.useSsl, cacheTime, null, null, cachePrefix);
+            request = csHttp.getWithCache(that.host, that.port, path, that.useSsl, cacheTime, null/*autoRefresh*/, forcedTimeout, cachePrefix);
           }
           else {
-            request = csHttp.get(that.host, that.port, path, that.useSsl);
+            request = csHttp.get(that.host, that.port, path, that.useSsl, forcedTimeout);
           }
           that.raw.getByPath[requestKey] = request;
         }
@@ -235,7 +244,7 @@ angular.module('cesium.bma.services', ['ngApi', 'cesium.http.services', 'cesium.
     }
 
     that.isAlive = function(node, timeout) {
-      node = node || that;
+      node = node || that;
       // WARN:
       //  - Cannot use previous get() function, because
       //    node can be !=that, or not be started yet
@@ -338,7 +347,7 @@ angular.module('cesium.bma.services', ['ngApi', 'cesium.http.services', 'cesium.
           if (!listeners || !listeners.length) {
             addListeners();
           }
-          console.debug('[BMA] Started in {}ms'.format(Date.now()-now));
+          console.debug('[BMA] Started in {0}ms'.format(Date.now()-now));
 
           that.api.node.raise.start();
           that.started = true;
@@ -381,7 +390,7 @@ angular.module('cesium.bma.services', ['ngApi', 'cesium.http.services', 'cesium.
     };
 
     that.filterAliveNodes = function(fallbackNodes, timeout) {
-      timeout = timeout || csConfig.timeout;
+      timeout = timeout || csSettings.data.timeout;
 
       // Filter to exclude the current BMA node
       fallbackNodes = _.filter(fallbackNodes || [], function(node) {
@@ -391,6 +400,9 @@ angular.module('cesium.bma.services', ['ngApi', 'cesium.http.services', 'cesium.
         return !same;
       });
 
+
+      console.debug('[BMA] Getting alive fallback nodes... (temiout: {0}ms)'.format(timeout));
+
       var aliveNodes = [];
       return $q.all(_.map(fallbackNodes, function(node) {
         return that.isAlive(node, timeout)
@@ -481,14 +493,14 @@ angular.module('cesium.bma.services', ['ngApi', 'cesium.http.services', 'cesium.
         membership: post('/blockchain/membership'),
         stats: {
           ud: get('/blockchain/with/ud', csCache.constants.MEDIUM),
-          tx: get('/blockchain/with/tx'),
-          newcomers: get('/blockchain/with/newcomers', csCache.constants.MEDIUM),
+          tx: get('/blockchain/with/tx', null, constants.TIMEOUT.LONG),
+          newcomers: get('/blockchain/with/newcomers', csCache.constants.MEDIUM, constants.TIMEOUT.LONG),
           hardship: get('/blockchain/hardship/:pubkey'),
           difficulties: get('/blockchain/difficulties')
         }
       },
       tx: {
-        sources: get('/tx/sources/:pubkey', csCache.constants.SHORT),
+        sources: get('/tx/sources/:pubkey', csCache.constants.SHORT, constants.TIMEOUT.VERY_LONG),
         process: post('/tx/process'),
         history: {
           all: function(params) {
@@ -527,14 +539,14 @@ angular.module('cesium.bma.services', ['ngApi', 'cesium.http.services', 'cesium.
           current: get('/blockchain/current')
         },
         wot: {
-          requirementsWithCache: get('/wot/requirements/:pubkey', csCache.constants.LONG),
-          requirements: get('/wot/requirements/:pubkey')
+          requirementsWithCache: get('/wot/requirements/:pubkey', csCache.constants.LONG, constants.TIMEOUT.LONG),
+          requirements: get('/wot/requirements/:pubkey', null, constants.TIMEOUT.LONG)
         },
         tx: {
           history: {
-            timesWithCache: get('/tx/history/:pubkey/times/:from/:to', csCache.constants.LONG),
-            times: get('/tx/history/:pubkey/times/:from/:to'),
-            all: get('/tx/history/:pubkey')
+            timesWithCache: get('/tx/history/:pubkey/times/:from/:to', csCache.constants.LONG, constants.TIMEOUT.LONG),
+            times: get('/tx/history/:pubkey/times/:from/:to', null, constants.TIMEOUT.LONG),
+            all: get('/tx/history/:pubkey', null, constants.TIMEOUT.LONG)
           }
         },
       }
@@ -1010,15 +1022,15 @@ angular.module('cesium.bma.services', ['ngApi', 'cesium.http.services', 'cesium.
 
   var service = new BMA();
 
-  service.instance = function(host, port, useSsl, useCache) {
+  service.instance = function(host, port, useSsl, useCache, timeout) {
     useCache = angular.isDefined(useCache) ? useCache : false; // No cache by default
-    return new BMA(host, port, useSsl, useCache);
+    return new BMA(host, port, useSsl, useCache, timeout);
   };
 
   service.lightInstance = function(host, port, useSsl, timeout) {
     port = port || 80;
     useSsl = angular.isDefined(useSsl) ? useSsl : (port == 443);
-    timeout = timeout || csConfig.timeout;
+    timeout = timeout || csSettings.data.timeout;
     return {
       host: host,
       port: port,
diff --git a/www/js/services/cache-services.js b/www/js/services/cache-services.js
index 0f132e761390bc7db7e7676d972f6e655d639fbe..1d16939f39c52069fc8fd659b99587d8d98af60f 100644
--- a/www/js/services/cache-services.js
+++ b/www/js/services/cache-services.js
@@ -5,10 +5,10 @@ angular.module('cesium.cache.services', ['angular-cache'])
 
   var
     constants = {
-      VERY_LONG: 54000000, /*15 days*/
-      LONG: 1 * 60  * 60 * 1000 /*1 hour*/,
-      MEDIUM: 5  * 60 * 1000 /*5 min*/,
-      SHORT: csSettings.defaultSettings.cacheTimeMs // around 1min
+      SHORT: csSettings.defaultSettings.cacheTimeMs, // around 1min
+      MEDIUM: 5  * 60 * 1000, // 5 min
+      LONG: 1 * 60  * 60 * 1000, // 1 hour
+      VERY_LONG: 54000000 // 15 days
     },
     storageMode = getSettingsStorageMode(),
     cacheNames = [],
diff --git a/www/js/services/device-services.js b/www/js/services/device-services.js
index 4353a4a2b0c1847c798cee3e783e95cee0a9ef51..b24579723741fa46a8620dffab7667d8888e0352 100644
--- a/www/js/services/device-services.js
+++ b/www/js/services/device-services.js
@@ -145,13 +145,77 @@ angular.module('cesium.device.services', ['cesium.utils.services', 'cesium.setti
       };
       exports.network = {
         connectionType: function() {
-          return navigator.connection.type || 'unknown';
+          if (!exports.network.enable) return  'unknown';
+          try {
+            return navigator.connection.type || 'unknown';
+          }
+          catch(err) {
+            console.error('[device] Cannot get connection type: ' + (err && err.message || err), err);
+            return 'unknown';
+          }
         },
         isOnline: function() {
-          return navigator.connection.type !== Connection.NONE;
+          try {
+            return navigator.connection.type !== Connection.NONE;
+          }
+          catch(err) {
+            console.error('[device] Cannot check if online: ' + (err && err.message || err), err);
+            return true;
+          }
         },
         isOffline: function() {
-          return navigator.connection.type === Connection.NONE;
+          try {
+            return navigator.connection.type === Connection.NONE;
+          }
+          catch(err) {
+            console.error('[device] Cannot check if offline: ' + (err && err.message || err), err);
+            return true;
+          }
+          return false;
+        },
+        timeout: function(defaultTimeout) {
+          defaultTimeout = defaultTimeout || csConfig.timeout;
+          var timeout;
+          try {
+            var connectionType = exports.network.connectionType();
+
+            // If desktop: use ethernet as default connection type
+            if (connectionType === 'unknown' && exports.isDesktop()) {
+              connectionType = 'ethernet';
+            }
+
+            switch (connectionType) {
+              case 'ethernet':
+                timeout = 1000; // 1 s
+                break;
+              case 'wifi':
+                timeout = 5000; // 5 s
+                break;
+              case 'cell': // (e.g. iOS)
+              case 'cell_4g':
+                timeout = 10000; // 10s for 4G
+                break;
+              case 'cell_2g': // Added cell_2g case
+                timeout = 60000; // 60s for 2G
+                break;
+              case 'cell_3g': // Added cell_3g case
+                timeout = 30000; // 30s for 2G
+                break;
+              case 'none':
+                timeout = 0;
+                break;
+              case 'unknown':
+              default:
+                timeout = defaultTimeout;
+                break;
+            }
+            console.debug('[network] Using timeout: {1}ms (connection type: \'{0}\')'.format(connectionType, timeout));
+
+            return timeout;
+          } catch(err) {
+            console.error('[device] Error while trying to get connection type: ' + (err && err.message || err));
+            return defaultTimeout;
+          }
         }
       };
 
@@ -248,12 +312,27 @@ angular.module('cesium.device.services', ['cesium.utils.services', 'cesium.setti
         return !!navigator.userAgent.match(/iPhone | iPad | iPod/i) || (!!navigator.userAgent.match(/Mobile/i) && !!navigator.userAgent.match(/Macintosh/i)) || ionic.Platform.isIOS();
       };
 
+      exports.isWindows = function() {
+        return !!navigator.userAgent.match(/Windows/i) || ionic.Platform.is("windows");
+      };
+
+      exports.isUbuntu = function() {
+        return !!navigator.userAgent.match(/Ubuntu|Linux x86_64/i) || ionic.Platform.is("ubuntu");
+      };
+
       exports.isDesktop = function() {
         if (!angular.isDefined(cache.isDesktop)) {
           try {
-            // Should have NodeJs and NW
-            cache.isDesktop = !exports.enable && !!process && !!nw && !!nw.App;
+
+            cache.isDesktop = !exports.enable && (
+              exports.isUbuntu()
+              || exports.isWindows()
+              || exports.isOSX()
+              // Should have NodeJs and NW
+              || (!!process && !!nw && !!nw.App)
+            );
           } catch (err) {
+            // If error (e.g. 'process not defined')
             cache.isDesktop = false;
           }
         }
diff --git a/www/js/services/http-services.js b/www/js/services/http-services.js
index 083136a43d8b16be1e8d2720404b58aad31816b5..a210367ee23e59186e94dd58c3e33c31596cb01b 100644
--- a/www/js/services/http-services.js
+++ b/www/js/services/http-services.js
@@ -1,10 +1,8 @@
 angular.module('cesium.http.services', ['cesium.cache.services'])
 
-.factory('csHttp', function($http, $q, $timeout, $window, csSettings, csCache, Device) {
+.factory('csHttp', function($http, $q, $timeout, $window, $translate, csConfig, csSettings, csCache, Device) {
   'ngInject';
 
-  var timeout = csSettings.data.timeout;
-
   var
     sockets = [],
     defaultCachePrefix = 'csHttp-',
@@ -12,13 +10,14 @@ angular.module('cesium.http.services', ['cesium.cache.services'])
     regexp = {
       POSITIVE_INTEGER: /^\d+$/,
       VERSION_PART_REGEXP: /^[0-9]+|alpha[0-9]+|beta[0-9]+|rc[0-9]+|[0-9]+-SNAPSHOT$/
+    },
+    errorCodes = {
+      TIMEOUT: -1, // Timeout reached
+      FORBIDDEN: 403,
+      NOT_FOUND: 404,
     }
   ;
 
-  if (!timeout) {
-    timeout=4000; // default
-  }
-
   function getServer(host, port) {
     // Remove port if 80 or 443
     return  !host ? null : (host + (port && port != 80 && port != 443 ? ':' + port : ''));
@@ -34,23 +33,36 @@ angular.module('cesium.http.services', ['cesium.cache.services'])
     return  protocol + '://' + getServer(host, port) + (path ? path : '');
   }
 
-  function processError(reject, data, url, status) {
-    if (data && data.message) {
+  function processError(reject, data, url, status, config, startTime) {
+    // Detected timeout error
+    var reachTimeout = status === -1 && (config && config.timeout > 0 && startTime > 0) && (Date.now() - startTime) >= config.timeout;
+    if (reachTimeout) {
+      console.error('[http] Request timeout on [{0}] after waiting {1}ms'.format(url, config.timeout));
+      $translate('ERROR.TIMEOUT_REACHED_URL', {url: url || '?'})
+        .then(function(message) {
+          reject({ucode: errorCodes.TIMEOUT, message: message});
+        })
+        .catch(function() {
+          reject(data);
+        });
+    }
+
+    else if (data && data.message) {
       reject(data);
     }
     else {
-      if (status == 403) {
-        reject({ucode: 403, message: 'Resource is forbidden' + (url ? ' ('+url+')' : '')});
+      if (status == errorCodes.FORBIDDEN) {
+        reject({ucode: errorCodes.FORBIDDEN, message: 'Resource is forbidden' + (url ? ' ('+url+')' : '')});
       }
-      else if (status == 404) {
-        reject({ucode: 404, message: 'Resource not found' + (url ? ' ('+url+')' : '')});
+      else if (status == errorCodes.NOT_FOUND) {
+        reject({ucode: errorCodes.NOT_FOUND, message: 'Resource not found' + (url ? ' ('+url+')' : '')});
       }
       else if (url) {
-        console.error('[http] Get HTTP error {status: ' + status + '} on [' + url + ']');
-        reject('Error while requesting [' + url + ']');
+        console.error('[http] Get HTTP error {status: {0}} on [{1}]'.format(status, url));
+        reject('Error while requesting [{0}}'.format(url));
       }
       else {
-        reject('Unknown error from node');
+        reject('Unknown HTTP error');
       }
     }
   }
@@ -81,18 +93,19 @@ angular.module('cesium.http.services', ['cesium.cache.services'])
     return function(params, config) {
       return $q(function(resolve, reject) {
         var mergedConfig = {
-          timeout: forcedTimeout || timeout,
+          timeout: forcedTimeout || csConfig.timeout,
           responseType: 'json'
         };
         if (typeof config === 'string') angular.merge(mergedConfig, config);
 
         prepare(url, params, mergedConfig, function(url, config) {
-            $http.get(url, config)
-            .success(function(data, status, headers, config) {
+          var startTime = Date.now();
+          $http.get(url, config)
+            .success(function(data) {
               resolve(data);
             })
-            .error(function(data, status, headers, config) {
-              processError(reject, data, url, status);
+            .error(function(data, status) {
+              processError(reject, data, url, status, config, startTime);
             });
         });
       });
@@ -101,7 +114,7 @@ angular.module('cesium.http.services', ['cesium.cache.services'])
 
   function getResourceWithCache(host, port, path, useSsl, maxAge, autoRefresh, forcedTimeout, cachePrefix) {
     var url = getUrl(host, port, path, useSsl);
-    cachePrefix = cachePrefix || defaultCachePrefix;
+    cachePrefix = cachePrefix || defaultCachePrefix;
     maxAge = maxAge || csCache.constants.LONG;
     allCachePrefixes[cachePrefix] = true;
 
@@ -110,7 +123,7 @@ angular.module('cesium.http.services', ['cesium.cache.services'])
     return function(params) {
       return $q(function(resolve, reject) {
         var config = {
-          timeout: forcedTimeout || timeout,
+          timeout: forcedTimeout || csConfig.timeout,
           responseType: 'json'
         };
 
@@ -129,12 +142,13 @@ angular.module('cesium.http.services', ['cesium.cache.services'])
         }
 
         prepare(url, params, config, function(url, config) {
+          var startTime = Date.now();
           $http.get(url, config)
             .success(function(data) {
               resolve(data);
             })
             .error(function(data, status) {
-              processError(reject, data, url, status);
+              processError(reject, data, url, status, config, startTime);
             });
         });
       });
@@ -146,31 +160,32 @@ angular.module('cesium.http.services', ['cesium.cache.services'])
     return function(data, params, config) {
       return $q(function(resolve, reject) {
         var mergedConfig = {
-          timeout: forcedTimeout || timeout,
+          timeout: forcedTimeout || csConfig.timeout, // We use a large timeout, when post, and NOT the settings timeout
           headers : {'Content-Type' : 'application/json;charset=UTF-8'}
         };
         if (typeof config === 'object') angular.merge(mergedConfig, config);
 
         prepare(url, params, mergedConfig, function(url, config) {
-            $http.post(url, data, config)
+          var startTime = Date.now();
+          $http.post(url, data, config)
             .success(function(data) {
               resolve(data);
             })
             .error(function(data, status) {
-              processError(reject, data, url, status);
+              processError(reject, data, url, status, config, startTime);
             });
         });
       });
     };
   }
 
-  function ws(host, port, path, useSsl, timeout) {
+  function ws(host, port, path, useSsl, forcedTimeout) {
     if (!path) {
       console.error('calling csHttp.ws without path argument');
       throw 'calling csHttp.ws without path argument';
     }
     var uri = getWsUrl(host, port, path, useSsl);
-    timeout = timeout || csSettings.data.timeout;
+    var timeout = forcedTimeout || csConfig.timeout;
 
     function _waitOpen(self) {
       if (!self.delegate) {
diff --git a/www/js/services/network-services.js b/www/js/services/network-services.js
index 778fd80d12918af0169412ff1cfdc99ae6eaf550..5c387303290108a8e06c2689635e24c145a8188d 100644
--- a/www/js/services/network-services.js
+++ b/www/js/services/network-services.js
@@ -1,7 +1,7 @@
 
 angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services', 'cesium.http.services'])
 
-.factory('csNetwork', function($rootScope, $q, $interval, $timeout, $window, csConfig, BMA, csHttp, csCurrency, Api) {
+.factory('csNetwork', function($rootScope, $q, $interval, $timeout, $window, csConfig, csSettings, BMA, Device, csHttp, csCurrency, Api) {
   'ngInject';
 
    var
@@ -90,10 +90,25 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
       data.searchingPeersOnNetwork = false;
       data.difficulties = null;
       data.ws2pHeads = null;
-      data.timeout = csConfig.timeout;
+      data.timeout = getDefaultTimeout();
       data.startTime = null;
     },
 
+   /**
+    * Compute a timeout, depending on connection type (wifi, ethernet, cell, etc.)
+    */
+   getDefaultTimeout = function () {
+     // Using timeout from settings
+     if (csSettings.data.expertMode) {
+       var timeout = csSettings.data.timeout || csConfig.timeout;
+       console.debug('[network] Using user defined timeout: {0}ms'.format(timeout));
+       return timeout;
+     }
+
+     // Computing timeout from the connection type
+     return Device.network.timeout(csConfig.timeout);
+    },
+
     hasPeers = function() {
       return data.peers && data.peers.length > 0;
     },
@@ -790,6 +805,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
       options = options || {};
       bma = bma || BMA;
       var pid = data.pid;
+
       startPromise = bma.ready()
         .then(function() {
           close(pid);
@@ -798,7 +814,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
           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;
+          data.timeout = angular.isDefined(options.timeout) ? options.timeout : getDefaultTimeout();
           data.startTime = Date.now();
 
           // Init a min block number
@@ -808,12 +824,16 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
             return csCurrency.blockchain.current(true/*use cache*/)
               .then(function(current) {
                 data.minOnlineBlockNumber = Math.max(0, current.number - constants.MAX_BLOCK_OFFSET);
+                if (Date.now() - data.startDate > 2000) {
+                  console.warn('[network-service] Resetting network start date, because blockchain.current() take more than 2s to respond');
+                  data.startTime = Date.now(); // Reset the startTime (use to compute remainingTime)
+                }
               });
           }
         })
         .then(function() {
-          console.info('[network] Starting from [{0}]'.format(bma.server));
           var now = Date.now();
+          console.info('[network] Starting from [{0}]'.format(bma.server));
 
           addListeners();
 
@@ -832,7 +852,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
           removeListeners();
           resetData();
         }
-      if (interval && pid === data.pid) {
+      if (interval && pid === data.pid && pid > 0) {
         $interval.cancel(interval);
       }
       startPromise = null;
@@ -877,6 +897,7 @@ angular.module('cesium.network.services', ['ngApi', 'cesium.currency.services',
       options.filter.ssl = isHttpsMode ? true : undefined;
       options.filter.online = true;
       options.filter.expertMode = false;
+      options.timeout = angular.isDefined(options.timeout) ? options.timeout : getDefaultTimeout();
 
       var now = Date.now();
       console.info('[network] Getting synchronized BMA peers...');
diff --git a/www/js/services/settings-services.js b/www/js/services/settings-services.js
index d962f4d3af6fe9ac2e625f50e19a942298f5d3f4..cb38c4ed9c6371b7b3f21f105d56f66f7b7ede31 100644
--- a/www/js/services/settings-services.js
+++ b/www/js/services/settings-services.js
@@ -57,7 +57,6 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config'])
   },
   // Settings that user cannot change himself (only config can override this values)
   fixedSettings = {
-    timeout : 4000,
     cacheTimeMs: 60000, /*1 min*/
     timeWarningExpireMembership: 2592000 * 2 /*=2 mois*/,
     timeWarningExpire: 2592000 * 3 /*=3 mois*/,
@@ -71,6 +70,7 @@ angular.module('cesium.settings.services', ['ngApi', 'cesium.config'])
     httpsMode: false
   },
   defaultSettings = angular.merge({
+    timeout : undefined, // Default will be set by csConfig.timeout
     useRelative: false,
     useLocalStorage: !!$window.localStorage, // override to false if no device
     useLocalStorageEncryption: false,
diff --git a/www/js/services/tx-services.js b/www/js/services/tx-services.js
index 7c54a7cb1c3370e0d197fefe7e798d103d0feb26..54a92967bfacd5b9b541a4327eaa65e81b9f10ac 100644
--- a/www/js/services/tx-services.js
+++ b/www/js/services/tx-services.js
@@ -287,7 +287,6 @@ angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services',
 
   function loadData(pubkey, fromTime) {
     var now = Date.now();
-
     var data;
 
     // Alert user, when request is too long (> 2s)
@@ -304,92 +303,92 @@ angular.module('cesium.tx.services', ['ngApi', 'cesium.bma.services',
       loadTx(pubkey, fromTime)
     ])
 
-      .then(function(res) {
-        // Copy sources and balance
-        data = res[0];
-        data.tx = res[1];
-
-        var txPendings = [];
-        var txErrors = [];
-        var balanceFromSource = data.balance;
-        var balanceWithPending = data.balance;
-
-        function _processPendingTx(tx) {
-          var consumedSources = [];
-          var valid = true;
-          if (tx.amount > 0) { // do not check sources from received TX
-            valid = false;
-            // TODO get sources from the issuer ?
-          }
-          else {
-            _.find(tx.inputs, function(input) {
-              var inputKey = input.split(':').slice(2).join(':');
-              var srcIndex = data.sourcesIndexByKey[inputKey];
-              if (angular.isDefined(srcIndex)) {
-                consumedSources.push(data.sources[srcIndex]);
-              }
-              else {
-                valid = false;
-                return true; // break
-              }
-            });
-            if (tx.sources) { // add source output
-              addSources(data, tx.sources);
+    .then(function(res) {
+      // Copy sources and balance
+      data = res[0];
+      data.tx = res[1];
+
+      var txPendings = [];
+      var txErrors = [];
+      var balanceFromSource = data.balance;
+      var balanceWithPending = data.balance;
+
+      function _processPendingTx(tx) {
+        var consumedSources = [];
+        var valid = true;
+        if (tx.amount > 0) { // do not check sources from received TX
+          valid = false;
+          // TODO get sources from the issuer ?
+        }
+        else {
+          _.find(tx.inputs, function(input) {
+            var inputKey = input.split(':').slice(2).join(':');
+            var srcIndex = data.sourcesIndexByKey[inputKey];
+            if (angular.isDefined(srcIndex)) {
+              consumedSources.push(data.sources[srcIndex]);
             }
-            delete tx.sources;
-            delete tx.inputs;
-          }
-          if (valid) {
-            balanceWithPending += tx.amount; // update balance
-            txPendings.push(tx);
-            _.forEach(consumedSources, function(src) {
-              src.consumed=true;
-            });
-          }
-          else {
-            txErrors.push(tx);
+            else {
+              valid = false;
+              return true; // break
+            }
+          });
+          if (tx.sources) { // add source output
+            addSources(data, tx.sources);
           }
+          delete tx.sources;
+          delete tx.inputs;
+        }
+        if (valid) {
+          balanceWithPending += tx.amount; // update balance
+          txPendings.push(tx);
+          _.forEach(consumedSources, function(src) {
+            src.consumed=true;
+          });
+        }
+        else {
+          txErrors.push(tx);
         }
+      }
 
-        var txs = data.tx.pendings;
-        var retry = true;
-        while(txs && txs.length) {
-          // process TX pendings
-          _.forEach(txs, _processPendingTx);
-
-          // Retry once (TX could be chained and processed in a wrong order)
-          if (txErrors.length > 0 && txPendings.length > 0 && retry) {
-            txs = txErrors;
-            txErrors = [];
-            retry = false;
-          }
-          else {
-            txs = null;
-          }
+      var txs = data.tx.pendings;
+      var retry = true;
+      while(txs && txs.length) {
+        // process TX pendings
+        _.forEach(txs, _processPendingTx);
+
+        // Retry once (TX could be chained and processed in a wrong order)
+        if (txErrors.length > 0 && txPendings.length > 0 && retry) {
+          txs = txErrors;
+          txErrors = [];
+          retry = false;
+        }
+        else {
+          txs = null;
         }
+      }
 
-        data.tx = data.tx || {};
-        data.tx.pendings = txPendings.sort(function(tx1, tx2) {
-          return (tx2.time - tx1.time);
-        });
-        data.tx.errors = txErrors.sort(function(tx1, tx2) {
-          return (tx2.time - tx1.time);
-        });
-        // Negative balance not allow (use only source's balance) - fix #769
-        data.balance = (balanceWithPending < 0) ? balanceFromSource : balanceWithPending;
-
-        // Will add uid (+ plugin will add name, avatar, etc. if enable)
-        var allTx = (data.tx.history || []).concat(data.tx.validating||[], data.tx.pendings||[], data.tx.errors||[]);
-        return csWot.extendAll(allTx, 'pubkey')
-          .then(function() {
-            console.debug('[tx] TX and sources loaded in '+ (Date.now()-now) +'ms');
-            return data;
-          });
-      })
-      .catch(function(err) {
-        console.warn("[tx] Error while getting sources and tx...", err);
-        throw err;
+      data.tx = data.tx || {};
+      data.tx.pendings = txPendings.sort(function(tx1, tx2) {
+        return (tx2.time - tx1.time);
       });
+      data.tx.errors = txErrors.sort(function(tx1, tx2) {
+        return (tx2.time - tx1.time);
+      });
+      // Negative balance not allow (use only source's balance) - fix #769
+      data.balance = (balanceWithPending < 0) ? balanceFromSource : balanceWithPending;
+
+      // Will add uid (+ plugin will add name, avatar, etc. if enable)
+      var allTx = (data.tx.history || []).concat(data.tx.validating||[], data.tx.pendings||[], data.tx.errors||[]);
+      return csWot.extendAll(allTx, 'pubkey')
+        .then(function() {
+          console.debug('[tx] TX and sources loaded in {0}ms'.format(Date.now() - now));
+          return data;
+        });
+    })
+    .catch(function(err) {
+      console.warn('[tx] Error while getting sources and tx: ' + (err && err.message || err), err);
+      throw err;
+    });
   }
 
   function loadSources(pubkey) {
diff --git a/www/js/services/wallet-services.js b/www/js/services/wallet-services.js
index 0201061d8eb7dccf97c4ecc584313cba51cfd9cc..790e7813cc2c5da251aed5335cdfa29a03b8fc8a 100644
--- a/www/js/services/wallet-services.js
+++ b/www/js/services/wallet-services.js
@@ -726,6 +726,9 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
     },
 
     loadTxAndSources = function(fromTime) {
+      // DEBUG
+      //console.debug('[wallet-service] Calling loadTxAndSources()');
+
       if (fromTime === 'pending') {
         UIUtils.loading.update({template: "INFO.LOADING_PENDING_TX"});
       }
@@ -936,6 +939,10 @@ angular.module('cesium.wallet.services', ['ngApi', 'ngFileSaver', 'cesium.bma.se
           else { // user cancelled
             throw 'CANCELLED';
           }
+        })
+        .catch(function(err) {
+          loadPromise = null;
+          throw err;
         });
     },
 
diff --git a/www/templates/settings/settings.html b/www/templates/settings/settings.html
index eefef17e87f972180bd616ae1ea7d69278fb4119..ebf3e9014d76f87827eb4e8f9e3c377438df9b3f 100644
--- a/www/templates/settings/settings.html
+++ b/www/templates/settings/settings.html
@@ -203,7 +203,7 @@
         <span class="item item-divider" translate>SETTINGS.NETWORK_SETTINGS</span>
 
         <!-- Expert mode ?-->
-        <div class="item item-text-wrap item-toggle dark hidden-xs hidden-sm">
+        <div class="item item-text-wrap item-toggle dark">
           <div class="input-label" ng-bind-html="'SETTINGS.EXPERT_MODE' | translate"></div>
           <h4 class="gray" ng-bind-html="'SETTINGS.EXPERT_MODE_HELP' | translate"></h4>
           <label class="toggle toggle-royal">
@@ -268,6 +268,16 @@
 <!--          <i class="icon ion-ios-arrow-right" ng-if="formData.expertMode"></i>-->
 <!--        </ion-item>-->
 
+        <label class="item item-select item-text-wrap" ng-if="formData.expertMode">
+          <div class="input-label" style="max-width: 70%;">
+            <span translate>SETTINGS.PEER_TIMEOUT</span>
+          </div>
+          <h4 class="gray text-wrap hidden-xs" ng-bind-html="'SETTINGS.PEER_TIMEOUT_HELP' | translate"></h4>
+
+          <select ng-model="formData.timeout"
+                  ng-options="t as (t|formatDurationMs) for t in timeouts track by t">
+          </select>
+        </label>
 
         <!-- Block validity window -->
         <label class="item item-input item-select item-text-wrap">
diff --git a/yarn.lock b/yarn.lock
index 6875d8ebfcf06935fe33de9c7462d73feef62786..924a8564fa8c6bf82e72bbf1c7ffab395615940a 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2584,10 +2584,10 @@ cordova-fetch@^3.0.0:
     semver "^7.1.3"
     which "^2.0.2"
 
-cordova-ios@^6.2.0:
-  version "6.2.0"
-  resolved "https://registry.npmjs.org/cordova-ios/-/cordova-ios-6.2.0.tgz#3aaec7376b9a202cdcaf28faab6a79823b04c519"
-  integrity sha512-sLjZg2QBI1SpQVwfe0MSn89YNVkBGLW9Q1vcFJBsqKBrhvoEOJ5Ytq0gwqdhgTOGzlwJUfxC6OHM3jcsRjtYrw==
+cordova-ios@^6.3.0:
+  version "6.3.0"
+  resolved "https://registry.npmjs.org/cordova-ios/-/cordova-ios-6.3.0.tgz#8655289f5806526dfbd5eb42bb372bebbed50a19"
+  integrity sha512-BZybgFzc7D0HmhkTYurFrRXiWgvYohmT7bwQsLPhf+VdiDjwGXbiSWgg3uP9MChvacCNcGXXUl2NhOaNC9UVaA==
   dependencies:
     cordova-common "^4.0.2"
     fs-extra "^9.1.0"
@@ -2635,10 +2635,10 @@ cordova-plugin-androidx@^3.0.0:
   resolved "https://registry.npmjs.org/cordova-plugin-androidx/-/cordova-plugin-androidx-3.0.0.tgz#03785d95d1c0e3ab42a45dd36fd4132e44e162f7"
   integrity sha512-niMnhcxKsu4/oKTUbL0jRAnh6/cdoIVxRxJqj3uEyv8CVOlAj1sWhX+9b1XiAo9+bejAM9BbA21YK0mChfbVTA==
 
-cordova-plugin-camera@^5.0.1:
-  version "5.0.1"
-  resolved "https://registry.npmjs.org/cordova-plugin-camera/-/cordova-plugin-camera-5.0.1.tgz#9e0e94a7d29b15e6708f186bf9e59a28fcc0ee53"
-  integrity sha512-9gXyZvI8u9KzsZuqmB8Yw+uheF+7f+usMAwvOMw7L7pqbykg+bm9US5zjhJbwit3A1cSblgZkpBafe5cFiMcTA==
+cordova-plugin-camera@^5.0.3:
+  version "5.0.3"
+  resolved "https://registry.npmjs.org/cordova-plugin-camera/-/cordova-plugin-camera-5.0.3.tgz#eb045f15ffa7722ffbec35a0cb32c1a0c5f91066"
+  integrity sha512-CfoqP8+0XGm8wS0Ri6BCaTTq195Z7ny/tXrD14DsYyR7hHjf1nn+B643tzdYuPNVynxMLRQa1T8n1EkCxFyUog==
 
 cordova-plugin-compat@^1.2.0:
   version "1.2.0"
@@ -2650,10 +2650,10 @@ cordova-plugin-customurlscheme@^5.0.2:
   resolved "https://nexus.e-is.pro/nexus/content/repositories/npmjs/cordova-plugin-customurlscheme/-/cordova-plugin-customurlscheme-5.0.2.tgz#c6246e469a2c23772ebfb545138354cb0dc1a683"
   integrity sha512-g139Av7iYD3xcSsCd5S6a7B7dp4GTqGYtvdhh44g4OS38+aX6XkC1lsCRmROuhLIs4fkwJqkrvxacH9H4U9Gsg==
 
-cordova-plugin-device@^2.0.3:
-  version "2.0.3"
-  resolved "https://nexus.e-is.pro/nexus/content/repositories/npmjs/cordova-plugin-device/-/cordova-plugin-device-2.0.3.tgz#c2b41b7efd0455dd097f89356d85bfdd5dadeb0f"
-  integrity sha512-Jb3V72btxf3XHpkPQsGdyc8N6tVBYn1vsxSFj43fIz9vonJDUThYPCJJHqk6PX6N4dJw6I4FjxkpfCR4LDYMlw==
+cordova-plugin-device@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.npmjs.org/cordova-plugin-device/-/cordova-plugin-device-2.1.0.tgz#ef2292c2f0a34d01b5aca6c39cf204e48a460fb2"
+  integrity sha512-FU0Lw1jZpuKOgG4v80LrfMAOIMCGfAVPumn7AwaX9S1iU/X3OPZUyoKUgP09q4bxL35IeNPkqNWVKYduAXZ1sg==
 
 cordova-plugin-dialogs@^2.0.2:
   version "2.0.2"
@@ -2679,19 +2679,24 @@ cordova-plugin-ionic-webview@^5.0.0:
   version "1.0.1"
   resolved "git+https://github.com/duniter-cesium/cordova-plugin-minisodium.git#0179f96d3d887975d7cbc57b6aef3eee6354dbb2"
 
+cordova-plugin-network-information@~3.0.0:
+  version "3.0.0"
+  resolved "https://registry.npmjs.org/cordova-plugin-network-information/-/cordova-plugin-network-information-3.0.0.tgz#8b5546b56b44e6d2c69be7cc920d5e134cbb2b7f"
+  integrity sha512-bBtP3PxIX8vshsfR0+F6co2e2cFLgjt18yKIdigzMwk6ANudWQ72RB3g2qMPyT6fBDWmUyE1Qd+bKQB/fZtQwQ==
+
 "cordova-plugin-secure-storage-android10@git+https://github.com/duniter-cesium/cordova-plugin-secure-storage-android10.git#6.0.4":
   version "6.0.4"
   resolved "git+https://github.com/duniter-cesium/cordova-plugin-secure-storage-android10.git#7aa41aaea5d7fd2c78d3ca9ad9c799cf9de4c9a0"
 
-cordova-plugin-splashscreen@^6.0.0:
-  version "6.0.0"
-  resolved "https://registry.npmjs.org/cordova-plugin-splashscreen/-/cordova-plugin-splashscreen-6.0.0.tgz#674718f87894cd75615da2b55905eb4cc719cf01"
-  integrity sha512-pm4ZtJKQY4bCGXVeIInbGrXilryTevYSKgfvoQJpW9UClOWKAxSsYf2/4G2u1vcn492svOSL42OSa2MhujBWEQ==
+cordova-plugin-splashscreen@^6.0.2:
+  version "6.0.2"
+  resolved "https://registry.npmjs.org/cordova-plugin-splashscreen/-/cordova-plugin-splashscreen-6.0.2.tgz#2151ee7256fbd64f07a84951854687855c56856f"
+  integrity sha512-7JiUfnInir+SCOEgTJ+5/cHF3UFl69jp6cAQfHtJaaQt9Pli8D8yTJjU0HGlJCvryvsVs4Xlc7/sEJM7vLJgvg==
 
-cordova-plugin-statusbar@^2.4.3:
-  version "2.4.3"
-  resolved "https://nexus.e-is.pro/nexus/content/repositories/npmjs/cordova-plugin-statusbar/-/cordova-plugin-statusbar-2.4.3.tgz#cc557aef66c27484e0f7f045004040d03321f82d"
-  integrity sha512-ThmXzl6QIKWFXf4wWw7Q/zpB+VKkz3VM958+5A0sXD4jmR++u7KnGttLksXshVwWr6lvGwUebLYtIyXwS4Ovcg==
+cordova-plugin-statusbar@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.npmjs.org/cordova-plugin-statusbar/-/cordova-plugin-statusbar-3.0.0.tgz#371b54cef2594550118ef51c1d677c8adb3bc668"
+  integrity sha512-nzkeWeyLA6+1FryzO0aeB6NS8MZ45gnBYeq2VZqfdNbddZEgtpI4XPYdBVxvm9NhcVoJ3tdA1OBnQD9JryoV0Q==
 
 cordova-plugin-vibration@^3.1.1:
   version "3.1.1"
@@ -2703,15 +2708,15 @@ cordova-plugin-websocket@^0.12.2:
   resolved "https://nexus.e-is.pro/nexus/content/repositories/npmjs/cordova-plugin-websocket/-/cordova-plugin-websocket-0.12.2.tgz#8de2593b20cc64150847e25261686df41d61fa39"
   integrity sha1-jeJZOyDMZBUIR+JSYWht9B1h+jk=
 
-cordova-plugin-whitelist@^1.3.4:
-  version "1.3.4"
-  resolved "https://nexus.e-is.pro/nexus/content/repositories/npmjs/cordova-plugin-whitelist/-/cordova-plugin-whitelist-1.3.4.tgz#31938545c7c3e7de35c20ab08c2c3afa06e8a3f9"
-  integrity sha512-EYC5eQFVkoYXq39l7tYKE6lEjHJ04mvTmKXxGL7quHLdFPfJMNzru/UYpn92AOfpl3PQaZmou78C7EgmFOwFQQ==
+cordova-plugin-whitelist@^1.3.5:
+  version "1.3.5"
+  resolved "https://registry.npmjs.org/cordova-plugin-whitelist/-/cordova-plugin-whitelist-1.3.5.tgz#13e1a6036f1c972135ad48cedeedd897baadf797"
+  integrity sha512-+v/VzCYBdGsIxJTP2m+RWaq7l/NLu7b976w6XGJUFiN2TVOeaGrytaR4jRy0w9akRai8uKFeBmuGHmlS/sOeCA==
 
-cordova-plugin-x-toast@^2.7.2:
-  version "2.7.2"
-  resolved "https://nexus.e-is.pro/nexus/content/repositories/npmjs/cordova-plugin-x-toast/-/cordova-plugin-x-toast-2.7.2.tgz#2f8ca705bc2664832298b348cb17204cda79c17f"
-  integrity sha512-nx4LaBkJyEk1MknkLC0/U904A42WX/1/OZUeUyXkKRtChShsoTdbWlvEqHKzPbELzf2bEMnXd1CI70WRv+a4hA==
+cordova-plugin-x-toast@^2.7.3:
+  version "2.7.3"
+  resolved "https://registry.npmjs.org/cordova-plugin-x-toast/-/cordova-plugin-x-toast-2.7.3.tgz#bc642998370e15c8f02204ccf67bbe90cb27a4fc"
+  integrity sha512-+1aV7wJZpJdeZKTbBiagGawAJq7LOA4TFD6Et1uyTz+OUr3F4url8mXS+qLsag6kBCRftWg9WTA2bZTZv4tBjw==
 
 cordova-serve@^4.0.0:
   version "4.0.0"