diff --git a/lib/app_block_observer.dart b/lib/app_block_observer.dart
deleted file mode 100644
index be917c0715dc39ad7c7ec13c155d5858280669a3..0000000000000000000000000000000000000000
--- a/lib/app_block_observer.dart
+++ /dev/null
@@ -1,35 +0,0 @@
-import 'package:bloc/bloc.dart';
-
-import 'main.dart';
-
-class AppBlocObserver extends BlocObserver {
-  @override
-  void onCreate(BlocBase<dynamic> bloc) {
-    super.onCreate(bloc);
-    logger('onCreate -- ${bloc.runtimeType}');
-  }
-
-  @override
-  void onEvent(Bloc<dynamic, dynamic> bloc, Object? event) {
-    super.onEvent(bloc, event);
-    logger('onEvent -- $event');
-  }
-
-  @override
-  void onTransition(Bloc<dynamic, dynamic> bloc, Transition transition) {
-    super.onTransition(bloc, transition);
-    logger('onTransition -- $transition');
-  }
-
-  @override
-  void onError(BlocBase<dynamic> bloc, Object error, StackTrace stackTrace) {
-    super.onError(bloc, error, stackTrace);
-    logger('onError -- $error');
-  }
-
-  @override
-  void onClose(BlocBase<dynamic> bloc) {
-    super.onClose(bloc);
-    logger('onClose -- ${bloc.runtimeType}');
-  }
-}
diff --git a/lib/data/models/isJsonSerializable.dart b/lib/data/models/isJsonSerializable.dart
new file mode 100644
index 0000000000000000000000000000000000000000..1490d7c31cab2c33acc2527dbfb6af34bae4e90f
--- /dev/null
+++ b/lib/data/models/isJsonSerializable.dart
@@ -0,0 +1,5 @@
+abstract class IsJsonSerializable<T> {
+  T fromJson(Map<String, dynamic> json);
+
+  Map<String, dynamic> toJson();
+}
diff --git a/lib/g1/api.dart b/lib/g1/api.dart
index ffcb6f55daf7a8552bebe8586a4d23e733e99a1f..be7e5e47f4fa85a70629dae18950c2beeba0f34c 100644
--- a/lib/g1/api.dart
+++ b/lib/g1/api.dart
@@ -1,19 +1,23 @@
 import 'dart:convert';
 import 'dart:io';
-import 'dart:typed_data';
 
+import 'package:flutter/foundation.dart';
 import 'package:http/http.dart' as http;
 import 'package:http/http.dart';
 
-import 'node_bloc.dart';
+import '../main.dart';
+import 'g1_helper.dart';
+import 'node.dart';
+import 'node_list_cubit.dart';
 
 // Tx history
 // https://g1.duniter.org/tx/history/FadJvhddHL7qbRd3WcRPrWEJJwABQa3oZvmCBhotc7Kg
 // https://g1.duniter.org/tx/history/6DrGg8cftpkgffv4Y4Lse9HSjgc8coEQor3yvMPHAnVH
 
