diff --git a/README.md b/README.md
index f4a4e7a5b846bd8f11c75cb480eead653548bea7..f0a1beeb94f2a29950e64f37eb4fca99f249c648 100644
--- a/README.md
+++ b/README.md
@@ -37,6 +37,11 @@ First of all, you can contribute translating Äž1nkgo to your language:
 
 [https://weblate.duniter.org/settings/g1nkgo/g1nkgo/](https://weblate.duniter.org/settings/g1nkgo/g1nkgo/)
 
+## Docker
+
+mkdir -p ~/.ginkgo/nginx-conf
+mkdir -p ~/.ginkgo/www 
+
 ## Dev contributions
 
 ### Prerequisites
diff --git a/assets/translations/ca.json b/assets/translations/ca.json
index b20bf1dd0e2ef5510ca9b0f8315be89a04e62607..220d277bbe11a31237aaf14b4b85d6c9c799f379 100644
--- a/assets/translations/ca.json
+++ b/assets/translations/ca.json
@@ -61,7 +61,6 @@
   "nothing_found": "No s'ha trobat res",
   "using_nodes": "Usant {nodes} nodes de {type}",
   "using_nodes_first": "El més ràpid: {node}",
-  "long_press_to_refresh": "Prem sostingudament per actualitzar",
   "technical_info_title": "Informació tècnica",
   "pattern_do_not_match": "Els patrons no coincideixen",
   "at_least_3": "Com a mínim calen 3 punts",
diff --git a/assets/translations/en.json b/assets/translations/en.json
index 1ff918b203e92b3ba6e677157e5f3dd3bde93705..e42ccebe083003cf586009e9758e5f1de8542b35 100644
--- a/assets/translations/en.json
+++ b/assets/translations/en.json
@@ -62,7 +62,7 @@
   "nothing_found": "Nothing found",
   "using_nodes": "Using {nodes} nodes of {type}",
   "using_nodes_first": "Faster: {node}",
-  "long_press_to_refresh": "Long press to refresh",
+  "long_press_to_refresh": "Reordering the current list of nodes. Tap an hold to refresh the list.",
   "technical_info_title": "Technical info",
   "pattern_do_not_match": "Patterns do not match",
   "at_least_3": "At least 3 points required",
@@ -95,5 +95,6 @@
   "transaction_receiving": "Receiving",
   "transaction_sent": "Sent",
   "transaction_received": "Received",
-  "valid_comment": "The comment cannot have accents or commas"
+  "valid_comment": "The comment cannot have accents or commas",
+  "search_limitation": "The search term must have at least 3 characters"
 }
diff --git a/assets/translations/es.json b/assets/translations/es.json
index 2c00c5a4f0616030b86b3e646d8f0352503c726f..23ba2fd4c044d184acddf19a2a41f9b2815c6a12 100644
--- a/assets/translations/es.json
+++ b/assets/translations/es.json
@@ -62,7 +62,7 @@
   "nothing_found": "No se ha encontrado nada",
   "using_nodes": "Usando {nodes} nodos de {type}",
   "using_nodes_first": "Más rápido: {node}",
-  "long_press_to_refresh": "Mantén pulsado para actualizar",
+  "long_press_to_refresh": "Reordenando la lista actual de nodos. Manten pulsado para refrescar la lista",
   "technical_info_title": "Información técnica",
   "pattern_do_not_match": "Los patrones no coinciden",
   "at_least_3": "Se requieren al menos 3 puntos",
@@ -94,5 +94,6 @@
   "transaction_receiving": "Recibiendo",
   "transaction_sent": "Enviado",
   "transaction_received": "Recibido",
-  "valid_comment": "El comentario no puede tener tildes o comas"
+  "valid_comment": "El comentario no puede tener tildes o comas",
+  "search_limitation": "La búsqueda debe tener al menos 3 caracteres"
 }
diff --git a/assets/translations/fr.json b/assets/translations/fr.json
index f5ad60d0d422513dab887b6cf2d8a65095295adf..9eed1a354dac3c21a143764ddbf446569d946804 100644
--- a/assets/translations/fr.json
+++ b/assets/translations/fr.json
@@ -56,7 +56,6 @@
   "nothing_found": "Rien trouvé",
   "using_nodes": "Utilisation de {nodes} nœuds de type {type}",
   "using_nodes_first": "Plus rapide : {node}",
-  "long_press_to_refresh": "Appuyez longuement pour rafraîchir",
   "technical_info_title": "Informations techniques",
   "pattern_do_not_match": "Les modèles ne correspondent pas",
   "at_least_3": "Au moins 3 points sont requis",
diff --git a/lib/data/models/contact.dart b/lib/data/models/contact.dart
index a7b09ea540761a0f41b7b5e345034f51bbd2843d..6b51fca9e564f4bb7910ecf5ff94b18f95ee6ba0 100644
--- a/lib/data/models/contact.dart
+++ b/lib/data/models/contact.dart
@@ -4,6 +4,7 @@ import 'package:copy_with_extension/copy_with_extension.dart';
 import 'package:equatable/equatable.dart';
 import 'package:json_annotation/json_annotation.dart';
 
+import '../../ui/ui_helpers.dart';
 import 'is_json_serializable.dart';
 import 'model_utils.dart';
 