-Future<String> getTxHistory(String publicKey) async {
+Future<String> getTxHistory(
+    NodeListCubit nodeListCubit, String publicKey) async {
   final Response response =
-      await NodeBloc().requestWithRetry('/tx/history/$publicKey');
+      await requestWithRetry(nodeListCubit, '/tx/history/$publicKey');
   if (response.statusCode == 200) {
     return response.body;
   } else {
@@ -21,8 +25,9 @@ Future<String> getTxHistory(String publicKey) async {
   }
 }
 
-Future<Response> getPeers() async {
-  final Response response = await NodeBloc().requestWithRetry('/network/peers');
+Future<Response> getPeers(NodeListCubit nodeListCubit) async {
+  final Response response =
+      await requestWithRetry(nodeListCubit, '/network/peers', dontRecord: true);
   if (response.statusCode == 200) {
     return response;
   } else {
@@ -30,9 +35,10 @@ Future<Response> getPeers() async {
   }
 }
 
-Future<Response> searchUser(String searchTerm) async {
+Future<Response> searchUser(
+    NodeListCubit nodeListCubit, String searchTerm) async {
   final Response response =
-      await NodeBloc().requestWithRetry('/wot/lookup/$searchTerm');
+      await requestWithRetry(nodeListCubit, '/wot/lookup/$searchTerm');
   return response;
 }
 
@@ -49,7 +55,8 @@ Not found sample:
 }
  */
 
-Future<String> getDataImageFromKey(String publicKey) async {
+Future<String> getDataImageFromKey(
+    NodeListCubit nodeListCubit, String publicKey) async {
   // FIXME (vjrj) use node manager and retry...
   final String url = 'https://g1.data.le-sou.org/user/profile/$publicKey';
   final Response response = await http.get(Uri.parse(url));
@@ -74,7 +81,123 @@ Uint8List imageFromBase64String(String base64String) {
       base64Decode(base64String.substring(base64String.indexOf(',') + 1)));
 }
 
-Future<Uint8List> getAvatar(String pubKey) async {
-  final String dataImage = await getDataImageFromKey(pubKey);
+Future<Uint8List> getAvatar(NodeListCubit nodeListCubit, String pubKey) async {
+  final String dataImage = await getDataImageFromKey(nodeListCubit, pubKey);
   return imageFromBase64String(dataImage);
 }
+
+Future<void> fetchDuniterNodes(NodeListCubit cubit) async {
+  final List<Node> nodes = await fetchNodesFromApi(cubit);
+  cubit.setDuniterNodes(nodes);
+}
+
+Future<List<Node>> fetchNodesFromApi(NodeListCubit cubit) async {
+  final List<Node> lNodes = <Node>[];
+  // To compare with something...
+  String fastestNode = 'https://g1.duniter.org';
+  late Duration fastestLatency = const Duration(minutes: 1);
+  try {
+    final Response response = await getPeers(cubit);
+    if (response.statusCode == 200) {
+      final Map<String, dynamic> peerList =
+          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')
+          .where(
+              (dynamic peer) => (peer as Map<String, dynamic>)['version'] == 10)
+          .where((dynamic peer) =>
+              (peer as Map<String, dynamic>)['status'] == 'UP')
+          .toList();
+      for (final dynamic peerR in peers) {
+        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>);
+          for (int j = 0; j < endpoints.length; j++) {
+            if (endpoints[j].startsWith('BMAS')) {
+              final String endpointUnParsed = endpoints[j];
+              final String? endpoint = parseHost(endpointUnParsed);
+              if (endpoint != null) {
+                final Duration latency = await _pingNode(endpoint);
+                if (fastestNode == null || latency < fastestLatency) {
+                  fastestNode = endpoint;
+                  fastestLatency = latency;
+                  if (!kReleaseMode) {
+                    logger('Node bloc: Current faster node $fastestNode');
+                  }
+                }
+                final Node node =
+                    Node(url: endpoint, latency: latency.inMicroseconds);
+                cubit.insertDuniterNode(node);
+                lNodes.insert(0, node);
+              }
+            }
+          }
+        }
+      }
+    }
+    logger('Node bloc: Loaded ${lNodes.length} duniter nodes');
+  } catch (e) {
+    logger('Error: $e');
+    rethrow;
+  }
+  lNodes.sort((Node a, Node b) => a.latency.compareTo(b.latency));
+  logger('First node in list ${lNodes.first.url}');
+  return lNodes;
+}
+
+Future<Duration> _pingNode(String node) async {
+  try {
+    final Stopwatch stopwatch = Stopwatch()..start();
+    await http.get(Uri.parse('$node/network/peers/self/ping'));
+    stopwatch.stop();
+    return stopwatch.elapsed;
+  } catch (e) {
+    // Handle exception when node is unavailable etc
+    logger('Node $node does not respond to ping $e');
+    return const Duration(days: 2);
+  }
+}
+
+Future<http.Response> requestWithRetry(NodeListCubit cubit, String path,
+    {bool dontRecord = false}) async {
+  return _requestWithRetry(cubit, cubit.duniterNodes, path, dontRecord);
+}
+
+Future<http.Response> requestCPlusWithRetry(
+    NodeListCubit cubit, String path) async {
+  return _requestWithRetry(cubit, cubit.cesiumPlusNodes, path, true);
+}
+
+Future<http.Response> _requestWithRetry(
+    NodeListCubit cubit, List<Node> nodes, String path, bool dontRecord) async {
+  for (int i = 0; i < nodes.length; i++) {
+    final Node node = nodes[i];
+    if (node.errors >= 3) {
+      // Too much errors skip
+      continue;
+    }
+    final Uri url = Uri.parse('${node.url}$path');
+    logger('Trying $url');
+    try {
+      final int startTime = DateTime.now().millisecondsSinceEpoch;
+      final Response response = await http.get(url);
+      final int endTime = DateTime.now().millisecondsSinceEpoch;
+      final int newLatency = endTime - startTime;
+      if (response.statusCode == 200) {
+        if (!dontRecord) {
+          cubit.updateDuniterNode(node.copyWith(latency: newLatency));
+        }
+        return response;
+      }
+    } catch (e) {
+      if (!dontRecord) {
+        cubit.updateDuniterNode(node.copyWith(errors: node.errors + 1));
+      }
+      continue;
+    }
+  }
+  throw Exception(
+      'Cannot make the request to any of the ${nodes.length} nodes');
+}
diff --git a/lib/g1/node.dart b/lib/g1/node.dart
index bbfc409710fdfb708b9deddebd3779f1a61c720e..edd93402babc26c1749c9881e577b63b6131c7c1 100644
--- a/lib/g1/node.dart
+++ b/lib/g1/node.dart
@@ -1,17 +1,20 @@
 import 'package:equatable/equatable.dart';
 import 'package:json_annotation/json_annotation.dart';
 
+import '../data/models/isJsonSerializable.dart';
+
 part 'node.g.dart';
 
 @JsonSerializable()
-class Node {
-  Node({
+class Node extends Equatable implements IsJsonSerializable<Node> {
+  const Node({
     required this.url,
     this.latency = 99999,
     this.errors = 0,
   });
 
   factory Node.fromJson(Map<String, dynamic> json) => _$NodeFromJson(json);
+
   final String url;
   final int latency;
   final int errors;
@@ -34,130 +37,17 @@ class Node {
   }
 
   @override
-  bool operator ==(Object other) =>
-      identical(this, other) ||
-      other is Node &&
-          runtimeType == other.runtimeType &&
-          url == other.url &&
-          latency == other.latency &&
-          errors == other.errors;
-
-  @override
-  int get hashCode => url.hashCode ^ latency.hashCode ^ errors.hashCode;
-}
-
-abstract class NodeEvent extends Equatable {
-  const NodeEvent();
-
-  @override
-  List<Object?> get props => <dynamic>[];
-}
-
-class AddNode extends NodeEvent {
-  const AddNode({required this.node});
-
-  final Node node;
-
-  @override
-  List<Object?> get props => [node];
-}
-
-class InsertNode extends NodeEvent {
-  const InsertNode({required this.node});
-
-  final Node node;
+  Map<String, dynamic> toJson() => _$NodeToJson(this);
 
   @override
-  List<Object?> get props => <dynamic>[node];
-}
-
-class AddCPlusNode extends NodeEvent {
-  const AddCPlusNode({required this.node});
-
-  final Node node;
+  Node fromJson(Map<String, dynamic> json) => Node.fromJson(json);
 
   @override
-  List<Object?> get props => <dynamic>[node];
-}
-
-extension NodeExtensions on Node {
-  Map<String, dynamic> toJson() {
-    return <String, dynamic>{
-      'url': url,
-      'latency': latency,
-      'errors': errors,
-    };
-  }
-
-  static Node fromJson(Map<String, dynamic> json) {
-    return Node(
-      url: json['url'] as String,
-      latency: json['latency'] as int,
-      errors: json['errors'] as int,
-    );
-  }
-}
-
-enum NodeStatus { loading, loaded }
+  List<Object?> get props => <dynamic>[url, latency, errors];
 
-class NodeState extends Equatable {
-  const NodeState(
-      {required this.nodes, required this.cPlusNodes, required this.status});
-
-  final List<Node> nodes;
-  final List<Node> cPlusNodes;
-  final NodeStatus status;
-
-  NodeState copyWith(
-      {List<Node>? nodes, List<Node>? cPlusNodes, NodeStatus? status}) {
-    return NodeState(
-      nodes: nodes ?? this.nodes,
-      cPlusNodes: cPlusNodes ?? this.cPlusNodes,
-      status: status ?? this.status,
-    );
-  }
-
-  @override
-  List<Object> get props => [nodes, cPlusNodes, status];
-}
-
-class LoadNodes extends NodeEvent {
-  const LoadNodes(this.nodes);
-
-  final List<Node> nodes;
-
-  @override
-  List<Object> get props => <Object>[nodes];
-
-  @override
-  String toString() => 'LoadNodes { nodes: $nodes }';
-}
-
-class SetNodes extends NodeEvent {
-  const SetNodes(this.nodes);
-
-  final List<Node> nodes;
-
-  @override
-  List<Object> get props => <Object>[nodes];
-
-  @override
-  String toString() => 'AddNodes { nodes: $nodes }';
-}
-
-class AddCPlusNodes extends NodeEvent {
-  const AddCPlusNodes(this.nodes);
-
-  final List<Node> nodes;
-
-  @override
-  List<Object> get props => [nodes];
-
-  @override
-  String toString() => 'AddNodes { nodes: $nodes }';
 }
 
-final List<Node> defaultDuniterNodes = <Node>[
+const List<Node> defaultDuniterNodes = <Node>[
   Node(url: 'https://g1.duniter.fr'),
   Node(url: 'https://g1.le-sou.org'),
   Node(url: 'https://g1.cgeek.fr'),
@@ -167,7 +57,7 @@ final List<Node> defaultDuniterNodes = <Node>[
   Node(url: 'https://g1.cgeek.fr')
 ];
 
-final List<Node> defaultCesiumPlusNodes = <Node>[
+const List<Node> defaultCesiumPlusNodes = <Node>[
   Node(url: 'https://g1.data.e-is.pro'),
   Node(url: 'https://g1.data.presler.fr'),
   Node(url: 'https://g1.data.le-sou.org'),
diff --git a/lib/g1/node_bloc.dart b/lib/g1/node_bloc.dart
deleted file mode 100644
index a855fae68f7474064fe8ecc6d06c91dac7922c53..0000000000000000000000000000000000000000
--- a/lib/g1/node_bloc.dart
+++ /dev/null
@@ -1,203 +0,0 @@
-import 'dart:async';
-import 'dart:convert';
-
-import 'package:flutter/foundation.dart';
-import 'package:http/http.dart' as http;
-import 'package:http/http.dart';
-import 'package:hydrated_bloc/hydrated_bloc.dart';
-
-import './node.dart';
-import '../main.dart';
-import 'api.dart';
-import 'g1_helper.dart';
-
-class NodeBloc extends HydratedBloc<NodeEvent, NodeState> {
-  factory NodeBloc() {
-    return _singleton;
-  }
-
-  NodeBloc._internal()
-      : super(NodeState(
-            nodes: defaultDuniterNodes,
-            cPlusNodes: defaultCesiumPlusNodes,
-            status: NodeStatus.loading)) {
-    // Init NodeBloc here
-    on<LoadNodes>((LoadNodes event, Emitter<NodeState> emit) async {
-      final List<Node> nodes = await fetchNodesFromApi();
-      emit(NodeState(
-          nodes: nodes,
-          cPlusNodes: defaultCesiumPlusNodes,
-          status: NodeStatus.loaded));
-    });
-    on<AddNode>((AddNode event, Emitter<NodeState> emit) async {});
-    on<InsertNode>((InsertNode event, Emitter<NodeState> emit) async {});
-    on<SetNodes>((SetNodes event, Emitter<NodeState> emit) async {});
-    on<AddCPlusNode>((AddCPlusNode event, Emitter<NodeState> emit) async {});
-    on<AddCPlusNodes>((AddCPlusNodes event, Emitter<NodeState> emit) async {});
-  }
-
-  static final NodeBloc _singleton = NodeBloc._internal();
-
-  List<Node> get nodeList => state.nodes;
-
-  List<Node> get cPlusNodeList => state.cPlusNodes;
-
-  Stream<NodeState> mapEventToState(NodeEvent event) async* {
-    if (event is LoadNodes) {
-      final List<Node> nodes = List<Node>.of(state.nodes)..addAll(event.nodes);
-      yield state.copyWith(nodes: nodes);
-    } else if (event is SetNodes) {
-      // final List<Node> nodes = List<Node>.of(state.nodes)..addAll(event.nodes);
-      yield state.copyWith(nodes: event.nodes);
-    } else if (event is AddNode) {
-      final List<Node> nodes = List<Node>.of(state.nodes)..add(event.node);
-      yield state.copyWith(nodes: nodes);
-    } else if (event is InsertNode) {
-      final List<Node> nodes = List<Node>.of(state.nodes)
-        ..insert(0, event.node);
-      yield state.copyWith(nodes: nodes);
-    } else if (event is AddCPlusNodes) {
-      final List<Node> nodes = List<Node>.of(state.cPlusNodes)
-        ..addAll(event.nodes);
-      yield state.copyWith(cPlusNodes: nodes);
-    } else if (event is AddCPlusNode) {
-      final List<Node> nodes = List<Node>.of(state.cPlusNodes)..add(event.node);
-      yield state.copyWith(cPlusNodes: nodes);
-    }
-  }
-
-  @override
-  NodeState? fromJson(Map<String, dynamic> json) {
-    final List<Node> nodes = (json['nodes'] as List<Node>)
-        .map((dynamic nodeJson) =>
-            Node.fromJson(nodeJson as Map<String, dynamic>))
-        .toList();
-    final List<Node> cPlusNodes = (json['cPlusNodes'] as List<Node>)
-        .map((dynamic nodeJson) =>
-            Node.fromJson(nodeJson as Map<String, dynamic>))
-        .toList();
-    logger(
-        'Loaded with ${nodes.length} duniter nodes and ${cPlusNodes.length} c+ nodes');
-    return NodeState(
-        nodes: nodes, cPlusNodes: cPlusNodes, status: NodeStatus.loaded);
-  }
-
-  @override
-  Map<String, dynamic>? toJson(NodeState state) {
-    return <String, dynamic>{
-      'nodes': state.nodes.map((Node node) => node.toJson()).toList(),
-    };
-  }
-
-  Future<void> loadNodes() async {
-    final List<Node> nodes = await fetchNodesFromApi();
-    add(SetNodes(nodes));
-  }
-
-  Future<List<Node>> fetchNodesFromApi() async {
-    final List<Node> nodes = <Node>[];
-    // To compare with somthing...
-    String fastestNode = 'https://g1.duniter.org';
-    late Duration fastestLatency = const Duration(minutes: 1);
-    try {
-      final Response response = await getPeers();
-      if (response.statusCode == 200) {
-        final Map<String, dynamic> peerList =
-            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')
-            .where((dynamic peer) =>
-                (peer as Map<String, dynamic>)['version'] == 10)
-            .where((dynamic peer) =>
-                (peer as Map<String, dynamic>)['status'] == 'UP')
-            .toList();
-        for (final dynamic peerR in peers) {
-          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>);
-            for (int j = 0; j < endpoints.length; j++) {
-              if (endpoints[j].startsWith('BMAS')) {
-                final String endpointUnParsed = endpoints[j];
-                final String? endpoint = parseHost(endpointUnParsed);
-                if (endpoint != null) {
-                  final Duration latency = await _pingNode(endpoint);
-                  if (fastestNode == null || latency < fastestLatency) {
-                    fastestNode = endpoint;
-                    fastestLatency = latency;
-                    if (!kReleaseMode) {
-                      logger('Node bloc: Current faster node $fastestNode');
-                    }
-                  }
-                  final Node node =
-                      Node(url: endpoint, latency: latency.inSeconds);
-                  add(InsertNode(node: node));
-                  nodes.insert(0, node);
-                }
-              }
-            }
-          }
-        }
-      }
-      logger('Node bloc: Loaded ${nodes.length} duniter nodes');
-    } catch (e) {
-      logger('Error: $e');
-      rethrow;
-    }
-    nodes.sort((Node a, Node b) => a.latency.compareTo(b.latency));
-    logger('First node in list ${nodes.first.url}');
-    return nodes;
-  }
-
-  Future<Duration> _pingNode(String node) async {
-    try {
-      final Stopwatch stopwatch = Stopwatch()..start();
-      await http.get(Uri.parse('$node/network/peers/self/ping'));
-      stopwatch.stop();
-      return stopwatch.elapsed;
-    } catch (e) {
-      // Handle exception when node is unavailable etc
-      logger('Node $node does not respond to ping $e');
-      return const Duration(days: 20);
-    }
-  }
-
-  Future<http.Response> requestWithRetry(String path) async {
-    return _requestWithRetry(nodeList, path);
-  }
-
-  Future<http.Response> requestCPlusWithRetry(String path) async {
-    return _requestWithRetry(cPlusNodeList, path);
-  }
-
-  Future<http.Response> _requestWithRetry(List<Node> nodes, String path) async {
-    for (int i = 0; i < nodes.length; i++) {
-      final Node node = nodes[i];
-      if (node.errors >= 3) {
-        // Too much errors skip
-        continue;
-      }
-      final Uri url = Uri.parse('${node.url}$path');
-      logger('Trying $url');
-      try {
-        final int startTime = DateTime.now().millisecondsSinceEpoch;
-        final Response response = await http.get(url);
-        final int endTime = DateTime.now().millisecondsSinceEpoch;
-        final int newLatency = endTime - startTime;
-        if (response.statusCode == 200) {
-          final Node newNode = node.copyWith(latency: newLatency, errors: 0);
-          nodes[i] = newNode;
-          return response;
-        }
-      } catch (e) {
-        final int newErrors = node.errors + 1;
-        final Node newNode = node.copyWith(errors: newErrors);
-        nodes[i] = newNode;
-        continue;
-      }
-    }
-    throw Exception(
-        'Cannot make the request to any of the ${nodes.length} nodes');
-  }
-}
diff --git a/lib/g1/node_list_cubit.dart b/lib/g1/node_list_cubit.dart
new file mode 100644
index 0000000000000000000000000000000000000000..00a5cd2f0dd5267615348d4ab007c0a693b22635
--- /dev/null
+++ b/lib/g1/node_list_cubit.dart
@@ -0,0 +1,52 @@
+import 'package:hydrated_bloc/hydrated_bloc.dart';
+
+import 'node.dart';
+import 'node_list_state.dart';
+
+class NodeListCubit extends HydratedCubit<NodeListState> {
+  NodeListCubit() : super(const NodeListState());
+
+  void addDuniterNode(Node node) {
+    emit(state.copyWith(duniterNodes: <Node>[...state.duniterNodes, node]));
+  }
+
+  void updateDuniterNode(Node updatedNode) {
+    final List<Node> updatedDuniterNodes = state.duniterNodes.map((Node n) {
+      return n.url == updatedNode.url ? updatedNode : n;
+    }).toList();
+    emit(state.copyWith(duniterNodes: updatedDuniterNodes));
+  }
+
+  void setDuniterNodes(List<Node> nodes) {
+    emit(state.copyWith(duniterNodes: nodes));
+  }
+
+  void insertDuniterNode(Node node) {
+    emit(state.copyWith(duniterNodes: <Node>[node, ...state.duniterNodes]));
+  }
+
+  void cleanDuniterErrorStats() {
+    emit(state.copyWith(
+        duniterNodes: duniterNodes
+            .map((Node node) => node.copyWith(errors: 0))
+            .toList()));
+  }
+
+  void addCesiumPlusNode(Node node) {
+    emit(state
+        .copyWith(cesiumPlusNodes: <Node>[...state.cesiumPlusNodes, node]));
+  }
+
+  List<Node> get duniterNodes => state.duniterNodes;
+
+  List<Node> get cesiumPlusNodes => state.cesiumPlusNodes;
+
+  @override
+  NodeListState? fromJson(Map<String, dynamic> json) =>
+      NodeListState.fromJson(json);
+
+  @override
+  Map<String, dynamic>? toJson(NodeListState state) {
+    return state.toJson();
+  }
+}
diff --git a/lib/g1/node_list_state.dart b/lib/g1/node_list_state.dart
new file mode 100644
index 0000000000000000000000000000000000000000..f30d53e06b8db0927c6d2ea013b43035adc41497
--- /dev/null
+++ b/lib/g1/node_list_state.dart
@@ -0,0 +1,33 @@
+import 'package:equatable/equatable.dart';
+import 'package:flutter/material.dart';
+import 'package:json_annotation/json_annotation.dart';
+
+import 'node.dart';
+
+part 'node_list_state.g.dart';
+
+@immutable
+@JsonSerializable()
+class NodeListState extends Equatable {
+  const NodeListState(
+      {this.duniterNodes = defaultDuniterNodes,
+      this.cesiumPlusNodes = defaultCesiumPlusNodes});
+
+  factory NodeListState.fromJson(Map<String, dynamic> json) =>
+      _$NodeListStateFromJson(json);
+
+  final List<Node> duniterNodes;
+  final List<Node> cesiumPlusNodes;
+
+  NodeListState copyWith(
+      {List<Node>? duniterNodes, List<Node>? cesiumPlusNodes}) {
+    return NodeListState(
+        duniterNodes: duniterNodes ?? this.duniterNodes,
+        cesiumPlusNodes: cesiumPlusNodes ?? this.cesiumPlusNodes);
+  }
+
+  @override
+  List<Object?> get props => <Object>[duniterNodes, cesiumPlusNodes];
+
+  Map<String, dynamic> toJson() => _$NodeListStateToJson(this);
+}
diff --git a/lib/g1/node_list_state.g.dart b/lib/g1/node_list_state.g.dart
new file mode 100644
index 0000000000000000000000000000000000000000..880b76cd2fa2b9b2f1ab6f7ba1d191f215355b48
--- /dev/null
+++ b/lib/g1/node_list_state.g.dart
@@ -0,0 +1,25 @@
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+part of 'node_list_state.dart';
+
+// **************************************************************************
+// JsonSerializableGenerator
+// **************************************************************************
+
+NodeListState _$NodeListStateFromJson(Map<String, dynamic> json) =>
+    NodeListState(
+      duniterNodes: (json['duniterNodes'] as List<dynamic>?)
+              ?.map((e) => Node.fromJson(e as Map<String, dynamic>))
+              .toList() ??
+          defaultDuniterNodes,
+      cesiumPlusNodes: (json['cesiumPlusNodes'] as List<dynamic>?)
+              ?.map((e) => Node.fromJson(e as Map<String, dynamic>))
+              .toList() ??
+          defaultCesiumPlusNodes,
+    );
+
+Map<String, dynamic> _$NodeListStateToJson(NodeListState instance) =>
+    <String, dynamic>{
+      'duniterNodes': instance.duniterNodes,
+      'cesiumPlusNodes': instance.cesiumPlusNodes,
+    };
diff --git a/lib/main.dart b/lib/main.dart
index 04559e69afbcf2bbfbd61f7a8996a07669499fb2..173ef22dcade5d02802ad7d23de22971a1bf8441 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -15,10 +15,11 @@ import 'package:introduction_screen/introduction_screen.dart';
 import 'package:path_provider/path_provider.dart';
 import 'package:responsive_framework/responsive_wrapper.dart';
 
-import 'app_block_observer.dart';
+import 'app_bloc_observer.dart';
 import 'config/theme.dart';
 import 'data/models/payment_cubit.dart';
-import 'g1/node_bloc.dart';
+import 'g1/api.dart';
+import 'g1/node_list_cubit.dart';
 import 'shared_prefs.dart';
 import 'ui/screens/skeleton_screen.dart';
 
@@ -70,23 +71,11 @@ void main() async {
     HydratedBloc.storage =
         await HydratedStorage.build(storageDirectory: tmpDir);
   }
-  final NodeBloc nodeBloc = NodeBloc();
 
-  if (nodeBloc.nodeList.length < 10) {
-    // Load nodes from /network/peers
-    nodeBloc.loadNodes();
-  } else {
-    // Try to start with the persisted
+  // Reset hive during developing
+  if (!kReleaseMode) {
+    await HydratedBloc.storage.clear();
   }
-  logger(
-      'Starting with ${nodeBloc.nodeList.length} duniter nodes and ${nodeBloc.cPlusNodeList.length} c+ nodes');
-
-  final Cron cron = Cron();
-  cron.schedule(Schedule.parse('*/45 * * * *'), () async {
-    // Every 45m check for faster node (maybe it something costly in terms of
-    // bandwidth
-    nodeBloc.loadNodes();
-  });
 
   runApp(
     EasyLocalization(
@@ -179,10 +168,11 @@ class _MyAppState extends State<MyApp> {
   @override
   Widget build(BuildContext context) {
     return MultiBlocProvider(
-        providers: <BlocProvider>[
+        providers: <BlocProvider<dynamic>>[
           BlocProvider<PaymentCubit>(
-            create: (BuildContext context) => PaymentCubit(),
-          ),
+              create: (BuildContext context) => PaymentCubit()),
+          BlocProvider<NodeListCubit>(
+              create: (BuildContext context) => NodeListCubit()),
           // Add other BlocProviders here if needed
         ],
         child: ConnectivityAppWrapper(
@@ -205,6 +195,29 @@ class _MyAppState extends State<MyApp> {
             child: _skipIntro ? const SkeletonScreen() : const AppIntro(),
           ),
           builder: (BuildContext buildContext, Widget? widget) {
+            final NodeListCubit nodeCubit =
+                BlocProvider.of<NodeListCubit>(buildContext);
+            final int nDuniterNodes = nodeCubit.duniterNodes.length;
+            final int nCesiumPlusNodes = nodeCubit.cesiumPlusNodes.length;
+            if (nDuniterNodes < 10) {
+              // Load nodes from /network/peers
+              fetchDuniterNodes(nodeCubit);
+            } else {
+              // Try to start with the persisted
+            }
+            logger(
+                'Starting with $nDuniterNodes duniter nodes and $nCesiumPlusNodes c+ nodes');
+
+            final Cron cron = Cron();
+            cron.schedule(Schedule.parse('*/45 * * * *'), () async {
+              // Every 45m check for faster node (maybe it something costly in terms of
+              // bandwidth
+              // nodeCubit.fetchDuniterNodes();
+            });
+            cron.schedule(Schedule.parse('*/90 * * * *'), () async {
+              // nodeCubit.cleanDuniterErrorStats();
+            });
+
             return ResponsiveWrapper.builder(
               ConnectivityWidgetWrapper(
                 message: tr('offline'),
diff --git a/lib/ui/screens/fifth_screen.dart b/lib/ui/screens/fifth_screen.dart
index 4a65bb9e94ef809df62d046c3faa51bf7247033f..653ed1067ae51ff2d6b06c2f491a48f257e622e1 100644
--- a/lib/ui/screens/fifth_screen.dart
+++ b/lib/ui/screens/fifth_screen.dart
@@ -1,7 +1,9 @@
 import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
 
 import '../../g1/export_import.dart';
-import '../../g1/node_bloc.dart';
+import '../../g1/node_list_cubit.dart';
+import '../../g1/node_list_state.dart';
 import '../ui_helpers.dart';
 import '../widgets/fifth_screen/grid_item.dart';
 import '../widgets/fifth_screen/info_card.dart';
@@ -14,56 +16,64 @@ class FifthScreen extends StatelessWidget {
 
   @override
   Widget build(BuildContext context) {
-    // FIXME(vjrj) this is not reactive
-    final String fasterNode = NodeBloc().nodeList.first.url;
-    return Material(
-      color: Theme.of(context).colorScheme.background,
-      child: ListView(
-          padding: const EdgeInsets.symmetric(horizontal: 16),
-          physics: const BouncingScrollPhysics(),
-          children: <Widget>[
-            const Header(text: 'bottom_nav_fifth'),
-            InfoCard(
-                title: 'connected-to', subtitle: fasterNode, icon: Icons.hub),
-            LinkCard(
-                title: 'code_card_title',
-                icon: Icons.code_rounded,
-                url: Uri.parse('https://git.duniter.org/vjrj/ginkgo')),
-            const TextDivider(text: 'key_tools_title'),
-            GridView.count(
-                physics: const NeverScrollableScrollPhysics(),
-                crossAxisCount: 2,
-                childAspectRatio: 2 / 1.15,
-                crossAxisSpacing: 8,
-                mainAxisSpacing: 8,
-                shrinkWrap: true,
-                padding: EdgeInsets.zero,
-                children: <GridItem>[
-                  GridItem(
-                      title: 'export-key',
-                      icon: Icons.download,
-                      onTap: () {
-                        showDialog(
-                          context: context,
-                          builder: (BuildContext context) {
-                            return const ExportImportPage();
-                          },
-                        );
-                      }),
-                  GridItem(
-                      title: 'import-key',
-                      icon: Icons.upload,
-                      onTap: () {
-                        const ExportImportPage();
-                      }),
-                  GridItem(
-                    title: 'copy-your-key',
-                    icon: Icons.copy,
-                    onTap: () => copyPublicKeyToClipboard(context),
-                  )
-                ]),
-            const SizedBox(height: 36),
-          ]),
-    );
+    return BlocBuilder<NodeListCubit, NodeListState>(
+        builder: (BuildContext context, NodeListState state) {
+      return Material(
+        color: Theme.of(context).colorScheme.background,
+        child: ListView(
+            padding: const EdgeInsets.symmetric(horizontal: 16),
+            physics: const BouncingScrollPhysics(),
+            children: <Widget>[
+              const Header(text: 'bottom_nav_fifth'),
+              InfoCard(
+                  title: 'connected-to',
+                  subtitle: context
+                      .read<NodeListCubit>()
+                      .duniterNodes
+                      .first
+                      .url
+                      .replaceFirst(':443', ''),
+                  icon: Icons.hub),
+              LinkCard(
+                  title: 'code_card_title',
+                  icon: Icons.code_rounded,
+                  url: Uri.parse('https://git.duniter.org/vjrj/ginkgo')),
+              const TextDivider(text: 'key_tools_title'),
+              GridView.count(
+                  physics: const NeverScrollableScrollPhysics(),
+                  crossAxisCount: 2,
+                  childAspectRatio: 2 / 1.15,
+                  crossAxisSpacing: 8,
+                  mainAxisSpacing: 8,
+                  shrinkWrap: true,
+                  padding: EdgeInsets.zero,
+                  children: <GridItem>[
+                    GridItem(
+                        title: 'export-key',
+                        icon: Icons.download,
+                        onTap: () {
+                          showDialog(
+                            context: context,
+                            builder: (BuildContext context) {
+                              return const ExportImportPage();
+                            },
+                          );
+                        }),
+                    GridItem(
+                        title: 'import-key',
+                        icon: Icons.upload,
+                        onTap: () {
+                          const ExportImportPage();
+                        }),
+                    GridItem(
+                      title: 'copy-your-key',
+                      icon: Icons.copy,
+                      onTap: () => copyPublicKeyToClipboard(context),
+                    )
+                  ]),
+              const SizedBox(height: 36),
+            ]),
+      );
+    });
   }
 }
diff --git a/lib/ui/screens/first_screen.dart b/lib/ui/screens/first_screen.dart
index 5b2a14ce69ff3d6d5730f75b20e4ead77b0fd220..6eee502ca7d8e3e9b941ab9c48eac8b3087bbd69 100644
--- a/lib/ui/screens/first_screen.dart
+++ b/lib/ui/screens/first_screen.dart
@@ -14,6 +14,8 @@ class FirstScreen extends StatefulWidget {
 }
 
 class _FirstScreenState extends State<FirstScreen> {
+  final ScrollController _controller = ScrollController();
+
   @override
   Widget build(BuildContext context) {
     WidgetsBinding.instance.addPostFrameCallback((_) async {
@@ -21,26 +23,29 @@ class _FirstScreenState extends State<FirstScreen> {
           .showSnackBar(SnackBar(content: Text(tr('demo-desc'))));
     });
     return Material(
-      color: Theme.of(context).colorScheme.background,
-      child: ListView(
-          padding: const EdgeInsets.symmetric(horizontal: 16),
-          physics: const BouncingScrollPhysics(),
-          children: <Widget>[
-            const Header(text: 'credit_card_title'),
-            CreditCard(),
-            const SizedBox(height: 8),
-            Padding(
-              padding: const EdgeInsets.symmetric(horizontal: 24),
-              child: Divider(
-                color:
-                    Theme.of(context).colorScheme.onBackground.withOpacity(.4),
+        color: Theme.of(context).colorScheme.background,
+        child: ListView(
+            padding: const EdgeInsets.symmetric(horizontal: 16),
+            physics: const AlwaysScrollableScrollPhysics(),
+            controller: _controller,
+            shrinkWrap: true,
+            children: <Widget>[
+              const Header(text: 'credit_card_title'),
+              CreditCard(),
+              const SizedBox(height: 8),
+              Padding(
+                padding: const EdgeInsets.symmetric(horizontal: 24),
+                child: Divider(
+                  color: Theme.of(context)
+                      .colorScheme
+                      .onBackground
+                      .withOpacity(.4),
+                ),
               ),
-            ),
-            const SizedBox(height: 10),
-            const PayContactSearchWidget(),
-            const SizedBox(height: 10),
-            const PayForm(),
-          ]),
-    );
+              const SizedBox(height: 10),
+              const PayContactSearchWidget(),
+              const SizedBox(height: 10),
+              const PayForm()
+            ]));
   }
 }
diff --git a/lib/ui/screens/skeleton_screen.dart b/lib/ui/screens/skeleton_screen.dart
index ad0c5f72f5a85474794d2d41c10c1dc3c9913008..c078a0c75e96d1cf2a076990a658bedf70c1fdd6 100644
--- a/lib/ui/screens/skeleton_screen.dart
+++ b/lib/ui/screens/skeleton_screen.dart
@@ -27,6 +27,7 @@ class SkeletonScreen extends StatelessWidget {
         create: (BuildContext context) => BottomNavCubit(),
         child: Scaffold(
           extendBodyBehindAppBar: true,
+          resizeToAvoidBottomInset: true,
           appBar: const AppBarGone(),
 
           /// When switching between tabs this will fade the old
diff --git a/lib/ui/ui_helpers.dart b/lib/ui/ui_helpers.dart
index 98882cb737e53f6516182f7c734d85cae1790428..503ef55e55e0cc77dbba9dc674875ca140937032 100644
--- a/lib/ui/ui_helpers.dart
+++ b/lib/ui/ui_helpers.dart
@@ -49,3 +49,13 @@ Widget avatar(bool hasAvatar, Uint8List? rawAvatar,
       : CircularIcon(
           iconData: Icons.person, backgroundColor: color, iconColor: bgColor);
 }
+
+String humanizeFromToPubKey(String publicAddress, String address) {
+  if (address == publicAddress) {
+    return tr('your_wallet');
+  } else {
+    return humanizePubKey(address);
+  }
+}
+
+String humanizePubKey(String address) => '\u{1F511}${address.substring(0, 8)}';
diff --git a/lib/ui/widgets/first_screen/pay_contact_search_dialog.dart b/lib/ui/widgets/first_screen/pay_contact_search_dialog.dart
index 851de9db9383bce76568d3c96312fc8d0128be17..59b6d51a9347d8a4ed2cac5c571ba22633818388 100644
--- a/lib/ui/widgets/first_screen/pay_contact_search_dialog.dart
+++ b/lib/ui/widgets/first_screen/pay_contact_search_dialog.dart
@@ -9,6 +9,8 @@ import 'package:simple_barcode_scanner/simple_barcode_scanner.dart';
 
 import '../../../data/models/payment_cubit.dart';
 import '../../../g1/api.dart';
+import '../../../g1/node_list_cubit.dart';
+import '../../../g1/node_list_state.dart';
 import '../../ui_helpers.dart';
 import '../loading_box.dart';
 
@@ -26,16 +28,18 @@ class _SearchDialogState extends State<SearchDialog> {
   List<dynamic> _results = <dynamic>[];
   bool _isLoading = false;
 
-  Future<void> _search() async {
+  Future<void> _search(NodeListCubit cubit) async {
     setState(() {
       _isLoading = true;
     });
 
-    final Response response = await searchUser(_searchTerm);
+    final Response response = await searchUser(cubit, _searchTerm);
     setState(() {
       _results = (const JsonDecoder().convert(response.body)
           as Map<String, dynamic>)['results'] as List<dynamic>;
       // debugPrint(_results.toString());
+      // FIXME (vjrj) search in the blockchain and if it's a key, just
+      // put the key as a result
       _isLoading = false;
     });
   }
@@ -43,123 +47,128 @@ class _SearchDialogState extends State<SearchDialog> {
   @override
   Widget build(BuildContext context) {
     bool isFavorite = false;
-    return Scaffold(
-      appBar: AppBar(
-        title: Text(tr('search_user_title')),
-        backgroundColor: Theme.of(context).colorScheme.primary,
-        foregroundColor: Colors.white,
-        actions: <Widget>[
-          IconButton(
-              icon: const Icon(Icons.qr_code_scanner),
-              onPressed: () async {
-                final String? publicKey = await Navigator.push(
-                    context,
-                    MaterialPageRoute<String>(
-                      builder: (BuildContext context) =>
-                          const SimpleBarcodeScannerPage(),
-                    ));
-                setState(() {
-                  if (publicKey is String) {
-                    /* context
-                        .read<PaymentCubit>()
-                        .selectUser(publicKey, nick, avatar); */
-                    // result = res;
-                  }
-                });
-              }),
-          IconButton(
-            icon: const Icon(Icons.close),
-            onPressed: () => Navigator.pop(context),
-          )
-        ],
-      ),
-      body: Padding(
-        padding: const EdgeInsets.all(16.0),
-        child: Column(
-          crossAxisAlignment: CrossAxisAlignment.stretch,
-          children: <Widget>[
-            TextField(
-              controller: _searchController,
-              decoration: InputDecoration(
-                filled: true,
-                fillColor: Colors.white,
-                labelText: tr('search_user'),
-                suffixIcon: IconButton(
-                  icon: const Icon(Icons.search),
-                  onPressed: () {
-                    _search();
-                  },
+    return BlocBuilder<NodeListCubit, NodeListState>(
+        builder: (BuildContext context, NodeListState state) {
+      final NodeListCubit nodeListCubit = context.read<NodeListCubit>();
+      return Scaffold(
+        appBar: AppBar(
+          title: Text(tr('search_user_title')),
+          backgroundColor: Theme.of(context).colorScheme.primary,
+          foregroundColor: Colors.white,
+          actions: <Widget>[
+            IconButton(
+                icon: const Icon(Icons.qr_code_scanner),
+                onPressed: () async {
+                  final String? publicKey = await Navigator.push(
+                      context,
+                      MaterialPageRoute<String>(
+                        builder: (BuildContext context) =>
+                            const SimpleBarcodeScannerPage(),
+                      ));
+                  setState(() {
+                    if (publicKey is String) {
+                      _searchController.text = publicKey;
+                      _search(nodeListCubit);
+                    }
+                  });
+                }),
+            IconButton(
+              icon: const Icon(Icons.close),
+              onPressed: () => Navigator.pop(context),
+            )
+          ],
+        ),
+        body: Padding(
+          padding: const EdgeInsets.all(16.0),
+          child: Column(
+            crossAxisAlignment: CrossAxisAlignment.stretch,
+            children: <Widget>[
+              TextField(
+                controller: _searchController,
+                decoration: InputDecoration(
+                  filled: true,
+                  fillColor: Colors.white,
+                  labelText: tr('search_user'),
+                  suffixIcon: IconButton(
+                    icon: const Icon(Icons.search),
+                    onPressed: () {
+                      _search(nodeListCubit);
+                    },
+                  ),
                 ),
+                onChanged: (String value) {
+                  setState(() {
+                    _searchTerm = value;
+                  });
+                },
+                onSubmitted: (_) {
+                  _search(nodeListCubit);
+                },
               ),
-              onChanged: (String value) {
-                setState(() {
-                  _searchTerm = value;
-                });
-              },
-              onSubmitted: (_) {
-                _search();
-              },
-            ),
-            if (_isLoading)
-              const LoadingBox()
-            else
-              Expanded(
-                child: ListView.builder(
-                    itemCount: _results.length,
-                    itemBuilder: (BuildContext context, int index) {
-                      final String uid =
-                          (((_results[index] as Map<String, dynamic>)['uids']
-                                  as List<dynamic>)[0]
-                              as Map<String, dynamic>)['uid'] as String;
-                      final String pubkey = (_results[index]
-                          as Map<String, dynamic>)['pubkey'] as String;
-                      return FutureBuilder<Uint8List>(
-                          future: getAvatar((_results[index]
-                              as Map<String, dynamic>)['pubkey'] as String),
-                          builder: (BuildContext context,
-                              AsyncSnapshot<Uint8List> snapshot) {
-                            return ListTile(
-                              title: Text(uid),
-                              tileColor: index.isEven
-                                  ? Colors.grey[200]
-                                  : Colors.white,
-                              onTap: () {
-                                context
-                                    .read<PaymentCubit>()
-                                    .selectUser(pubkey, uid, snapshot.data!);
-                                Navigator.pop(context, _results[index]);
-                              },
-                              leading: avatar(
-                                snapshot.hasData,
-                                snapshot.data,
-                                bgColor: index.isEven
-                                    ? defAvatarColor
-                                    : defAvatarBgColor,
-                                color: index.isEven
-                                    ? defAvatarBgColor
-                                    : defAvatarColor,
-                              ),
-                              trailing: IconButton(
-                                icon: Icon(
-                                  isFavorite
-                                      ? Icons.favorite
-                                      : Icons.favorite_border,
-                                  color:
-                                      isFavorite ? Colors.red.shade400 : null,
-                                ),
-                                onPressed: () {
-                                  setState(() {
-                                    isFavorite = !isFavorite;
-                                  });
+              if (_isLoading)
+                const LoadingBox()
+              else
+                Expanded(
+                  child: ListView.builder(
+                      itemCount: _results.length,
+                      itemBuilder: (BuildContext context, int index) {
+                        final String uid =
+                            (((_results[index] as Map<String, dynamic>)['uids']
+                                    as List<dynamic>)[0]
+                                as Map<String, dynamic>)['uid'] as String;
+                        final String pubkey = (_results[index]
+                            as Map<String, dynamic>)['pubkey'] as String;
+                        return FutureBuilder<Uint8List>(
+                            future: getAvatar(
+                                nodeListCubit,
+                                (_results[index]
+                                        as Map<String, dynamic>)['pubkey']
+                                    as String),
+                            builder: (BuildContext context,
+                                AsyncSnapshot<Uint8List> snapshot) {
+                              return ListTile(
+                                title: Text(uid),
+                                tileColor: index.isEven
+                                    ? Colors.grey[200]
+                                    : Colors.white,
+                                onTap: () {
+                                  context
+                                      .read<PaymentCubit>()
+                                      .selectUser(pubkey, uid, snapshot.data!);
+                                  Navigator.pop(context, _results[index]);
                                 },
-                              ),
-                            );
-                          });
-                    }),
-              )
-          ],
+                                leading: avatar(
+                                  snapshot.hasData,
+                                  snapshot.data,
+                                  bgColor: index.isEven
+                                      ? defAvatarColor
+                                      : defAvatarBgColor,
+                                  color: index.isEven
+                                      ? defAvatarBgColor
+                                      : defAvatarColor,
+                                ),
+                                trailing: IconButton(
+                                  icon: Icon(
+                                    isFavorite
+                                        ? Icons.favorite
+                                        : Icons.favorite_border,
+                                    color:
+                                        isFavorite ? Colors.red.shade400 : null,
+                                  ),
+                                  onPressed: () {
+                                    setState(() {
+                                      isFavorite = !isFavorite;
+                                    });
+                                  },
+                                ),
+                              );
+                            });
+                      }),
+                )
+            ],
+          ),
         ),
-      ),
-    );
+      );
+    });
   }
 }
diff --git a/lib/ui/widgets/first_screen/recipient_widget.dart b/lib/ui/widgets/first_screen/recipient_widget.dart
index fc0fb1d598c8d9fe8ae7e77abe5fc1e2541e87ee..be706da5dbcc0b0c43c1150cd143521bd202fa40 100644
--- a/lib/ui/widgets/first_screen/recipient_widget.dart
+++ b/lib/ui/widgets/first_screen/recipient_widget.dart
@@ -31,7 +31,7 @@ class RecipientWidget extends StatelessWidget {
                             ),
                           ),
                           Text(
-                            state.publicKey,
+                            humanizePubKey(state.publicKey),
                             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 84c93f563e85299780de080a29c27a48e3ac1602..940fb8db6cd88ffd0720e5e8b85765d6eabae443 100644
--- a/lib/ui/widgets/fourth_screen/transaction_page.dart
+++ b/lib/ui/widgets/fourth_screen/transaction_page.dart
@@ -1,10 +1,14 @@
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/material.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
 
 import '../../../g1/api.dart';
+import '../../../g1/node_list_cubit.dart';
+import '../../../g1/node_list_state.dart';
 import '../../../g1/transaction.dart';
 import '../../../g1/transaction_parser.dart';
 import '../../../shared_prefs.dart';
+import '../../ui_helpers.dart';
 import '../header.dart';
 import '../loading_box.dart';
 
@@ -37,7 +41,7 @@ class _TransactionsAndBalanceWidgetState
     super.dispose();
   }
 
-  Future<TransactionsAndBalance> _loadTransactions() async {
+  Future<TransactionsAndBalance> _loadTransactions(NodeListCubit cubit) async {
     // carga de datos asíncrona
     // ...
     // disabled, as we have to change the nodes
@@ -49,8 +53,8 @@ class _TransactionsAndBalanceWidgetState
               _balanceAmount = currentBal;
             })); */
 
-    final String txData =
-        await getTxHistory('6DrGg8cftpkgffv4Y4Lse9HSjgc8coEQor3yvMPHAnVH');
+    final String txData = await getTxHistory(
+        cubit, '6DrGg8cftpkgffv4Y4Lse9HSjgc8coEQor3yvMPHAnVH');
     final TransactionsAndBalance result = transactionParser(txData);
     /*  .then((String txData) {
     final TransactionsAndBalance result = transactionParser(txData);
@@ -67,102 +71,100 @@ class _TransactionsAndBalanceWidgetState
   Widget build(BuildContext context) {
     String pubKey = SharedPreferencesHelper().getPubKey();
     pubKey = '6DrGg8cftpkgffv4Y4Lse9HSjgc8coEQor3yvMPHAnVH';
-    return FutureBuilder<TransactionsAndBalance>(
-        future: _loadTransactions(),
-        builder: (BuildContext context,
-            AsyncSnapshot<TransactionsAndBalance> results) {
-          if (results.hasData) {
-            return Stack(children: <Widget>[
-              const Header(text: 'transactions'),
-              ListView.builder(
-                shrinkWrap: true,
-                physics: const BouncingScrollPhysics(),
-                controller: _transactionListController,
-                itemCount: results.data!.transactions.length,
-                itemBuilder: (BuildContext context, int index) {
-                  return ListTile(
-                    title: Text(
-                        tr('transaction_from_to', namedArgs: <String, String>{
-                      'from': humanizeAddress(
-                          pubKey, results.data!.transactions[index].from),
-                      'to': humanizeAddress(
-                          pubKey, results.data!.transactions[index].to)
-                    })),
-                    subtitle:
-                        results.data!.transactions[index].comment.isNotEmpty
-                            ? Text(
-                                results.data!.transactions[index].comment,
-                                style: const TextStyle(
-                                  fontStyle: FontStyle.italic,
-                                ),
-                              )
-                            : null,
-                    tileColor: index.isEven ? Colors.grey[200] : Colors.white,
-                    trailing: Text(
-                        '${results.data!.transactions[index].amount < 0 ? "" : "+"}${(results.data!.transactions[index].amount / 100).toStringAsFixed(2)} Äž1',
-                        style: TextStyle(
-                            color: results.data!.transactions[index].amount < 0
-                                ? Colors.red
-                                : Colors.blue)),
-                  );
-                },
-              ),
-              DraggableScrollableSheet(
-                  initialChildSize: 0.1,
-                  minChildSize: 0.1,
-                  maxChildSize: 0.9,
-                  builder: (BuildContext context,
-                          ScrollController scrollController) =>
-                      Container(
-                        decoration: BoxDecoration(
-                          color: Theme.of(context).colorScheme.inversePrimary,
-                          border: Border.all(
+    return BlocBuilder<NodeListCubit, NodeListState>(
+        builder: (BuildContext context, NodeListState state) => FutureBuilder<
+                TransactionsAndBalance>(
+            future: _loadTransactions(context.read<NodeListCubit>()),
+            builder: (BuildContext context,
+                AsyncSnapshot<TransactionsAndBalance> results) {
+              if (results.hasData) {
+                return Stack(children: <Widget>[
+                  const Header(text: 'transactions'),
+                  ListView.builder(
+                    shrinkWrap: true,
+                    physics: const BouncingScrollPhysics(),
+                    controller: _transactionListController,
+                    itemCount: results.data!.transactions.length,
+                    itemBuilder: (BuildContext context, int index) {
+                      return ListTile(
+                        title: Text(tr('transaction_from_to',
+                            namedArgs: <String, String>{
+                              'from': humanizeFromToPubKey(pubKey,
+                                  results.data!.transactions[index].from),
+                              'to': humanizeFromToPubKey(
+                                  pubKey, results.data!.transactions[index].to)
+                            })),
+                        subtitle:
+                            results.data!.transactions[index].comment.isNotEmpty
+                                ? Text(
+                                    results.data!.transactions[index].comment,
+                                    style: const TextStyle(
+                                      fontStyle: FontStyle.italic,
+                                    ),
+                                  )
+                                : null,
+                        tileColor:
+                            index.isEven ? Colors.grey[200] : Colors.white,
+                        trailing: Text(
+                            '${results.data!.transactions[index].amount < 0 ? "" : "+"}${(results.data!.transactions[index].amount / 100).toStringAsFixed(2)} Äž1',
+                            style: TextStyle(
+                                color:
+                                    results.data!.transactions[index].amount < 0
+                                        ? Colors.red
+                                        : Colors.blue)),
+                      );
+                    },
+                  ),
+                  DraggableScrollableSheet(
+                      initialChildSize: 0.1,
+                      minChildSize: 0.1,
+                      maxChildSize: 0.9,
+                      builder: (BuildContext context,
+                              ScrollController scrollController) =>
+                          Container(
+                            decoration: BoxDecoration(
                               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>[
-                            const Header(text: 'balance'),
-                            Padding(
-                              padding:
-                                  const EdgeInsets.symmetric(vertical: 20.0),
-                              child: Center(
-                                  child: Text(
-                                '${(results.data!.balance / 100).toStringAsFixed(2)} Äž1',
-                                style: const TextStyle(
-                                    fontSize: 36.0,
-                                    fontWeight: FontWeight.bold),
-                              )),
+                              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>[
+                                const Header(text: 'balance'),
+                                Padding(
+                                  padding: const EdgeInsets.symmetric(
+                                      vertical: 20.0),
+                                  child: Center(
+                                      child: Text(
+                                    '${(results.data!.balance / 100).toStringAsFixed(2)} Äž1',
+                                    style: const TextStyle(
+                                        fontSize: 36.0,
+                                        fontWeight: FontWeight.bold),
+                                  )),
+                                ),
+                                /*
                           Expanded(
                               child: BalanceChart(transactions: _transactions)),
                           */
-                          ],
-                        )),
-                      ))
-            ]);
-          } else if (results.hasError) {
-            // FIXME
-            return const Text('Error al cargar los datos.');
-          } else {
-            return const LoadingBox();
-          }
-        });
-  }
-
-  String humanizeAddress(String publicAddress, String address) {
-    if (address == publicAddress) {
-      return tr('your_wallet');
-    } else {
-      return '\u{1F511}${address.substring(0, 8)}';
-    }
+                              ],
+                            )),
+                          ))
+                ]);
+              } else if (results.hasError) {
+                // FIXME
+                return const Text('Error al cargar los datos.');
+              } else {
+                return const LoadingBox();
+              }
+            }));
   }
 }