@@ -23,6 +24,16 @@ class Contact extends Equatable implements IsJsonSerializable<Contact> {
   factory Contact.fromJson(Map<String, dynamic> json) =>
       _$ContactFromJson(json);
 
+  Contact merge(Contact c) {
+    return Contact(
+      nick: c.nick ?? nick,
+      pubKey: c.pubKey,
+      avatar: c.avatar ?? avatar,
+      notes: c.notes ?? notes,
+      name: c.name ?? name,
+    );
+  }
+
   final String? nick;
   final String pubKey;
   @JsonKey(fromJson: uIntFromList, toJson: uIntToList)
@@ -33,6 +44,8 @@ class Contact extends Equatable implements IsJsonSerializable<Contact> {
   @override
   List<Object?> get props => <dynamic>[nick, pubKey, avatar, notes, name];
 
+  bool get hasAvatar => avatar != null;
+
   @override
   Map<String, dynamic> toJson() => _$ContactToJson(this);
 
@@ -43,4 +56,11 @@ class Contact extends Equatable implements IsJsonSerializable<Contact> {
   String toString() {
     return 'Contact $pubKey, hasAvatar: ${avatar != null}, nick: $nick, name: $name';
   }
+
+  String get title => name != null && nick != null
+      ? '$name ($nick)'
+      : nick ?? name ?? humanizePubKey(pubKey);
+
+  String? get subtitle =>
+      (nick != null || name != null) ? humanizePubKey(pubKey) : null;
 }
diff --git a/lib/data/models/node_list_cubit.dart b/lib/data/models/node_list_cubit.dart
index c6e213ac48ca440c2018c6e88948986a869ab133..fddb303b4daf749579f43c17b607c7086e07a52b 100644
--- a/lib/data/models/node_list_cubit.dart
+++ b/lib/data/models/node_list_cubit.dart
@@ -2,10 +2,40 @@ import 'package:hydrated_bloc/hydrated_bloc.dart';
 
 import 'node.dart';
 import 'node_list_state.dart';
+import 'node_type.dart';
 
 class NodeListCubit extends HydratedCubit<NodeListState> {
   NodeListCubit() : super(NodeListState());
 
+  void shuffle(NodeType type) {
+    switch (type) {
+      case NodeType.duniter:
+        emit(state.copyWith(duniterNodes: shuffleFirstN(state.duniterNodes)));
+        break;
+      case NodeType.cesiumPlus:
+        emit(state.copyWith(
+            cesiumPlusNodes: shuffleFirstN(state.cesiumPlusNodes)));
+        break;
+      case NodeType.gva:
+        emit(state.copyWith(gvaNodes: shuffleFirstN(state.gvaNodes)));
+        break;
+    }
+  }
+
+  // shuffle fist n nodes
+  List<Node> shuffleFirstN(List<Node> list, [int n = 5]) {
+    if (list.length <= n) {
+      list.shuffle();
+    } else {
+      final List<Node> subList = list.sublist(0, n);
+      subList.shuffle();
+      for (int i = 0; i < n; i++) {
+        list[i] = subList[i];
+      }
+    }
+    return list;
+  }
+
   void setDuniterNodes(List<Node> nodes) {
     emit(state.copyWith(duniterNodes: nodes));
   }
diff --git a/lib/data/models/payment_cubit.dart b/lib/data/models/payment_cubit.dart
index 6538413d2b940eee8b332ae47b4c6aa894fbcc90..96afa86261141c12624ef49a530c1132eef58a23 100644
--- a/lib/data/models/payment_cubit.dart
+++ b/lib/data/models/payment_cubit.dart
@@ -1,7 +1,6 @@
-import 'dart:typed_data';
-
 import 'package:hydrated_bloc/hydrated_bloc.dart';
 
+import 'contact.dart';
 import 'payment_state.dart';
 
 class PaymentCubit extends HydratedCubit<PaymentState> {
@@ -20,16 +19,15 @@ class PaymentCubit extends HydratedCubit<PaymentState> {
     emit(newState);
   }
 
-  void selectUser(String publicKey, String? nick, Uint8List? avatar,
-      [double? amount]) {
-    final PaymentState newState = PaymentState(
-        publicKey: publicKey, nick: nick, avatar: avatar, amount: amount);
+  void selectUser(Contact contact, [double? amount]) {
+    final PaymentState newState =
+        PaymentState(contact: contact, amount: amount);
     emit(newState);
   }
 
-  void selectKeyAmount(String publicKey, double amount) {
+  void selectKeyAmount(Contact contact, double amount) {
     final PaymentState newState =
-        PaymentState(publicKey: publicKey, amount: amount);
+        PaymentState(contact: contact, amount: amount);
     emit(newState);
   }
 
@@ -45,9 +43,9 @@ class PaymentCubit extends HydratedCubit<PaymentState> {
     emit(state.copyWith(status: PaymentStatus.sending));
   }
 
-  void selectKey(String publicKey) {
+  void selectKey(Contact? contact) {
     final PaymentState newState = PaymentState(
-        publicKey: publicKey, amount: state.amount, comment: state.comment);
+        contact: contact, amount: state.amount, comment: state.comment);
     emit(newState);
   }
 
@@ -65,22 +63,14 @@ class PaymentCubit extends HydratedCubit<PaymentState> {
   void selectAmount(double? amount) {
     // As copyWith ignores null amounts
     final PaymentState newState = PaymentState(
-        publicKey: state.publicKey,
-        nick: state.nick,
-        comment: state.comment,
-        avatar: state.avatar,
-        amount: amount);
+        contact: state.contact, comment: state.comment, amount: amount);
     emit(newState);
   }
 
   void setComment(String comment) {
     // As copyWith ignores null amounts
     final PaymentState newState = PaymentState(
-        publicKey: state.publicKey,
-        amount: state.amount,
-        comment: comment,
-        avatar: state.avatar,
-        nick: state.nick);
+        contact: state.contact, amount: state.amount, comment: comment);
     emit(newState);
   }
 }
diff --git a/lib/data/models/payment_state.dart b/lib/data/models/payment_state.dart
index 581d54dda75838772d944cfc78c6e9dbd67a1b99..6bcb52d182790ec4d98579f4076d1877b3912c3a 100644
--- a/lib/data/models/payment_state.dart
+++ b/lib/data/models/payment_state.dart
@@ -1,10 +1,8 @@
-import 'dart:typed_data';
-
 import 'package:equatable/equatable.dart';
 import 'package:json_annotation/json_annotation.dart';
 
 import '../../g1/g1_helper.dart';
-import 'model_utils.dart';
+import 'contact.dart';
 
 part 'payment_state.g.dart';
 
@@ -13,9 +11,7 @@ enum PaymentStatus { notSent, sending, isSent }
 @JsonSerializable()
 class PaymentState extends Equatable {
   const PaymentState({
-    required this.publicKey,
-    this.nick,
-    this.avatar,
+    this.contact,
     this.comment = '',
     this.amount,
     this.status = PaymentStatus.notSent,
@@ -26,14 +22,11 @@ class PaymentState extends Equatable {
 
   bool canBeSent() =>
       status == PaymentStatus.notSent &&
-      validateKey(publicKey) &&
+      (contact != null && validateKey(contact!.pubKey)) &&
       amount != null &&
       amount! > 0;
 
-  final String publicKey;
-  final String? nick;
-  @JsonKey(fromJson: uIntFromList, toJson: uIntToList)
-  final Uint8List? avatar;
+  final Contact? contact;
   final String comment;
   final double? amount;
   final PaymentStatus status;
@@ -41,34 +34,26 @@ class PaymentState extends Equatable {
   Map<String, dynamic> toJson() => _$PaymentStateToJson(this);
 
   PaymentState copyWith({
-    String? publicKey,
-    String? nick,
-    Uint8List? avatar,
+    Contact? contact,
     String? comment,
     double? amount,
     PaymentStatus? status,
   }) {
     return PaymentState(
-      publicKey: publicKey ?? this.publicKey,
-      nick: nick ?? this.nick,
-      avatar: avatar ?? this.avatar,
+      contact: contact ?? this.contact,
       comment: comment ?? this.comment,
       amount: amount ?? this.amount,
       status: status ?? this.status,
     );
   }
 
-  static PaymentState emptyPayment = const PaymentState(
-    publicKey: '',
-    nick: '',
-  );
+  static PaymentState emptyPayment = const PaymentState();
 
   @override
   String toString() {
-    return '$publicKey ${amount ?? ""}';
+    return '$contact.pubKey ${amount ?? ""}';
   }
 
   @override
-  List<Object?> get props =>
-      <dynamic>[publicKey, nick, avatar, comment, amount, status];
+  List<Object?> get props => <dynamic>[contact, comment, amount, status];
 }
diff --git a/lib/data/models/payment_state.g.dart b/lib/data/models/payment_state.g.dart
index 5bb586a666591a9cdace210b44e9d06ea78c68ea..d935dcf0a9441ad8675ef21164c2c8f865aa39b1 100644
--- a/lib/data/models/payment_state.g.dart
+++ b/lib/data/models/payment_state.g.dart
@@ -7,9 +7,9 @@ part of 'payment_state.dart';
 // **************************************************************************
 
 PaymentState _$PaymentStateFromJson(Map<String, dynamic> json) => PaymentState(
-      publicKey: json['publicKey'] as String,
-      nick: json['nick'] as String?,
-      avatar: uIntFromList(json['avatar']),
+      contact: json['contact'] == null
+          ? null
+          : Contact.fromJson(json['contact'] as Map<String, dynamic>),
       comment: json['comment'] as String? ?? '',
       amount: (json['amount'] as num?)?.toDouble(),
       status: $enumDecodeNullable(_$PaymentStatusEnumMap, json['status']) ??
@@ -18,9 +18,7 @@ PaymentState _$PaymentStateFromJson(Map<String, dynamic> json) => PaymentState(
 
 Map<String, dynamic> _$PaymentStateToJson(PaymentState instance) =>
     <String, dynamic>{
-      'publicKey': instance.publicKey,
-      'nick': instance.nick,
-      'avatar': uIntToList(instance.avatar),
+      'contact': instance.contact,
       'comment': instance.comment,
       'amount': instance.amount,
       'status': _$PaymentStatusEnumMap[instance.status]!,
diff --git a/lib/g1/api.dart b/lib/g1/api.dart
index 640d7276de54c8580055f9f362b9049454283796..f6c2095b2d561976a4a731fb6e83185735879f84 100644
--- a/lib/g1/api.dart
+++ b/lib/g1/api.dart
@@ -1,5 +1,4 @@
 import 'dart:convert';
-
 // import 'dart:developer' as developer;
 import 'dart:io';
 
@@ -23,7 +22,7 @@ import 'g1_helper.dart';
 
 Future<String> getTxHistory(String publicKey) async {
   final Response response =
-  await requestWithRetry(NodeType.duniter, '/tx/history/$publicKey');
+      await requestWithRetry(NodeType.duniter, '/tx/history/$publicKey');
   if (response.statusCode == 200) {
     return response.body;
   } else {
@@ -44,29 +43,36 @@ Future<Response> getPeers() async {
 
 Future<Response> searchCPlusUser(String searchTerm) async {
   final Response response = await requestCPlusWithRetry(
-      '/user/profile/_search?q=title:*$searchTerm* OR _id:$searchTerm* OR _id:$searchTerm',
+      '/user/profile/_search?q=title:$searchTerm OR issuer:$searchTerm',
       retryWith404: false);
   return response;
 }
 
-Future<Contact> getProfile(String pubKey) async {
+Future<Contact> getProfile(String pubKey,
+    [bool onlyCPlusProfile = false]) async {
   try {
     final Response cPlusResponse = await requestCPlusWithRetry(
         '/user/profile/$pubKey',
         retryWith404: false);
     final Map<String, dynamic> result =
-    const JsonDecoder().convert(cPlusResponse.body) as Map<String, dynamic>;
+        const JsonDecoder().convert(cPlusResponse.body) as Map<String, dynamic>;
     if (result['found'] == false) {
       return Contact(pubKey: pubKey);
     }
-
-    final String? nick = await gvaNick(pubKey);
     final Map<String, dynamic> profile =
-    const JsonDecoder().convert(cPlusResponse.body) as Map<String, dynamic>;
-
+        const JsonDecoder().convert(cPlusResponse.body) as Map<String, dynamic>;
     final Contact c = contactFromResultSearch(profile);
-    logger('Contact retrieved in search $c');
-    return c.copyWith(nick: nick);
+    if (!onlyCPlusProfile) {
+      // This penalize the gva rate limit
+      // final String? nick = await gvaNick(pubKey);
+      final List<Contact> wotList = await searchWot(pubKey);
+      if (wotList.isNotEmpty) {
+        final Contact c = wotList[0];
+        c.copyWith(nick: c.nick);
+      }
+    }
+    logger('Contact retrieved in getProfile $c (c+ only $onlyCPlusProfile)');
+    return c;
   } catch (e) {
     logger('Error in getProfile $e');
     return Contact(pubKey: pubKey);
@@ -84,7 +90,6 @@ Not found sample:
 }
  */
 Future<List<Contact>> searchWot(String searchTerm) async {
-  // USE gva.getUsername
   final Response response = await requestDuniterWithRetry(
       '/wot/lookup/$searchTerm',
       retryWith404: false);
@@ -92,7 +97,7 @@ Future<List<Contact>> searchWot(String searchTerm) async {
   final List<Contact> contacts = <Contact>[];
   if (response.statusCode == HttpStatus.ok) {
     final Map<String, dynamic> data =
-    json.decode(response.body) as Map<String, dynamic>;
+        json.decode(response.body) as Map<String, dynamic>;
     final List<dynamic> results = data['results'] as List<dynamic>;
     // logger('Returning wot results ${results.length}');
     if (results.isNotEmpty) {
@@ -106,6 +111,9 @@ Future<List<Contact>> searchWot(String searchTerm) async {
     }
   }
   logger('Returning wot contact ${contacts.length}');
+  if (contacts.isNotEmpty) {
+    logger('First: ${contacts.first}');
+  }
   return contacts;
 }
 
@@ -116,11 +124,11 @@ Future<Contact> getWot(Contact contact) async {
   // Will be better to analyze the 404 response (to detect faulty node)
   if (response.statusCode == HttpStatus.ok) {
     final Map<String, dynamic> data =
-    json.decode(response.body) as Map<String, dynamic>;
+        json.decode(response.body) as Map<String, dynamic>;
     final List<dynamic> results = data['results'] as List<dynamic>;
     if (results.isNotEmpty) {
       final List<dynamic> uids =
-      (results[0] as Map<String, dynamic>)['uids'] as List<dynamic>;
+          (results[0] as Map<String, dynamic>)['uids'] as List<dynamic>;
       if (uids.isNotEmpty) {
         // ignore: avoid_dynamic_calls
         return contact.copyWith(nick: uids[0]!['uid'] as String);
@@ -133,14 +141,14 @@ Future<Contact> getWot(Contact contact) async {
 @Deprecated('use getProfile')
 Future<String> _getDataImageFromKey(String publicKey) async {
   final Response response =
-  await requestCPlusWithRetry('/user/profile/$publicKey');
+      await requestCPlusWithRetry('/user/profile/$publicKey');
   if (response.statusCode == HttpStatus.ok) {
     final Map<String, dynamic> data =
-    json.decode(response.body) as Map<String, dynamic>;
+        json.decode(response.body) as Map<String, dynamic>;
     final Map<String, dynamic> source = data['_source'] as Map<String, dynamic>;
     if (source.containsKey('avatar')) {
       final Map<String, dynamic> avatarData =
-      source['avatar'] as Map<String, dynamic>;
+          source['avatar'] as Map<String, dynamic>;
       if (avatarData.containsKey('_content')) {
         final String content = avatarData['_content'] as String;
         return 'data:image/png;base64,$content';
@@ -193,18 +201,16 @@ Future<void> fetchCesiumPlusNodes({bool force = false}) async {
   NodeManager().updateNodes(type, nodes);
 }
 
-int nodesWorking(NodeType type) =>
-    NodeManager()
-        .nodeList(type)
-        .where((Node n) => n.errors < NodeManager.maxNodeErrors)
-        .toList()
-        .length;
+int nodesWorking(NodeType type) => NodeManager()
+    .nodeList(type)
+    .where((Node n) => n.errors < NodeManager.maxNodeErrors)
+    .toList()
+    .length;
 
-List<Node> nodesWorkingList(NodeType type) =>
-    NodeManager()
-        .nodeList(type)
-        .where((Node n) => n.errors < NodeManager.maxNodeErrors)
-        .toList();
+List<Node> nodesWorkingList(NodeType type) => NodeManager()
+    .nodeList(type)
+    .where((Node n) => n.errors < NodeManager.maxNodeErrors)
+    .toList();
 
 Future<List<Node>> _fetchDuniterNodesFromPeers(NodeType type) async {
   final List<Node> lNodes = <Node>[];
@@ -216,14 +222,14 @@ Future<List<Node>> _fetchDuniterNodesFromPeers(NodeType type) async {
     final Response response = await getPeers();
     if (response.statusCode == 200) {
       final Map<String, dynamic> peerList =
-      jsonDecode(response.body) as Map<String, dynamic>;
+          jsonDecode(response.body) as Map<String, dynamic>;
       final List<dynamic> peers = (peerList['peers'] as List<dynamic>)
           .where((dynamic peer) =>
-      (peer as Map<String, dynamic>)['currency'] == 'g1')
+              (peer as Map<String, dynamic>)['currency'] == 'g1')
           .where(
               (dynamic peer) => (peer as Map<String, dynamic>)['version'] == 10)
           .where((dynamic peer) =>
-      (peer as Map<String, dynamic>)['status'] == 'UP')
+              (peer as Map<String, dynamic>)['status'] == 'UP')
           .toList();
       // reorder peer list
       peers.shuffle();
@@ -231,7 +237,7 @@ Future<List<Node>> _fetchDuniterNodesFromPeers(NodeType type) async {
         final Map<String, dynamic> peer = peerR as Map<String, dynamic>;
         if (peer['endpoints'] != null) {
           final List<String> endpoints =
-          List<String>.from(peer['endpoints'] as List<dynamic>);
+              List<String>.from(peer['endpoints'] as List<dynamic>);
           for (int j = 0; j < endpoints.length; j++) {
             if (endpoints[j].startsWith(apyType)) {
               final String endpointUnParsed = endpoints[j];
@@ -242,10 +248,9 @@ Future<List<Node>> _fetchDuniterNodesFromPeers(NodeType type) async {
                 try {
                   final Duration latency = await _pingNode(endpoint, type);
                   logger(
-                      'Evaluating node: $endpoint, latency ${latency
-                          .inMicroseconds}');
+                      'Evaluating node: $endpoint, latency ${latency.inMicroseconds}');
                   final Node node =
-                  Node(url: endpoint, latency: latency.inMicroseconds);
+                      Node(url: endpoint, latency: latency.inMicroseconds);
                   if (fastestNode == null || latency < fastestLatency) {
                     fastestNode = endpoint;
                     fastestLatency = latency;
@@ -273,8 +278,7 @@ Future<List<Node>> _fetchDuniterNodesFromPeers(NodeType type) async {
       }
     }
     logger(
-        'Fetched ${lNodes.length} ${type
-            .name} nodes ordered by latency (first: ${lNodes.first.url})');
+        'Fetched ${lNodes.length} ${type.name} nodes ordered by latency (first: ${lNodes.first.url})');
   } catch (e, stacktrace) {
     logger('General error in fetch ${type.name} nodes: $e');
     logger(stacktrace);
@@ -318,8 +322,7 @@ Future<List<Node>> _fetchNodes(NodeType type) async {
     }
 
     logger(
-        'Fetched ${lNodes.length} ${type
-            .name} nodes ordered by latency (first: ${lNodes.first.url})');
+        'Fetched ${lNodes.length} ${type.name} nodes ordered by latency (first: ${lNodes.first.url})');
   } catch (e, stacktrace) {
     logger('General error in fetch ${type.name}: $e');
     logger(stacktrace);
@@ -331,19 +334,18 @@ Future<List<Node>> _fetchNodes(NodeType type) async {
 
 Future<Duration> _pingNode(String node, NodeType type) async {
   try {
-    final Stopwatch stopwatch = Stopwatch()
-      ..start();
+    final Stopwatch stopwatch = Stopwatch()..start();
     await http
         .get(Uri.parse(type == NodeType.duniter
-        ? '$node/network/peers/self/ping'
-        : type == NodeType.cesiumPlus
-        ?
-    // see: http://g1.data.e-is.pro/network/peering
-    '$node/network/peering'
-        :
-    // gva (just the url)
-    node))
-    // Decrease http timeout during ping
+            ? '$node/network/peers/self/ping'
+            : type == NodeType.cesiumPlus
+                ?
+                // see: http://g1.data.e-is.pro/network/peering
+                '$node/network/peering'
+                :
+                // gva (just the url)
+                node))
+        // Decrease http timeout during ping
         .timeout(const Duration(seconds: 10));
     stopwatch.stop();
     return stopwatch.elapsed;
@@ -374,30 +376,28 @@ Future<http.Response> requestGvaWithRetry(String path,
   return _requestWithRetry(NodeType.gva, path, true, retryWith404);
 }
 
-Future<http.Response> _requestWithRetry(NodeType type, String path,
-    bool dontRecord, bool retryWith404) async {
-  final List<Node> nodes = NodeManager().nodeList(type).where((
-      Node node) => node.errors <= NodeManager.maxNodeErrors).toList();
+Future<http.Response> _requestWithRetry(
+    NodeType type, String path, bool dontRecord, bool retryWith404) async {
+  final List<Node> nodes = NodeManager()
+      .nodeList(type)
+      .where((Node node) => node.errors <= NodeManager.maxNodeErrors)
+      .toList();
   if (nodes.isEmpty) {
     nodes.addAll(type == NodeType.duniter
         ? defaultDuniterNodes
         : type == NodeType.cesiumPlus
-        ? defaultCesiumPlusNodes
-        : defaultGvaNodes);
+            ? defaultCesiumPlusNodes
+            : defaultGvaNodes);
   }
   for (int i = 0; i < nodes.length; i++) {
     final Node node = nodes[i];
     try {
       final Uri url = Uri.parse('${node.url}$path');
       logger('Fetching $url (${type.name})');
-      final int startTime = DateTime
-          .now()
-          .millisecondsSinceEpoch;
+      final int startTime = DateTime.now().millisecondsSinceEpoch;
       final Response response =
-      await http.get(url).timeout(const Duration(seconds: 10));
-      final int endTime = DateTime
-          .now()
-          .millisecondsSinceEpoch;
+          await http.get(url).timeout(const Duration(seconds: 10));
+      final int endTime = DateTime.now().millisecondsSinceEpoch;
       final int newLatency = endTime - startTime;
       if (!kReleaseMode) {
         logger('response.statusCode: ${response.statusCode}');
@@ -452,13 +452,14 @@ Future<String> pay(
           recipient: to,
           amount: amount,
           comment: comment ?? '',
-          cesiumSeed: wallet.seed);
+          cesiumSeed: wallet.seed,
+          raiseException: true);
       logger('GVA replied with "$response"');
       return response;
     } catch (e, stacktrace) {
       logger(e);
       logger(stacktrace);
-      return "Oops! the payment failed. Something didn't work as expected";
+      return "Oops! the payment failed. Something didn't work as expected ($e)";
     }
   }
   return output;
@@ -471,8 +472,7 @@ String getGvaNode([bool useProxy = true]) {
     nodes.shuffle();
     // Reference of working proxy 'https://g1demo.comunes.net/proxy/g1v1.p2p.legal/gva/';
     final String node = useProxy
-        ? 'https://g1demo.comunes.net/proxy/${nodes.first.url.replaceFirst(
-        'https://', '').replaceFirst('http://', '')}/'
+        ? 'https://g1demo.comunes.net/proxy/${nodes.first.url.replaceFirst('https://', '').replaceFirst('http://', '')}/'
         : nodes.first.url;
     return node;
   } else {
@@ -494,8 +494,8 @@ Future<String?> gvaNick(String pubKey) async {
       pubKey, (Gva gva) => gva.getUsername(pubKey));
 }
 
-Future<T?> gvaFunctionWrapper<T>(String pubKey,
-    Future<T?> Function(Gva) specificFunction) async {
+Future<T?> gvaFunctionWrapper<T>(
+    String pubKey, Future<T?> Function(Gva) specificFunction) async {
   final List<Node> nodes = NodeManager()
       .nodeList(NodeType.gva)
       .where((Node node) => node.errors <= NodeManager.maxNodeErrors)
diff --git a/lib/g1/g1_helper.dart b/lib/g1/g1_helper.dart
index 490c4306f10db51004b611aa31f942e019d13784..25a04f439c4498ee6c4ceedb2e1bb058c9759d18 100644
--- a/lib/g1/g1_helper.dart
+++ b/lib/g1/g1_helper.dart
@@ -6,6 +6,7 @@ import 'package:durt/durt.dart';
 import 'package:encrypt/encrypt.dart' as encrypt;
 import 'package:encrypt/encrypt.dart';
 
+import '../data/models/contact.dart';
 import '../data/models/payment_state.dart';
 
 Random createRandom() {
@@ -121,7 +122,7 @@ PaymentState? parseScannedUri(String qr) {
   if (matchKeyAmount != null) {
     final String publicKey = matchKeyAmount.group(1)!;
     final double amount = double.parse(matchKeyAmount.group(2)!);
-    return PaymentState(publicKey: publicKey, amount: amount);
+    return PaymentState(contact: Contact(pubKey: publicKey), amount: amount);
   }
 
   // Match no amount
@@ -129,12 +130,12 @@ PaymentState? parseScannedUri(String qr) {
   final RegExpMatch? matchKey = regexKey.firstMatch(qr);
   if (matchKey != null) {
     final String publicKey = matchKey.group(1)!;
-    return PaymentState(publicKey: publicKey);
+    return PaymentState(contact: Contact(pubKey: publicKey));
   }
 
   // Match key only
   if (validateKey(qr)) {
-    return PaymentState(publicKey: qr);
+    return PaymentState(contact: Contact(pubKey: qr));
   }
 
   return null;
diff --git a/lib/ui/contacts_cache.dart b/lib/ui/contacts_cache.dart
index c7501198d77a3061fd53cceb2330542701cec834..ba833410d07bfc9e1422ea754e4423dcaa10a9b7 100644
--- a/lib/ui/contacts_cache.dart
+++ b/lib/ui/contacts_cache.dart
@@ -19,33 +19,30 @@ class ContactsCache {
   static ContactsCache? _instance;
   final Map<String, List<Completer<Contact>>> _pendingRequests =
       <String, List<Completer<Contact>>>{};
+  static Duration duration =
+      kReleaseMode ? const Duration(days: 3) : const Duration(hours: 5);
 
   Future<Contact> getContact(String pubKey) async {
-    final String cacheKey = 'contact-$pubKey';
-    const Duration duration =
-        kReleaseMode ? Duration(days: 3) : Duration(minutes: 5);
+    final String cacheKey = _key(pubKey);
 
+    Contact? cachedContact;
     try {
-      final String? cachedValue = window.localStorage[cacheKey];
-      if (cachedValue != null) {
-        final Map<String, dynamic> decodedValue =
-            json.decode(cachedValue) as Map<String, dynamic>;
-        final DateTime timestamp =
-            DateTime.parse(decodedValue['timestamp'] as String);
-
-        if (DateTime.now().isBefore(timestamp.add(duration))) {
-          final Contact contact =
-              Contact.fromJson(decodedValue['data'] as Map<String, dynamic>);
-          if (!kReleaseMode) {
-            // logger('Returning cached contact $contact');
-          }
-          return contact;
-        }
-      }
+      cachedContact = _retrieveContact(pubKey);
     } catch (e) {
       logger('Error while retrieving contact from cache: $e, $pubKey');
     }
 
+    if (cachedContact != null) {
+      if (!kReleaseMode) {
+        logger('Returning cached contact $cachedContact');
+      }
+      return cachedContact;
+    } else {
+      if (!kReleaseMode) {
+        logger('Contact $pubKey not cached');
+      }
+    }
+
     if (_pendingRequests.containsKey(pubKey)) {
       final Completer<Contact> completer = Completer<Contact>();
       _pendingRequests[pubKey]!.add(completer);
@@ -55,11 +52,11 @@ class ContactsCache {
     final Completer<Contact> completer = Completer<Contact>();
     _pendingRequests[pubKey] = <Completer<Contact>>[completer];
     try {
-      final Contact contact = await getProfile(pubKey);
+      cachedContact = await getProfile(pubKey);
 
       final String encodedValue = json.encode(<String, dynamic>{
         'timestamp': DateTime.now().toIso8601String(),
-        'data': contact.toJson(),
+        'data': cachedContact.toJson(),
       });
       window.localStorage[cacheKey] = encodedValue;
       if (!kReleaseMode) {
@@ -67,11 +64,11 @@ class ContactsCache {
       }
       // Send to listeners
       for (final Completer<Contact> completer in _pendingRequests[pubKey]!) {
-        completer.complete(contact);
+        completer.complete(cachedContact);
       }
       _pendingRequests.remove(pubKey);
 
-      return contact;
+      return cachedContact;
     } catch (e) {
       // Send error to listeners
       for (final Completer<Contact> completer in _pendingRequests[pubKey]!) {
@@ -82,4 +79,49 @@ class ContactsCache {
       rethrow;
     }
   }
+
+  String _key(String pubKey) => 'contact-$pubKey';
+
+  void addContact(Contact contact) {
+    // Get the cached version of the contact, if it exists
+    Contact? cachedContact = _retrieveContact(contact.pubKey);
+
+    // Merge the new contact with the cached contact
+    if (cachedContact != null) {
+      // logger('Merging contact $contact with cached contact $cachedContact');
+      cachedContact = cachedContact.merge(contact);
+    } else {
+      // logger('Adding contact $contact to cache as is not cached');
+      cachedContact = contact;
+    }
+
+    // Cache the merged contact
+    final String encodedValue = json.encode(<String, dynamic>{
+      'timestamp': DateTime.now().toIso8601String(),
+      'data': cachedContact.toJson(),
+    });
+    window.localStorage[_key(contact.pubKey)] = encodedValue;
+    // logger('Added contact $cachedContact to cache');
+  }
+
+  Contact? _retrieveContact(String pubKey) {
+    final String? cachedValue = window.localStorage[_key(pubKey)];
+    if (cachedValue != null) {
+      final Map<String, dynamic> decodedValue =
+          json.decode(cachedValue) as Map<String, dynamic>;
+      final DateTime timestamp =
+          DateTime.parse(decodedValue['timestamp'] as String);
+      final bool before = DateTime.now().isBefore(timestamp.add(duration));
+      if (before) {
+        final Contact contact =
+            Contact.fromJson(decodedValue['data'] as Map<String, dynamic>);
+        if (!kReleaseMode) {
+          logger('Returning cached contact $contact');
+        }
+        return contact;
+      }
+      // logger('Cached contact $pubKey is expired');
+    }
+    return null;
+  }
 }
diff --git a/lib/ui/screens/pay_form.dart b/lib/ui/screens/pay_form.dart
index e1f334f54a511a2c24f746abf651ecd6c6dbf5f6..7c1cc19f89aa68a3927a58885f9c25f512748b0e 100644
--- a/lib/ui/screens/pay_form.dart
+++ b/lib/ui/screens/pay_form.dart
@@ -41,7 +41,7 @@ class _PayFormState extends State<PayForm> {
             TextFormField(
               controller: _commentController,
               onChanged: (String? value) {
-                final bool? validate = _commentValidate();
+                final bool validate = _commentValidate();
                 if (validate != null &&
                     value != null &&
                     value.isNotEmpty &&
@@ -72,10 +72,11 @@ class _PayFormState extends State<PayForm> {
                   : () async {
                       // We disable the number, anyway
                       context.read<PaymentCubit>().sending();
+                      final String contactPubKey = state.contact!.pubKey;
                       final bool? confirmed = await _confirmSend(
                           context,
                           state.amount!.toString(),
-                          humanizePubKey(state.publicKey));
+                          humanizePubKey(contactPubKey));
                       if (!mounted) {
                         return;
                       }
@@ -83,7 +84,7 @@ class _PayFormState extends State<PayForm> {
                         context.read<PaymentCubit>().sentFailed();
                       } else {
                         final String response = await pay(
-                            to: state.publicKey,
+                            to: contactPubKey,
                             comment: state.comment,
                             amount: state.amount!);
                         if (!mounted) {
diff --git a/lib/ui/ui_helpers.dart b/lib/ui/ui_helpers.dart
index 1e38bbaeb46be8c5c78394d96d0e68a3a824ced3..c167066bc374111b8d7b1c8f4038ae7859727883 100644
--- a/lib/ui/ui_helpers.dart
+++ b/lib/ui/ui_helpers.dart
@@ -87,14 +87,14 @@ String humanizeContact(String publicAddress, Contact contact) {
 String humanizePubKey(String address) => '\u{1F511} ${simplifyPubKey(address)}';
 
 String simplifyPubKey(String address) => address.substring(0, 8);
-
+/*
 Widget humanizePubKeyAsWidget(String pubKey) => Text(
       humanizePubKey(pubKey),
       style: const TextStyle(
         fontSize: 16.0,
       ),
     );
-
+*/
 Color tileColor(int index, BuildContext context, [bool inverse = false]) {
   final ColorScheme colorScheme = Theme.of(context).colorScheme;
   final Color selectedColor = colorScheme.primary.withOpacity(0.1);
@@ -133,7 +133,7 @@ String formatKAmount(BuildContext context, double amount) =>
 double parseToDoubleLocalized(String locale, String double) =>
     NumberFormat.decimalPattern(locale).parse(double).toDouble();
 
-String getAppVersion() => '0.0.9';
+String getAppVersion() => '0.0.10';
 
 String localizeNumber(BuildContext context, double amount) =>
     NumberFormat.decimalPattern(context.locale.toString()).format(amount);
@@ -147,13 +147,14 @@ Contact contactFromResultSearch(Map<String, dynamic> record) {
       avatar: avatarBase64);
 }
 
+/*
 Contact contactFromUserProfile(Map<String, dynamic> source) {
   final Uint8List? avatarBase64 = _getAvatarFromResults(source);
   return Contact(
       pubKey: source['issuer'] as String,
       name: source['title'] as String,
       avatar: avatarBase64);
-}
+} */
 
 Uint8List? _getAvatarFromResults(Map<String, dynamic> source) {
   Uint8List? avatarBase64;
@@ -175,3 +176,21 @@ void fetchTransactions(BuildContext context) {
   final NodeListCubit nodeListCubit = context.read<NodeListCubit>();
   transCubit.fetchTransactions(nodeListCubit);
 }
+
+ListTile contactToListItem(Contact contact, int index, BuildContext context,
+    [VoidCallback? onTap, Widget? trailing]) {
+  final String title = contact.title;
+  final Widget? subtitle =
+      contact.subtitle != null ? Text(contact.subtitle!) : null;
+  return ListTile(
+      title: Text(title),
+      subtitle: subtitle,
+      tileColor: tileColor(index, context),
+      onTap: onTap,
+      leading: avatar(
+        contact.avatar,
+        bgColor: tileColor(index, context),
+        color: tileColor(index, context, true),
+      ),
+      trailing: trailing);
+}
diff --git a/lib/ui/widgets/fifth_screen/node_info.dart b/lib/ui/widgets/fifth_screen/node_info.dart
index eecba3d81d7a322dce1652b3c265a7780e753a5d..0356c6d6a26f51b3cc4d3c4a9b21769ea0b88924 100644
--- a/lib/ui/widgets/fifth_screen/node_info.dart
+++ b/lib/ui/widgets/fifth_screen/node_info.dart
@@ -25,11 +25,14 @@ class NodeInfoCard extends StatelessWidget {
               ? state.cesiumPlusNodes
               : state.gvaNodes;
       return GestureDetector(
-          onTap: () => ScaffoldMessenger.of(context).showSnackBar(
-                SnackBar(
-                  content: Text(tr('long_press_to_refresh')),
-                ),
+          onTap: () {
+            context.read<NodeListCubit>().shuffle(type);
+            ScaffoldMessenger.of(context).showSnackBar(
+              SnackBar(
+                content: Text(tr('long_press_to_refresh')),
               ),
+            );
+          },
           onLongPress: () {
             logger('On long press');
             if (type == NodeType.duniter) {
diff --git a/lib/ui/widgets/first_screen/pay_contact_search_button.dart b/lib/ui/widgets/first_screen/pay_contact_search_button.dart
index d546b9342fd1346e249d4f476955b05319a6520a..c6e5162499ebd8b02fde5c40ba65e9e64bf789cc 100644
--- a/lib/ui/widgets/first_screen/pay_contact_search_button.dart
+++ b/lib/ui/widgets/first_screen/pay_contact_search_button.dart
@@ -19,7 +19,7 @@ class _PayContactSearchButtonState extends State<PayContactSearchButton> {
   Widget build(BuildContext context) {
     return BlocBuilder<PaymentCubit, PaymentState>(
         builder: (BuildContext context, PaymentState state) {
-      if (state.publicKey.isEmpty) {
+      if (state.contact == null || state.contact!.pubKey.isEmpty) {
         return ElevatedButton.icon(
           onPressed: () {
             showDialog(
diff --git a/lib/ui/widgets/first_screen/pay_contact_search_page.dart b/lib/ui/widgets/first_screen/pay_contact_search_page.dart
index 3b4e83876e44c038e7584e07a9d608f83b63c877..ab48736e7b63f95a5e0b6fc7e5b2d614fe56db09 100644
--- a/lib/ui/widgets/first_screen/pay_contact_search_page.dart
+++ b/lib/ui/widgets/first_screen/pay_contact_search_page.dart
@@ -14,6 +14,7 @@ import '../../../data/models/payment_cubit.dart';
 import '../../../data/models/payment_state.dart';
 import '../../../g1/api.dart';
 import '../../../g1/g1_helper.dart';
+import '../../contacts_cache.dart';
 import '../../logger.dart';
 import '../../ui_helpers.dart';
 import '../custom_error_widget.dart';
@@ -35,18 +36,24 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> {
   bool _isLoading = false;
 
   Future<void> _search() async {
+    if (_searchTerm.length < 3) {
+      ScaffoldMessenger.of(context).showSnackBar(
+        SnackBar(content: Text(tr('search_limitation'))),
+      );
+      return;
+    }
+
     setState(() {
       _isLoading = true;
     });
 
     final Response cPlusResponse = await searchCPlusUser(_searchTerm);
-    if (cPlusResponse.statusCode == 404) {
-      setState(() {
-        _results = <Contact>[];
-      });
-    } else {
-      _results = await searchWot(_searchTerm);
-      // FIXME(vjrj) ... no avatars in wot!
+
+    setState(() {
+      _results = <Contact>[];
+    });
+
+    if (cPlusResponse.statusCode != 404) {
       setState(() {
         // Add cplus users
         final List<dynamic> hits = ((const JsonDecoder()
@@ -55,22 +62,52 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> {
         for (final dynamic hit in hits) {
           final Contact c =
               contactFromResultSearch(hit as Map<String, dynamic>);
-          logger('Contact retrieved in search $c');
-          _results.add(c);
+          logger('Contact retrieved in c+ search $c');
+          ContactsCache().addContact(c);
+          _addIfNotPresent(c);
         }
         logger('Found: ${_results.length}');
-        _isLoading = false;
       });
     }
+
+    final List<Contact> wotResults = await searchWot(_searchTerm);
+    // ignore: prefer_foreach
+    for (final Contact c in wotResults) {
+      ContactsCache().addContact(c);
+      _addIfNotPresent(c);
+      // retrieve extra results with c+ profile
+      for (final Contact wotC in wotResults) {
+        final Contact cachedWotProfile =
+            await ContactsCache().getContact(wotC.pubKey);
+        if (cachedWotProfile.name == null) {
+          // Users without c+ profile
+          final Contact cPlusProfile =
+              await getProfile(cachedWotProfile.pubKey, true);
+          ContactsCache().addContact(cPlusProfile);
+        }
+      }
+    }
+
     if (_results.isEmpty && validateKey(_searchTerm)) {
       logger('$_searchTerm looks like a plain pub key');
       setState(() {
-        _isLoading = true;
         final Contact contact = Contact(pubKey: _searchTerm);
         _results.add(contact);
-        _isLoading = false;
       });
     }
+
+    setState(() {
+      _isLoading = false;
+    });
+  }
+
+  void _addIfNotPresent(Contact contact) {
+    if (_results
+        .where((Contact c) => c.pubKey == contact.pubKey)
+        .toList()
+        .isEmpty) {
+      _results.add(contact);
+    }
   }
 
   @override
@@ -100,21 +137,17 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> {
                   final PaymentState? pay = parseScannedUri(scannedKey);
                   if (pay != null) {
                     logger('Scanned $pay');
-                    _searchTerm = pay.publicKey;
+                    _searchTerm = pay.contact!.pubKey;
                     await _search();
                   }
                   logger('QR result length ${_results.length}');
                   if (_results.length == 1 && pay != null) {
                     final Contact contact = _results[0];
-                    paymentCubit.selectUser(
-                        contact.pubKey,
-                        contact.nick ?? contact.name,
-                        contact.avatar,
-                        pay.amount);
+                    paymentCubit.selectUser(contact, pay.amount);
                   } else if (pay!.amount != null) {
-                    paymentCubit.selectKeyAmount(pay.publicKey, pay.amount!);
+                    paymentCubit.selectKeyAmount(pay.contact!, pay.amount!);
                   } else {
-                    paymentCubit.selectKey(pay.publicKey);
+                    paymentCubit.selectKey(pay.contact);
                   }
                   if (!mounted) {
                     return;
@@ -141,15 +174,11 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> {
                 labelText: tr('search_user'),
                 suffixIcon: IconButton(
                   icon: const Icon(Icons.search),
-                  onPressed: () {
-                    _search();
-                  },
+                  onPressed: () => _searchTerm.length < 3 ? null : _search(),
                 ),
               ),
               onChanged: (String value) {
-                setState(() {
-                  _searchTerm = value;
-                });
+                _searchTerm = value;
               },
               onSubmitted: (_) {
                 _search();
@@ -166,7 +195,7 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> {
                     itemBuilder: (BuildContext context, int index) {
                       final Contact contact = _results[index];
                       return FutureBuilder<Contact>(
-                          future: getWot(contact),
+                          future: ContactsCache().getContact(contact.pubKey),
                           builder: (BuildContext context,
                               AsyncSnapshot<Contact> snapshot) {
                             Widget widget;
@@ -190,31 +219,18 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> {
   }
 
   Widget _buildItem(Contact contact, int index, BuildContext context) {
-    logger('Contact retrieved $contact');
-    final String pubKey = contact.pubKey;
-    final String title = contact.nick ?? contact.name ?? humanizePubKey(pubKey);
-    final Widget? subtitle = (contact.nick != null || contact.name != null)
-        ? Text(humanizePubKey(pubKey))
-        : null;
-    final bool hasAvatar = contact.avatar != null;
-    return ListTile(
-      title: Text(title),
-      subtitle: subtitle,
-      tileColor: tileColor(index, context),
-      onTap: () {
-        context.read<PaymentCubit>().selectUser(pubKey,
-            contact.nick ?? contact.name, hasAvatar ? contact.avatar : null);
+    return contactToListItem(
+      contact,
+      index,
+      context,
+      () {
+        context.read<PaymentCubit>().selectUser(contact);
         Navigator.pop(context);
       },
-      leading: avatar(
-        contact.avatar,
-        bgColor: tileColor(index, context),
-        color: tileColor(index, context, true),
-      ),
-      trailing: BlocBuilder<ContactsCubit, ContactsState>(
+      BlocBuilder<ContactsCubit, ContactsState>(
           builder: (BuildContext context, ContactsState state) {
         final ContactsCubit contactsCubit = context.read<ContactsCubit>();
-        final bool isFavorite = contactsCubit.isContact(pubKey);
+        final bool isFavorite = contactsCubit.isContact(contact.pubKey);
         return IconButton(
             icon: Icon(
               isFavorite ? Icons.favorite : Icons.favorite_border,
@@ -226,7 +242,7 @@ class _PayContactSearchPageState extends State<PayContactSearchPage> {
                   contactsCubit.addContact(contact);
                 } else {
                   contactsCubit.removeContact(Contact(
-                    pubKey: pubKey,
+                    pubKey: contact.pubKey,
                   ));
                 }
               });
diff --git a/lib/ui/widgets/first_screen/recipient_widget.dart b/lib/ui/widgets/first_screen/recipient_widget.dart
index 9da9d361a60069506fa4ebfe96d196b78e93418b..58a5ab854e91d9af560d1b620c3cbb0331510edc 100644
--- a/lib/ui/widgets/first_screen/recipient_widget.dart
+++ b/lib/ui/widgets/first_screen/recipient_widget.dart
@@ -17,24 +17,29 @@ class RecipientWidget extends StatelessWidget {
                 child: Row(
                   crossAxisAlignment: CrossAxisAlignment.start,
                   children: <Widget>[
-                    avatar(state.avatar),
+                    avatar(state.contact!.avatar),
                     const SizedBox(width: 16.0),
                     Expanded(
                       child: Column(
                         crossAxisAlignment: CrossAxisAlignment.start,
                         children: <Widget>[
-                          if (state.nick != null)
+                          if (state.contact!.title != null)
                             Text(
-                              state.nick!,
+                              state.contact!.title,
                               style: const TextStyle(
                                 fontSize: 20.0,
                                 fontWeight: FontWeight.bold,
                               ),
                             ),
-                          Padding(
-                              padding: EdgeInsets.fromLTRB(
-                                  0, state.nick != null ? 0 : 12, 0, 0),
-                              child: humanizePubKeyAsWidget(state.publicKey)),
+                          if (state.contact!.subtitle != null)
+                            Padding(
+                                padding: const EdgeInsets.fromLTRB(0, 2, 0, 0),
+                                child: Text(
+                                  state.contact!.subtitle!,
+                                  style: const TextStyle(
+                                    fontSize: 16.0,
+                                  ),
+                                )),
                         ],
                       ),
                     ),
diff --git a/lib/ui/widgets/fourth_screen/transaction_page.dart b/lib/ui/widgets/fourth_screen/transaction_page.dart
index 1086ec4c0f6e222b4e0bd09f228a2752d812c77c..9a6067d2439650da31d7109d34c93f721d1c3201 100644
--- a/lib/ui/widgets/fourth_screen/transaction_page.dart
+++ b/lib/ui/widgets/fourth_screen/transaction_page.dart
@@ -57,7 +57,7 @@ class _TransactionsAndBalanceWidgetState
   }
 
   final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
-  GlobalKey<RefreshIndicatorState>();
+      GlobalKey<RefreshIndicatorState>();
 
   @override
   Widget build(BuildContext context) {
@@ -68,10 +68,7 @@ class _TransactionsAndBalanceWidgetState
       final double balance = transBalanceState.balance;
       return BackdropScaffold(
           appBar: BackdropAppBar(
-            backgroundColor: Theme
-                .of(context)
-                .colorScheme
-                .inversePrimary,
+            backgroundColor: Theme.of(context).colorScheme.inversePrimary,
             title: Text(tr('balance')),
             actions: <Widget>[
               IconButton(
@@ -81,60 +78,51 @@ class _TransactionsAndBalanceWidgetState
                   // _refreshTransactions();
                 },
               ),
-              const BackdropToggleButton(
-                // The default
-                // icon: AnimatedIcons.close_menu,
+              // BackdropToggleButton()
+              IconButton(
+                icon: const Icon(Icons.savings),
+                onPressed: () => Backdrop.of(context).animationController,
               )
             ],
           ),
           backLayer: Center(
               child: Container(
-                decoration: BoxDecoration(
-                  color: Theme
-                      .of(context)
-                      .colorScheme
-                      .inversePrimary,
-                  border: Border.all(
-                      color: Theme
-                          .of(context)
-                          .colorScheme
-                          .inversePrimary,
-                      width: 3),
-                  /* borderRadius: const BorderRadius.only(
+            decoration: BoxDecoration(
+              color: Theme.of(context).colorScheme.inversePrimary,
+              border: Border.all(
+                  color: Theme.of(context).colorScheme.inversePrimary,
+                  width: 3),
+              /* borderRadius: const BorderRadius.only(
               topLeft: Radius.circular(8),
               topRight: Radius.circular(8),
             ), */
+            ),
+            child: Scrollbar(
+                child: ListView(
+              //   controller: scrollController,
+              children: <Widget>[
+                Padding(
+                  padding: const EdgeInsets.symmetric(vertical: 10.0),
+                  child: Center(
+                      child: Text(
+                    formatKAmount(context, balance),
+                    style: TextStyle(
+                        fontSize: 36.0,
+                        color:
+                            balance == 0 ? Colors.lightBlue : Colors.lightBlue,
+                        fontWeight: FontWeight.bold),
+                  )),
                 ),
-                child: Scrollbar(
-                    child: ListView(
-                      //   controller: scrollController,
-                      children: <Widget>[
-                        Padding(
-                          padding: const EdgeInsets.symmetric(vertical: 10.0),
-                          child: Center(
-                              child: Text(
-                                formatKAmount(context, balance),
-                                style: TextStyle(
-                                    fontSize: 36.0,
-                                    color:
-                                    balance == 0 ? Colors.lightBlue : Colors
-                                        .lightBlue,
-                                    fontWeight: FontWeight.bold),
-                              )),
-                        ),
-                        if (!kReleaseMode) TransactionChart()
-                        /*BalanceChart(
+                if (!kReleaseMode) TransactionChart()
+                /*BalanceChart(
                                     transactions: .transactions),*/
-                      ],
-                    )),
-              )),
+              ],
+            )),
+          )),
           subHeader: BackdropSubHeader(
             title: Text(tr('transactions')),
             divider: Divider(
-              color: Theme
-                  .of(context)
-                  .colorScheme
-                  .surfaceVariant,
+              color: Theme.of(context).colorScheme.surfaceVariant,
               height: 0,
             ),
           ),
@@ -154,19 +142,16 @@ class _TransactionsAndBalanceWidgetState
                           child: Header(text: 'transactions'))), */
                   Expanded(
                       child: Padding(
-                        padding: const EdgeInsets.fromLTRB(0, 0, 0, 50),
-                        child: transactions.isEmpty
-                            ? Padding(
+                    padding: const EdgeInsets.fromLTRB(0, 0, 0, 50),
+                    child: transactions.isEmpty
+                        ? Padding(
                             padding: const EdgeInsets.symmetric(horizontal: 20),
                             child: Center(child: Text(tr('no_transactions'))))
-                            : RefreshIndicator(
+                        : RefreshIndicator(
                             key: _refreshIndicatorKey,
                             color: Colors.white,
                             backgroundColor:
-                            Theme
-                                .of(context)
-                                .colorScheme
-                                .primary,
+                                Theme.of(context).colorScheme.primary,
                             strokeWidth: 4.0,
                             onRefresh: () async {
                               return _refreshTransactions();
@@ -255,7 +240,7 @@ class _TransactionsAndBalanceWidgetState
                                       */
                               },
                             )),
-                      ))
+                  ))
                 ]),
           ));
     });
diff --git a/lib/ui/widgets/third_screen/contacts_page.dart b/lib/ui/widgets/third_screen/contacts_page.dart
index be8cf150fee2ba325886e30f546a8102bacbf723..36c2ffae125e5d2fad2c504598834cfe84318b9b 100644
--- a/lib/ui/widgets/third_screen/contacts_page.dart
+++ b/lib/ui/widgets/third_screen/contacts_page.dart
@@ -38,7 +38,6 @@ class _ContactsPageState extends State<ContactsPage> {
 
   @override
   Widget build(BuildContext context) {
-    // final ContactsCubit cubit = context.read<ContactsCubit>();
     return BlocBuilder<ContactsCubit, ContactsState>(
         builder: (BuildContext context, ContactsState state) {
       return Padding(
@@ -127,22 +126,7 @@ class _ContactsPageState extends State<ContactsPage> {
                           ),
                         ],
                       ),
-                      child: ListTile(
-                        title: contact.nick != null
-                            ? Text(contact.nick!)
-                            : contact.name != null
-                                ? Text(contact.name!)
-                                : humanizePubKeyAsWidget(contact.pubKey),
-                        subtitle: contact.nick != null || contact.name != null
-                            ? humanizePubKeyAsWidget(contact.pubKey)
-                            : null,
-                        leading: avatar(
-                          contact.avatar,
-                          bgColor: tileColor(index, context),
-                          color: tileColor(index, context, true),
-                        ),
-                        tileColor: tileColor(index, context),
-                      ),
+                      child: contactToListItem(contact, index, context),
                     );
                   },
                 )),
@@ -153,9 +137,7 @@ class _ContactsPageState extends State<ContactsPage> {
   }
 
   void onSent(BuildContext c, Contact contact) {
-    c
-        .read<PaymentCubit>()
-        .selectUser(contact.pubKey, contact.nick, contact.avatar);
+    c.read<PaymentCubit>().selectUser(contact);
     c.read<BottomNavCubit>().updateIndex(0);
   }
 }
diff --git a/pubspec.yaml b/pubspec.yaml
index 1c1b9fd0347605e76d478ddff2b222c209cda094..801ebc12464e474bae146f8d4a80aaca85a2bac1 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
 # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
 # Read more about iOS versioning at
 # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
-version: 0.0.9
+version: 0.0.10
 
 environment:
   sdk: ">=2.17.1 <3.0.0"
diff --git a/test/contacts_test.dart b/test/contacts_test.dart
index c7d72a6e8ffd7659526bd851eb97670cc277d289..aad6424d4d710d28a9e862d3af86d55eaf00e275 100644
--- a/test/contacts_test.dart
+++ b/test/contacts_test.dart
@@ -35,4 +35,49 @@ void main() {
     expect(contactFromWithAvatar.avatar!.toList(),
         equals(<int>[68, 174, 66, 96, 130]));
   });
+
+  group('Contact merge test', () {
+    test('Merge properties', () {
+      const Contact contact1 = Contact(nick: 'nick1', pubKey: 'pubKey');
+
+      final Contact contact2 = Contact(
+        pubKey: 'pubKey',
+        avatar: Uint8List.fromList(<int>[1, 2, 3]),
+        name: 'name2',
+        notes: 'notes2',
+      );
+
+      final List<Contact> merged = <Contact>[
+        contact1.merge(contact2),
+        contact2.merge(contact1),
+      ];
+      for (final Contact mergedContact in merged) {
+        expect(mergedContact.nick, 'nick1');
+        expect(mergedContact.pubKey, 'pubKey');
+        expect(mergedContact.avatar, Uint8List.fromList(<int>[1, 2, 3]));
+        expect(mergedContact.notes, 'notes2');
+        expect(mergedContact.name, 'name2');
+      }
+    });
+
+    test('Merge with empty contact', () {
+      final Contact contact1 = Contact(
+        nick: 'nick1',
+        pubKey: 'pubKey1',
+        avatar: Uint8List.fromList(<int>[1, 2, 3]),
+        notes: 'notes1',
+        name: 'name1',
+      );
+
+      const Contact contact2 = Contact(pubKey: 'pubKey1');
+
+      final Contact mergedContact = contact1.merge(contact2);
+
+      expect(mergedContact.nick, 'nick1');
+      expect(mergedContact.pubKey, 'pubKey1');
+      expect(mergedContact.avatar, Uint8List.fromList(<int>[1, 2, 3]));
+      expect(mergedContact.notes, 'notes1');
+      expect(mergedContact.name, 'name1');
+    });
+  });
 }
diff --git a/test/keys_test.dart b/test/keys_test.dart
index 6936a4046f63874695292b5a69532baa37bba3c6..1962d5bf6ad0eb33133ab9f81166de08a7c5974f 100644
--- a/test/keys_test.dart
+++ b/test/keys_test.dart
@@ -54,16 +54,16 @@ void main() {
     final String uriA = getQrUri(publicKey, '10');
     final PaymentState? payA = parseScannedUri(uriA);
     expect(payA!.amount, equals(10));
-    expect(payA.publicKey, equals(publicKey));
+    expect(payA.contact!.pubKey, equals(publicKey));
 
     final String uriB = getQrUri(publicKey);
     final PaymentState? payB = parseScannedUri(uriB);
     expect(payB!.amount, equals(null));
-    expect(payB.publicKey, equals(publicKey));
+    expect(payB.contact!.pubKey, equals(publicKey));
 
     final PaymentState? payC = parseScannedUri(publicKey);
     expect(payC!.amount, equals(null));
-    expect(payC.publicKey, equals(publicKey));
+    expect(payC.contact!.pubKey, equals(publicKey));
   });
 
   test('encrypt/decrypt of keys', () {