diff --git a/assets/translations/en.json b/assets/translations/en.json
index 817f5d64feef011bd46f4aca98b519cf3f22e7d7..1e8c5e8bfcc19a354b24c14966b4af42983c5df5 100644
--- a/assets/translations/en.json
+++ b/assets/translations/en.json
@@ -139,5 +139,7 @@
   "nodes_tech_info": "Node List",
   "info_links": "Links",
   "error_importing_wallet": "Error importing wallet",
-  "no_nodes_found": "We couldn't communicate with any node. Please try again later."
+  "no_nodes_found": "We couldn't communicate with any node. Please try again later.",
+  "fetch_tx_error": "Something went wrong while fetching your transactions.",
+  "retry": "RETRY"
 }
diff --git a/assets/translations/es.json b/assets/translations/es.json
index 69a9a3c89edfc3906332a7bfeafdbae56d815730..1e0d9d59c0fc06a14b86c977600d6475d190b8d6 100644
--- a/assets/translations/es.json
+++ b/assets/translations/es.json
@@ -139,5 +139,7 @@
   "nodes_tech_info": "Lista de Nodos",
   "info_links": "Enlaces",
   "error_importing_wallet": "Error importando monedero",
-  "no_nodes_found": "No hemos podido comunicarnos con ningún nodo. Por favor, inténtalo de nuevo más tarde."
+  "no_nodes_found": "No hemos podido comunicarnos con ningún nodo. Por favor, inténtalo de nuevo más tarde.",
+  "fetch_tx_error": "Algo ha ido mal al obtener tus transacciones",
+  "retry": "REINTENTAR"
 }
diff --git a/lib/data/models/transaction.dart b/lib/data/models/transaction.dart
index 8ae72562dac7d4e396d24e72e16ff239dc784934..73d8bf43507bdef2e98d50a71385fbd5f86f9a7a 100644
--- a/lib/data/models/transaction.dart
+++ b/lib/data/models/transaction.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 'contact.dart';
 import 'model_utils.dart';
 import 'transaction_type.dart';
 
@@ -19,6 +20,8 @@ class Transaction extends Equatable {
     required this.amount,
     required this.comment,
     required this.time,
+    required this.fromC,
+    required this.toC,
     this.toAvatar,
     this.toNick,
     this.fromAvatar,
@@ -31,6 +34,8 @@ class Transaction extends Equatable {
   final TransactionType type;
   final String from;
   final String to;
+  final Contact fromC;
+  final Contact toC;
   final double amount;
   final String comment;
   final DateTime time;
diff --git a/lib/data/models/transaction.g.dart b/lib/data/models/transaction.g.dart
index 91fd88c26e332c46f01dc82befc55b6e1c23c979..3f060cac494b27d7218de36f51187100b50b0131 100644
--- a/lib/data/models/transaction.g.dart
+++ b/lib/data/models/transaction.g.dart
@@ -19,6 +19,10 @@ abstract class _$TransactionCWProxy {
 
   Transaction time(DateTime time);
 
+  Transaction fromC(Contact fromC);
+
+  Transaction toC(Contact toC);
+
   Transaction toAvatar(Uint8List? toAvatar);
 
   Transaction toNick(String? toNick);
@@ -40,6 +44,8 @@ abstract class _$TransactionCWProxy {
     double? amount,
     String? comment,
     DateTime? time,
+    Contact? fromC,
+    Contact? toC,
     Uint8List? toAvatar,
     String? toNick,
     Uint8List? fromAvatar,
@@ -71,6 +77,12 @@ class _$TransactionCWProxyImpl implements _$TransactionCWProxy {
   @override
   Transaction time(DateTime time) => this(time: time);
 
+  @override
+  Transaction fromC(Contact fromC) => this(fromC: fromC);
+
+  @override
+  Transaction toC(Contact toC) => this(toC: toC);
+
   @override
   Transaction toAvatar(Uint8List? toAvatar) => this(toAvatar: toAvatar);
 
@@ -98,6 +110,8 @@ class _$TransactionCWProxyImpl implements _$TransactionCWProxy {
     Object? amount = const $CopyWithPlaceholder(),
     Object? comment = const $CopyWithPlaceholder(),
     Object? time = const $CopyWithPlaceholder(),
+    Object? fromC = const $CopyWithPlaceholder(),
+    Object? toC = const $CopyWithPlaceholder(),
     Object? toAvatar = const $CopyWithPlaceholder(),
     Object? toNick = const $CopyWithPlaceholder(),
     Object? fromAvatar = const $CopyWithPlaceholder(),
@@ -128,6 +142,14 @@ class _$TransactionCWProxyImpl implements _$TransactionCWProxy {
           ? _value.time
           // ignore: cast_nullable_to_non_nullable
           : time as DateTime,
+      fromC: fromC == const $CopyWithPlaceholder() || fromC == null
+          ? _value.fromC
+          // ignore: cast_nullable_to_non_nullable
+          : fromC as Contact,
+      toC: toC == const $CopyWithPlaceholder() || toC == null
+          ? _value.toC
+          // ignore: cast_nullable_to_non_nullable
+          : toC as Contact,
       toAvatar: toAvatar == const $CopyWithPlaceholder()
           ? _value.toAvatar
           // ignore: cast_nullable_to_non_nullable
@@ -165,6 +187,8 @@ Transaction _$TransactionFromJson(Map<String, dynamic> json) => Transaction(
       amount: (json['amount'] as num).toDouble(),
       comment: json['comment'] as String,
       time: DateTime.parse(json['time'] as String),
+      fromC: Contact.fromJson(json['fromC'] as Map<String, dynamic>),
+      toC: Contact.fromJson(json['toC'] as Map<String, dynamic>),
       toAvatar: uIntFromList(json['toAvatar']),
       toNick: json['toNick'] as String?,
       fromAvatar: uIntFromList(json['fromAvatar']),
@@ -176,6 +200,8 @@ Map<String, dynamic> _$TransactionToJson(Transaction instance) =>
       'type': _$TransactionTypeEnumMap[instance.type]!,
       'from': instance.from,
       'to': instance.to,
+      'fromC': instance.fromC,
+      'toC': instance.toC,
       'amount': instance.amount,
       'comment': instance.comment,
       'time': instance.time.toIso8601String(),
diff --git a/lib/data/models/transaction_balance_state.dart b/lib/data/models/transaction_balance_state.dart
index ebef45c68283b20f135f1030f33e8322c160735e..6d9c94e15a96c55a2360f45ee2f6f65e30672a89 100644
--- a/lib/data/models/transaction_balance_state.dart
+++ b/lib/data/models/transaction_balance_state.dart
@@ -9,11 +9,13 @@ part 'transaction_balance_state.g.dart';
 @JsonSerializable()
 @CopyWith()
 class TransactionsAndBalanceState extends Equatable {
-  TransactionsAndBalanceState({required this.transactions,
-    required this.balance,
-    required this.lastChecked,
-    DateTime? latestSentNotification,
-    DateTime? latestReceivedNotification})
+  TransactionsAndBalanceState(
+      {required this.transactions,
+      required this.balance,
+      required this.lastChecked,
+      DateTime? latestSentNotification,
+      DateTime? latestReceivedNotification,
+      this.endCursor})
       : latestSentNotification = latestSentNotification ?? DateTime.now(),
         latestReceivedNotification =
             latestReceivedNotification ?? DateTime.now();
@@ -26,16 +28,17 @@ class TransactionsAndBalanceState extends Equatable {
   final DateTime lastChecked;
   final DateTime latestSentNotification;
   final DateTime latestReceivedNotification;
+  final String? endCursor;
 
   Map<String, dynamic> toJson() => _$TransactionsAndBalanceStateToJson(this);
 
   @override
-  List<Object?> get props =>
-      <dynamic>[
+  List<Object?> get props => <dynamic>[
         transactions,
         balance,
         lastChecked,
         latestSentNotification,
-        latestReceivedNotification
+        latestReceivedNotification,
+        endCursor
       ];
 }
diff --git a/lib/data/models/transaction_balance_state.g.dart b/lib/data/models/transaction_balance_state.g.dart
index b1b604c1dd3275ccb6cebd68c7c6f5be4106b0df..78eaf603b71cefb8a28c6caa130e84edc9535da5 100644
--- a/lib/data/models/transaction_balance_state.g.dart
+++ b/lib/data/models/transaction_balance_state.g.dart
@@ -19,6 +19,8 @@ abstract class _$TransactionsAndBalanceStateCWProxy {
   TransactionsAndBalanceState latestReceivedNotification(
       DateTime? latestReceivedNotification);
 
+  TransactionsAndBalanceState endCursor(String? endCursor);
+
   /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `TransactionsAndBalanceState(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
   ///
   /// Usage
@@ -31,6 +33,7 @@ abstract class _$TransactionsAndBalanceStateCWProxy {
     DateTime? lastChecked,
     DateTime? latestSentNotification,
     DateTime? latestReceivedNotification,
+    String? endCursor,
   });
 }
 
@@ -62,6 +65,10 @@ class _$TransactionsAndBalanceStateCWProxyImpl
           DateTime? latestReceivedNotification) =>
       this(latestReceivedNotification: latestReceivedNotification);
 
+  @override
+  TransactionsAndBalanceState endCursor(String? endCursor) =>
+      this(endCursor: endCursor);
+
   @override
 
   /// This function **does support** nullification of nullable fields. All `null` values passed to `non-nullable` fields will be ignored. You can also use `TransactionsAndBalanceState(...).copyWith.fieldName(...)` to override fields one at a time with nullification support.
@@ -76,6 +83,7 @@ class _$TransactionsAndBalanceStateCWProxyImpl
     Object? lastChecked = const $CopyWithPlaceholder(),
     Object? latestSentNotification = const $CopyWithPlaceholder(),
     Object? latestReceivedNotification = const $CopyWithPlaceholder(),
+    Object? endCursor = const $CopyWithPlaceholder(),
   }) {
     return TransactionsAndBalanceState(
       transactions:
@@ -102,6 +110,10 @@ class _$TransactionsAndBalanceStateCWProxyImpl
               ? _value.latestReceivedNotification
               // ignore: cast_nullable_to_non_nullable
               : latestReceivedNotification as DateTime?,
+      endCursor: endCursor == const $CopyWithPlaceholder()
+          ? _value.endCursor
+          // ignore: cast_nullable_to_non_nullable
+          : endCursor as String?,
     );
   }
 }
@@ -131,6 +143,7 @@ TransactionsAndBalanceState _$TransactionsAndBalanceStateFromJson(
       latestReceivedNotification: json['latestReceivedNotification'] == null
           ? null
           : DateTime.parse(json['latestReceivedNotification'] as String),
+      endCursor: json['endCursor'] as String?,
     );
 
 Map<String, dynamic> _$TransactionsAndBalanceStateToJson(
@@ -143,4 +156,5 @@ Map<String, dynamic> _$TransactionsAndBalanceStateToJson(
           instance.latestSentNotification.toIso8601String(),
       'latestReceivedNotification':
           instance.latestReceivedNotification.toIso8601String(),
+      'endCursor': instance.endCursor,
     };
diff --git a/lib/data/models/transaction_cubit.dart b/lib/data/models/transaction_cubit.dart
index 4222bbb1a5e5ed716b2f3c137b2d402f9562122b..8b492d836213533d15162951445a6fa4263da455 100644
--- a/lib/data/models/transaction_cubit.dart
+++ b/lib/data/models/transaction_cubit.dart
@@ -27,6 +27,7 @@ class TransactionsCubit extends HydratedCubit<TransactionsAndBalanceState> {
   String get storagePrefix =>
       kIsWeb ? 'TransactionsCubit' : super.storagePrefix;
 
+/*
   void addTransaction(Transaction transaction) {
     final TransactionsAndBalanceState currentState = state;
     final List<Transaction> newTransactions =
@@ -39,17 +40,19 @@ class TransactionsCubit extends HydratedCubit<TransactionsAndBalanceState> {
   void updateTransactions(
       List<Transaction> newTransactions, double newBalance) {
     emit(state.copyWith(transactions: newTransactions, balance: newBalance));
-  }
+  }*/
 
-  Future<void> fetchTransactions(NodeListCubit cubit, {int retries = 5}) async {
+  Future<List<Transaction>> fetchTransactions(NodeListCubit cubit,
+      {int retries = 5, int? pageSize, String? cursor}) async {
     Tuple2<Map<String, dynamic>?, Node> txDataResult;
     bool success = false;
 
     for (int attempt = 0; attempt < retries; attempt++) {
-      txDataResult =
-          await gvaHistoryAndBalance(SharedPreferencesHelper().getPubKey());
+      txDataResult = await gvaHistoryAndBalance(
+          SharedPreferencesHelper().getPubKey(), pageSize, cursor);
       final Node node = txDataResult.item2;
-      logger('Loading transactions using $node --------------------');
+      logger(
+          'Loading transactions using $node (pageSize: $pageSize, cursor: $cursor) --------------------');
 
       if (txDataResult.item1 == null) {
         logger(
@@ -61,7 +64,7 @@ class TransactionsCubit extends HydratedCubit<TransactionsAndBalanceState> {
 
       final Map<String, dynamic> txData = txDataResult.item1!;
       final TransactionsAndBalanceState newState =
-          transactionsGvaParser(txData, state);
+          await transactionsGvaParser(txData, state);
 
       if (newState.balance < 0) {
         logger('Warning: Negative balance in node ${txDataResult.item2}');
@@ -74,6 +77,7 @@ class TransactionsCubit extends HydratedCubit<TransactionsAndBalanceState> {
           'Last received notification: ${newState.latestReceivedNotification.toIso8601String()})}');
       logger(
           'Last sent notification: ${newState.latestSentNotification.toIso8601String()})}');
+
       emit(newState);
       for (final Transaction tx in newState.transactions.reversed) {
         if (tx.type == TransactionType.received &&
@@ -97,11 +101,13 @@ class TransactionsCubit extends HydratedCubit<TransactionsAndBalanceState> {
           emit(newState.copyWith(latestSentNotification: tx.time));
         }
       }
+      return newState.transactions;
     }
     if (!success) {
-      logger('Failed to get transactions after $retries attempts');
-      return;
+      throw Exception('Failed to get transactions after $retries attempts');
     }
+    // This should not be executed
+    return <Transaction>[];
   }
 
   @override
diff --git a/lib/g1/api.dart b/lib/g1/api.dart
index 44be87a8c5a037a5529377fdf98e02b44af35743..6d828b4c0079f13dc8f31517e65aaed58e5ae1b7 100644
--- a/lib/g1/api.dart
+++ b/lib/g1/api.dart
@@ -30,7 +30,7 @@ final String currency = currencyDotEnv.isEmpty ? 'g1' : currencyDotEnv;
 
 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 {
@@ -58,7 +58,7 @@ Future<Response> searchCPlusUser(String searchTerm) async {
       '/user/profile/_search?q=title:$searchTermLower OR issuer:$searchTerm OR title:$searchTermCapitalized OR title:$searchTerm';
 
   final Response response =
-      await requestCPlusWithRetry(query, retryWith404: false);
+  await requestCPlusWithRetry(query, retryWith404: false);
   return response;
 }
 
@@ -69,12 +69,12 @@ Future<Contact> getProfile(String pubKey,
         '/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 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);
     if (!onlyCPlusProfile) {
       // This penalize the gva rate limit
@@ -111,7 +111,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) {
@@ -138,11 +138,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);
@@ -155,14 +155,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';
@@ -257,16 +257,18 @@ Future<void> _fetchGvaNodes({bool force = false}) async {
   NodeManager().loading = false;
 }
 
-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>[];
@@ -278,14 +280,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'] == currency)
+      (peer as Map<String, dynamic>)['currency'] == currency)
           .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();
@@ -293,7 +295,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];
@@ -305,7 +307,9 @@ Future<List<Node>> _fetchDuniterNodesFromPeers(NodeType type) async {
                   final NodeCheck nodeCheck = await _pingNode(endpoint, type);
                   final Duration latency = nodeCheck.latency;
                   logger(
-                      'Evaluating node: $endpoint, latency ${latency.inMicroseconds} currentBlock: ${nodeCheck.currentBlock}');
+                      'Evaluating node: $endpoint, latency ${latency
+                          .inMicroseconds} currentBlock: ${nodeCheck
+                          .currentBlock}');
                   final Node node = Node(
                       url: endpoint,
                       latency: latency.inMicroseconds,
@@ -337,7 +341,8 @@ Future<List<Node>> _fetchDuniterNodesFromPeers(NodeType type) async {
       }
     }
     logger(
-        'Fetched ${lNodes.length} ${type.name} nodes ordered by latency ${lNodes.isNotEmpty ? '(first: ${lNodes.first.url})' : '(zero nodes)'}');
+        'Fetched ${lNodes.length} ${type.name} nodes ordered by latency ${lNodes
+            .isNotEmpty ? '(first: ${lNodes.first.url})' : '(zero nodes)'}');
   } catch (e, stacktrace) {
     await Sentry.captureException(e, stackTrace: stacktrace);
     logger('General error in fetch ${type.name} nodes: $e');
@@ -389,7 +394,8 @@ 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) {
     await Sentry.captureException(e, stackTrace: stacktrace);
     logger('General error in fetch ${type.name}: $e');
@@ -406,7 +412,8 @@ Future<NodeCheck> _pingNode(String node, NodeType type) async {
   int currentBlock = 0;
   Duration latency;
   try {
-    final Stopwatch stopwatch = Stopwatch()..start();
+    final Stopwatch stopwatch = Stopwatch()
+      ..start();
     if (type == NodeType.duniter) {
       final Response response = await http
           .get(Uri.parse('$node/blockchain/current'))
@@ -415,7 +422,7 @@ Future<NodeCheck> _pingNode(String node, NodeType type) async {
       latency = stopwatch.elapsed;
       if (response.statusCode == 200) {
         final Map<String, dynamic> json =
-            jsonDecode(response.body) as Map<String, dynamic>;
+        jsonDecode(response.body) as Map<String, dynamic>;
         currentBlock = json['number'] as int;
       } else {
         latency = wrongNodeDuration;
@@ -424,7 +431,7 @@ Future<NodeCheck> _pingNode(String node, NodeType type) async {
       // see: http://g1.data.e-is.pro/network/peering
       await http
           .get(Uri.parse('$node/network/peering'))
-          // Decrease http timeout during ping
+      // Decrease http timeout during ping
           .timeout(timeout);
       stopwatch.stop();
       latency = stopwatch.elapsed;
@@ -440,7 +447,8 @@ Future<NodeCheck> _pingNode(String node, NodeType type) async {
       latency = balance >= 0 ? stopwatch.elapsed : wrongNodeDuration;
     }
     logger(
-        'Ping tested in node $node ($type), latency ${latency.inMicroseconds}, current block $currentBlock');
+        'Ping tested in node $node ($type), latency ${latency
+            .inMicroseconds}, current block $currentBlock');
     return NodeCheck(latency: latency, currentBlock: currentBlock);
   } catch (e) {
     // Handle exception when node is unavailable etc
@@ -469,8 +477,8 @@ 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 {
+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)
@@ -479,19 +487,24 @@ Future<http.Response> _requestWithRetry(
     nodes.addAll(type == NodeType.duniter
         ? defaultDuniterNodes
         : type == NodeType.cesiumPlus
-            ? defaultCesiumPlusNodes
-            : defaultGvaNodes);
+        ? defaultCesiumPlusNodes
+        : defaultGvaNodes);
   }
-  for (final int timeout in <int>[10, 25]) {
+  for (final int timeout in <int>[10]) {
+    // only one timeout for now
     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(Duration(seconds: timeout));
-        final int endTime = DateTime.now().millisecondsSinceEpoch;
+        await http.get(url).timeout(Duration(seconds: timeout));
+        final int endTime = DateTime
+            .now()
+            .millisecondsSinceEpoch;
         final int newLatency = endTime - startTime;
         if (!kReleaseMode) {
           logger('response.statusCode: ${response.statusCode}');
@@ -536,11 +549,10 @@ Future<http.Response> _requestWithRetry(
       'Cannot make the request to any of the ${nodes.length} nodes');
 }
 
-Future<String> pay(
-    {required String to,
-    required double amount,
-    String? comment,
-    bool? useMempool}) async {
+Future<String> pay({required String to,
+  required double amount,
+  String? comment,
+  bool? useMempool}) async {
   try {
     final SelectedGvaNode selected = getGvaNode();
 
@@ -549,7 +561,8 @@ Future<String> pay(
       final Gva gva = Gva(node: nodeUrl);
       final CesiumWallet wallet = await SharedPreferencesHelper().getWallet();
       logger(
-          'Trying $nodeUrl to send $amount to $to with comment ${comment ?? ''}');
+          'Trying $nodeUrl to send $amount to $to with comment ${comment ??
+              ''}');
 
       final String response = await gva.pay(
           recipient: to,
@@ -599,15 +612,18 @@ class SelectedGvaNode {
 
 String proxyfyNode(String nodeUrl) {
   final String url = inProduction && kIsWeb
-      ? '${window.location.protocol}//${window.location.hostname}/proxy/${nodeUrl.replaceFirst('https://', '').replaceFirst('http://', '')}/'
+      ? '${window.location.protocol}//${window.location
+      .hostname}/proxy/${nodeUrl.replaceFirst('https://', '').replaceFirst(
+      'http://', '')}/'
       : nodeUrl;
   return url;
 }
 
-Future<Tuple2<Map<String, dynamic>?, Node>> gvaHistoryAndBalance(
-    String pubKey) async {
+Future<Tuple2<Map<String, dynamic>?, Node>> gvaHistoryAndBalance(String pubKey,
+    [int? pageSize, String? cursor]) async {
+  logger('Get tx history (page size: $pageSize: cursor $cursor)');
   return gvaFunctionWrapper<Map<String, dynamic>>(
-      pubKey, (Gva gva) => gva.history(pubKey));
+      pubKey, (Gva gva) => gva.history(pubKey, pageSize, cursor));
 }
 
 Future<Tuple2<double?, Node>> gvaBalance(String pubKey) async {
@@ -619,19 +635,23 @@ Future<Tuple2<String?, Node>> gvaNick(String pubKey) async {
       pubKey, (Gva gva) => gva.getUsername(pubKey));
 }
 
-Future<Tuple2<T?, Node>> gvaFunctionWrapper<T>(
-    String pubKey, Future<T?> Function(Gva) specificFunction) async {
+Future<Tuple2<T?, Node>> gvaFunctionWrapper<T>(String pubKey,
+    Future<T?> Function(Gva) specificFunction) async {
   final List<Node> nodes = _getBestGvaNodes();
   for (int i = 0; i < nodes.length; i++) {
     final Node node = nodes[i];
     try {
       final Gva gva = Gva(node: proxyfyNode(node.url));
       logger('Trying to use gva ${node.url}');
-      final T? result = await specificFunction(gva);
+      final T? result = await specificFunction(gva)
+          .timeout(const Duration(seconds: 10), onTimeout: () {
+        throw Exception('Timeout');
+      });
+      logger('Returning results from ${node.url}');
       return Tuple2<T?, Node>(result, node);
     } catch (e) {
-      // await Sentry.captureMessage(
-      //     'Error trying to use gva node ${node.url} $e');
+      await Sentry.captureMessage(
+          'Error trying to use gva node ${node.url} $e');
       logger('Error trying ${node.url} $e');
       increaseNodeErrors(NodeType.gva, node);
       continue;
@@ -647,8 +667,8 @@ List<Node> _getBestGvaNodes() {
       .toList();
   final int maxCurrentBlock = fnodes.fold(
       0,
-      (int max, Node node) =>
-          node.currentBlock > max ? node.currentBlock : max);
+          (int max, Node node) =>
+      node.currentBlock > max ? node.currentBlock : max);
   final List<Node> nodes = fnodes
       .where((Node node) => node.currentBlock == maxCurrentBlock)
       .toList();
@@ -657,7 +677,8 @@ List<Node> _getBestGvaNodes() {
     // Fallback
     nodes.addAll(defaultGvaNodes);
   }
-  nodes.shuffle();
+  // Don't shuffle for now
+  // nodes.shuffle();
   return nodes;
 }
 
diff --git a/lib/g1/transaction_parser.dart b/lib/g1/transaction_parser.dart
index d5c5ab9747a95d799509e5eb828fc5e236378625..6399ee71d5d16b298cd62c9a684f217a5eeb78d2 100644
--- a/lib/g1/transaction_parser.dart
+++ b/lib/g1/transaction_parser.dart
@@ -1,12 +1,14 @@
 import 'dart:convert';
 
+import '../data/models/contact.dart';
 import '../data/models/transaction.dart';
 import '../data/models/transaction_balance_state.dart';
 import '../data/models/transaction_type.dart';
+import '../ui/contacts_cache.dart';
 
 final RegExp exp = RegExp(r'\((.*?)\)');
 
-TransactionsAndBalanceState transactionParser(String txData) {
+Future<TransactionsAndBalanceState> transactionParser(String txData) async {
   final Map<String, dynamic> parsedTxData =
       json.decode(txData) as Map<String, dynamic>;
   final String pubKey = parsedTxData['pubkey'] as String;
@@ -39,12 +41,17 @@ TransactionsAndBalanceState transactionParser(String txData) {
       logger('Timestamp: $timestamp');
       logger('Fecha: $txDate');
     } */
+    final Contact fromC = await ContactsCache().getContact(address2!);
+    final Contact toC = await ContactsCache().getContact(address1!);
+
     tx.insert(
         0,
         Transaction(
             type: type,
-            from: address2!,
-            to: address1!,
+            from: address2,
+            fromC: fromC,
+            to: address1,
+            toC: toC,
             amount: pubKey == address2 ? -amount : amount,
             comment: comment,
             time: txDate));
@@ -53,8 +60,8 @@ TransactionsAndBalanceState transactionParser(String txData) {
       transactions: tx, balance: balance, lastChecked: DateTime.now());
 }
 
-TransactionsAndBalanceState transactionsGvaParser(
-    Map<String, dynamic> txData, TransactionsAndBalanceState state) {
+Future<TransactionsAndBalanceState> transactionsGvaParser(
+    Map<String, dynamic> txData, TransactionsAndBalanceState state) async {
   // Balance
   final dynamic rawBalance = txData['balance'];
   final double amount = rawBalance != null
@@ -69,30 +76,34 @@ TransactionsAndBalanceState transactionsGvaParser(
   final Map<String, dynamic> both =
       txsHistoryBc['both'] as Map<String, dynamic>;
   final List<dynamic> edges = both['edges'] as List<dynamic>;
+  final Map<String, dynamic> pageInfo =
+      both['pageInfo'] as Map<String, dynamic>;
   final List<Transaction> txs = <Transaction>[];
   for (final dynamic edgeRaw in edges) {
     final Transaction tx =
-        _transactionGvaParser(edgeRaw as Map<String, dynamic>);
+        await _transactionGvaParser(edgeRaw as Map<String, dynamic>);
     txs.add(tx);
   }
   final List<dynamic> receiving = txsHistoryMp['receiving'] as List<dynamic>;
   final List<dynamic> sending = txsHistoryMp['sending'] as List<dynamic>;
   for (final dynamic receiveRaw in receiving) {
-    final Transaction tx = _txGvaParse(
+    final Transaction tx = await _txGvaParse(
         receiveRaw as Map<String, dynamic>, TransactionType.receiving);
     txs.insert(0, tx);
   }
   for (final dynamic sendingRaw in sending) {
-    final Transaction tx = _txGvaParse(
+    final Transaction tx = await _txGvaParse(
         sendingRaw as Map<String, dynamic>, TransactionType.sending);
     txs.insert(0, tx);
   }
-
   return state.copyWith(
-      transactions: txs, balance: amount, lastChecked: DateTime.now());
+      transactions: txs,
+      balance: amount,
+      lastChecked: DateTime.now(),
+      endCursor: pageInfo['endCursor'] as String?);
 }
 
-Transaction _transactionGvaParser(Map<String, dynamic> edge) {
+Future<Transaction> _transactionGvaParser(Map<String, dynamic> edge) {
   final Map<String, dynamic> parsedTxData = edge;
   // Direction
   final String direction = parsedTxData['direction'] as String;
@@ -103,7 +114,8 @@ Transaction _transactionGvaParser(Map<String, dynamic> edge) {
   return _txGvaParse(tx, type);
 }
 
-Transaction _txGvaParse(Map<String, dynamic> tx, TransactionType type) {
+Future<Transaction> _txGvaParse(
+    Map<String, dynamic> tx, TransactionType type) async {
   final List<dynamic> issuers = tx['issuers'] as List<dynamic>;
   final List<dynamic> outputs = tx['outputs'] as List<dynamic>;
   final String from = issuers[0] as String;
@@ -121,11 +133,14 @@ Transaction _txGvaParse(Map<String, dynamic> tx, TransactionType type) {
   }
   // Comment
   final String comment = tx['comment'] as String;
-
+  final Contact fromC = await ContactsCache().getContact(from);
+  final Contact toC = await ContactsCache().getContact(to!);
   return Transaction(
     type: type,
     from: from,
-    to: to!,
+    fromC: fromC,
+    to: to,
+    toC: toC,
     amount: amount,
     comment: comment,
     time: time,
diff --git a/lib/ui/widgets/fourth_screen/transaction_item.dart b/lib/ui/widgets/fourth_screen/transaction_item.dart
index 182e622ae34970fdd01b5e5b7e7e7eb81b3e9fc4..d157e517b2ca598bb2adec5f0f3f4ddd2c4121a9 100644
--- a/lib/ui/widgets/fourth_screen/transaction_item.dart
+++ b/lib/ui/widgets/fourth_screen/transaction_item.dart
@@ -3,14 +3,12 @@ import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:flutter_slidable/flutter_slidable.dart';
 
-import '../../../data/models/contact.dart';
 import '../../../data/models/contact_cubit.dart';
 import '../../../data/models/transaction.dart';
 import '../../../data/models/transaction_balance_state.dart';
 import '../../../data/models/transaction_cubit.dart';
 import '../../../data/models/transaction_type.dart';
 import '../../../shared_prefs.dart';
-import '../../contacts_cache.dart';
 import '../../ui_helpers.dart';
 
 class TransactionListItem extends StatelessWidget {
@@ -26,27 +24,16 @@ class TransactionListItem extends StatelessWidget {
   final int index;
 
   @override
-  Widget build(BuildContext context) =>
-      BlocBuilder<TransactionsCubit, TransactionsAndBalanceState>(
-          builder: (BuildContext context,
-                  TransactionsAndBalanceState transBalanceState) =>
-              FutureBuilder<List<Contact>>(
-                  future: _fetchContact(pubKey, transaction),
-                  builder: (BuildContext context,
-                      AsyncSnapshot<List<Contact>> snapshot) {
-                    if (snapshot.hasData) {
-                      return _buildTransactionItem(context, snapshot.data!);
-                    } else if (snapshot.hasError) {
-                      return Text('Error ${snapshot.error}');
-                    } else {
-                      return _buildTransactionItem(context, <Contact>[
-                        Contact(pubKey: transaction.from),
-                        Contact(pubKey: transaction.to)
-                      ]);
-                    }
-                  }));
+  Widget build(BuildContext context) {
+    // logger('TransactionListItem build');
+    return BlocBuilder<TransactionsCubit, TransactionsAndBalanceState>(
+        builder: (BuildContext context,
+                TransactionsAndBalanceState transBalanceState) =>
+            _buildTransactionItem(context, transaction));
+  }
 
-  Slidable _buildTransactionItem(BuildContext context, List<Contact> contacts) {
+  Slidable _buildTransactionItem(
+      BuildContext context, Transaction transaction) {
     IconData? icon;
     Color? iconColor;
     String statusText;
@@ -83,8 +70,9 @@ class TransactionListItem extends StatelessWidget {
           children: <SlidableAction>[
             SlidableAction(
               onPressed: (BuildContext c) {
-                contactsCubit.addContact(
-                    transaction.isIncoming ? contacts[0] : contacts[1]);
+                contactsCubit.addContact(transaction.isIncoming
+                    ? transaction.fromC
+                    : transaction.toC);
                 ScaffoldMessenger.of(context).showSnackBar(
                   SnackBar(
                     content: Text(tr('contact_added')),
@@ -142,9 +130,10 @@ class TransactionListItem extends StatelessWidget {
                             child: Text(
                               tr('transaction_from_to',
                                   namedArgs: <String, String>{
-                                    'from':
-                                        humanizeContact(myPubKey, contacts[0]),
-                                    'to': humanizeContact(myPubKey, contacts[1])
+                                    'from': humanizeContact(
+                                        myPubKey, transaction.fromC),
+                                    'to': humanizeContact(
+                                        myPubKey, transaction.toC)
                                   }),
                               style: const TextStyle(
                                 fontSize: 14.0,
@@ -194,16 +183,4 @@ class TransactionListItem extends StatelessWidget {
           ),
         ));
   }
-
-  Future<List<Contact>> _fetchContact(
-      String pubKey, Transaction transaction) async {
-    final Contact myContact = await ContactsCache().getContact(pubKey);
-    if (pubKey == transaction.from) {
-      final Contact to = await ContactsCache().getContact(transaction.to);
-      return <Contact>[myContact, to];
-    } else {
-      final Contact from = await ContactsCache().getContact(transaction.from);
-      return <Contact>[from, myContact];
-    }
-  }
 }
diff --git a/lib/ui/widgets/fourth_screen/transaction_page.dart b/lib/ui/widgets/fourth_screen/transaction_page.dart
index f5f874bb846a9bbb5f07a6951a705f737caf008e..a746ebc614be3293a325084683acc66a265ac536 100644
--- a/lib/ui/widgets/fourth_screen/transaction_page.dart
+++ b/lib/ui/widgets/fourth_screen/transaction_page.dart
@@ -1,14 +1,17 @@
 import 'package:backdrop/backdrop.dart';
+import 'package:easy_debounce/easy_throttle.dart';
 import 'package:easy_localization/easy_localization.dart';
 import 'package:flutter/foundation.dart';
 import 'package:flutter/material.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
 
 import '../../../data/models/node_list_cubit.dart';
 import '../../../data/models/transaction.dart';
 import '../../../data/models/transaction_balance_state.dart';
 import '../../../data/models/transaction_cubit.dart';
 import '../../../shared_prefs.dart';
+import '../../logger.dart';
 import '../../ui_helpers.dart';
 import 'transaction_chart.dart';
 import 'transaction_item.dart';
@@ -29,37 +32,65 @@ class _TransactionsAndBalanceWidgetState
   late NodeListCubit nodeListCubit;
   late TransactionsCubit transCubit;
   bool isLoading = false;
+  static const int _pageSize = 20;
+
+  final PagingController<String?, Transaction> _pagingController =
+  PagingController<String?, Transaction>(firstPageKey: null);
 
   @override
   void initState() {
-    super.initState();
-    _transScrollController.addListener(_scrollListener);
     // Remove in the future
     transCubit = context.read<TransactionsCubit>();
     nodeListCubit = context.read<NodeListCubit>();
-    transCubit.fetchTransactions(nodeListCubit);
+    _pagingController.addPageRequestListener((String? cursor) {
+      EasyThrottle.throttle('my-throttler-$cursor', const Duration(seconds: 1),
+              () => _fetchPage(cursor),
+          onAfter:
+              () {} // <-- Optional callback, called after the duration has passed
+      );
+    });
+    _pagingController.addStatusListener((PagingStatus status) {
+      if (status == PagingStatus.subsequentPageError) {
+        ScaffoldMessenger.of(context).showSnackBar(
+          SnackBar(
+            content: Text(tr('fetch_tx_error')),
+            action: SnackBarAction(
+              label: tr('retry'),
+              onPressed: () => _pagingController.retryLastFailedRequest(),
+            ),
+          ),
+        );
+      }
+    });
+    super.initState();
   }
 
-  @override
-  void dispose() {
-    _transScrollController.removeListener(_scrollListener);
-    _transScrollController.dispose();
-    super.dispose();
-  }
+  Future<void> _fetchPage(String? cursor) async {
+    logger('Fetching from transaction page with cursor $cursor');
+    try {
+      final List<Transaction> newItems = await transCubit.fetchTransactions(
+          nodeListCubit,
+          cursor: cursor, pageSize: _pageSize);
 
-  Future<void> _scrollListener() async {
-    if (_transScrollController.offset == 0) {
-      _refreshIndicatorKey.currentState?.show();
+      final bool isLastPage = newItems.length < _pageSize;
+      if (isLastPage) {
+        _pagingController.appendLastPage(newItems);
+      } else {
+        final String? nextCursor = transCubit.state.endCursor;
+        _pagingController.appendPage(newItems, nextCursor);
+      }
+    } catch (error) {
+      _pagingController.error = error;
     }
   }
 
-  Future<void> _refreshTransactions() async {
-    return transCubit.fetchTransactions(nodeListCubit);
+  @override
+  void dispose() {
+    _transScrollController.dispose();
+    _pagingController.dispose();
+    super.dispose();
   }
 
-  final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
-      GlobalKey<RefreshIndicatorState>();
-
   @override
   Widget build(BuildContext context) {
     final String myPubKey = SharedPreferencesHelper().getPubKey();
@@ -69,25 +100,33 @@ 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(
-                icon: const Icon(Icons.refresh),
-                onPressed: () {
-                  _refreshIndicatorKey.currentState?.show();
-                  // _refreshTransactions();
-                },
-              ),
+                  icon: const Icon(Icons.refresh),
+                  onPressed: () =>
+                      EasyThrottle.throttle(
+                          'my-throttler-refresh',
+                          const Duration(seconds: 1),
+                              () => _pagingController.refresh(),
+                          onAfter:
+                              () {} // <-- Optional callback, called after the duration has passed
+                      )),
               // const BackdropToggleButton(),
               LayoutBuilder(
                   builder: (BuildContext lContext,
-                          BoxConstraints constraints) =>
+                      BoxConstraints constraints) =>
                       IconButton(
-                          // icon: const Icon(Icons.account_balance_wallet),
+                        // icon: const Icon(Icons.account_balance_wallet),
                           icon: const Icon(Icons.savings),
                           onPressed: () {
-                            if (Backdrop.of(lContext).isBackLayerConcealed) {
+                            if (Backdrop
+                                .of(lContext)
+                                .isBackLayerConcealed) {
                               Backdrop.of(lContext).revealBackLayer();
                             } else {
                               Backdrop.of(lContext).concealBackLayer();
@@ -98,158 +137,132 @@ class _TransactionsAndBalanceWidgetState
           ),
           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),
-                  )),
                 ),
-                if (!kReleaseMode) TransactionChart(transactions: transactions)
-              ],
-            )),
-          )),
+                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(
+                            transactions: transactions)
+                      ],
+                    )),
+              )),
           subHeader: BackdropSubHeader(
             title: Text(tr('transactions')),
             divider: Divider(
-              color: Theme.of(context).colorScheme.surfaceVariant,
+              color: Theme
+                  .of(context)
+                  .colorScheme
+                  .surfaceVariant,
               height: 0,
             ),
           ),
-          frontLayer: Center(
-            child: Column(
-                crossAxisAlignment: CrossAxisAlignment.start,
-                children: <Widget>[
-                  /* Container(
-                      /* color: Theme
-                      .of(context)
-                      .colorScheme
-                      .surfaceVariant, */
-                      height: 70,
-                      width: double.infinity,
-                      child: const Padding(
-                          padding: EdgeInsets.symmetric(horizontal: 16),
-                          child: Header(text: 'transactions'))), */
-                  Expanded(
-                      child: 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(
-                            key: _refreshIndicatorKey,
-                            color: Colors.white,
-                            backgroundColor:
-                                Theme.of(context).colorScheme.primary,
-                            strokeWidth: 4.0,
-                            onRefresh: () async {
-                              return _refreshTransactions();
-                            },
-                            // Pull from top to show refresh indicator.
-                            child: ListView.builder(
-                              physics: const AlwaysScrollableScrollPhysics(),
-                              shrinkWrap: true,
-                              controller: _transScrollController,
-                              itemCount: transactions.length,
-                              // Size of elements
-                              // itemExtent: 100,
-                              itemBuilder: (BuildContext context, int index) {
-                                return TransactionListItem(
-                                  pubKey: myPubKey,
-                                  index: index,
-                                  transaction: transactions[index],
-                                );
-                                /*
-                                   Slidable(
+          frontLayer: RefreshIndicator(
+            color: Colors.white,
+            backgroundColor: Theme
+                .of(context)
+                .colorScheme
+                .primary,
+            strokeWidth: 4.0,
+            onRefresh: () =>
+            Future<void>.sync(
+                  () => _pagingController.refresh(),
+            ),
+            child: CustomScrollView(
+                shrinkWrap: true,
+                // scrollDirection: Axis.vertical,
+                slivers: <Widget>[
+                  // Some widget before all,
+                  PagedSliverList<String?, Transaction>(
+                      pagingController: _pagingController,
+                      // separatorBuilder: (BuildContext context, int index) =>
+                      //    const Divider(),
+                      builderDelegate: PagedChildBuilderDelegate<Transaction>(
+                          animateTransitions: true,
+                          transitionDuration: const Duration(milliseconds: 500),
+                          itemBuilder: (BuildContext context, Transaction tx,
+                              int index) {
+                            return TransactionListItem(
+                              pubKey: myPubKey,
+                              index: index,
+                              transaction: tx,
+                            );
+                          },
+                          noItemsFoundIndicatorBuilder: (_) =>
+                              Padding(
+                                  padding:
+                                  const EdgeInsets.symmetric(horizontal: 20),
+                                  child:
+                                  Center(child: Text(tr('no_transactions'))))))
+
+                  /*
 
-                                      // Specify a key if the Slidable is dismissible.
-                                      key: const ValueKey<int>(0),
-                                      // The end action pane is the one at the right or the bottom side.
-                                      endActionPane: ActionPane(
-                                        motion: const ScrollMotion(),
-                                        children: <SlidableAction>[
-                                          SlidableAction(
-                                            onPressed: (BuildContext c) {
-                                              _addContact(transactions, index,
-                                                  myPubKey, contactsCubit);
-                                              // FIXME i18n
-                                              ScaffoldMessenger.of(context)
-                                                  .showSnackBar(
-                                                SnackBar(
-                                                  content:
-                                                      Text(tr('contact_added')),
-                                                ),
-                                              );
-                                            },
-                                            backgroundColor:
-                                                Theme.of(context).primaryColor,
-                                            foregroundColor: Colors.white,
-                                            icon: Icons.contacts,
-                                            label: tr('add_contact'),
-                                          ),
-                                        ],
-                                      ),
-                                      child: ListTile(
-                                        title: Text(tr('transaction_from_to',
-                                            namedArgs: <String, String>{
-                                              'from': humanizeFromToPubKey(
-                                                  myPubKey,
-                                                  transactions[index].from),
-                                              'to': humanizeFromToPubKey(
-                                                  myPubKey,
-                                                  transactions[index].to)
-                                            })),
-                                        subtitle: Column(
-                                            crossAxisAlignment:
-                                                CrossAxisAlignment.start,
-                                            children: <Widget>[
-                                              if (transactions[index]
-                                                  .comment
-                                                  .isNotEmpty)
-                                                Text(
-                                                  transactions[index].comment,
-                                                  style: const TextStyle(
-                                                    fontStyle: FontStyle.italic,
-                                                  ),
-                                                ),
-                                              Text(humanizeTime(
-                                                  transactions[index].time,
-                                                  context.locale.toString())!)
-                                            ]),
-                                        tileColor: tileColor(index, context),
-                                        trailing: Text(
-                                            '${transactions[index].amount < 0 ? "" : "+"}${(transactions[index].amount / 100).toStringAsFixed(2)} Äž1',
-                                            style: TextStyle(
-                                                color:
-                                                    transactions[index].amount <
-                                                            0
-                                                        ? Colors.red
-                                                        : Colors.blue)),
-                                      ));
-                                      */
+          Center(
+            child:
+                Column(crossAxisAlignment: CrossAxisAlignment.start, children: <
+                    Widget>[
+              Expanded(
+                  child: 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(
+                              key: _refreshIndicatorKey,
+                              color: Colors.white,
+                              backgroundColor:
+                                  Theme.of(context).colorScheme.primary,
+                              strokeWidth: 4.0,
+                              onRefresh: () async {
+                                return _refreshTransactions();
                               },
-                            )),
-                  ))
+                              // Pull from top to show refresh indicator.
+                              child: PagedListView<String?, Transaction>(
+                                  pagingController: _pagingController,
+                                  builderDelegate:
+                                      PagedChildBuilderDelegate<Transaction>(
+                                    animateTransitions: true,
+                                    noMoreItemsIndicatorBuilder: (_) =>
+                                        const Text('No more transactions'),
+                                    itemBuilder: (BuildContext context,
+                                        Transaction tx, int index) {
+                                      return TransactionListItem(
+                                        pubKey: myPubKey,
+                                        index: index,
+                                        transaction: tx,
+                                      );
+                                    },
+                                  ))))) */
                 ]),
           ));
     });
diff --git a/pubspec.lock b/pubspec.lock
index 696e5e73ada37cf3b3e66a7bc370e345d3452d72..e8149bb6c136344070130c3699aff64909eb80c9 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -237,10 +237,10 @@ packages:
     dependency: transitive
     description:
       name: connectivity_plus
-      sha256: "8875e8ed511a49f030e313656154e4bbbcef18d68dfd32eb853fac10bce48e96"
+      sha256: d73575bb66216738db892f72ba67dc478bd3b5490fbbcf43644b57645eabc822
       url: "https://pub.dev"
     source: hosted
-    version: "3.0.3"
+    version: "3.0.4"
   connectivity_plus_platform_interface:
     dependency: transitive
     description:
@@ -253,10 +253,10 @@ packages:
     dependency: "direct main"
     description:
       name: connectivity_wrapper
-      sha256: "467557b12d1468c15fd518fbfddc2147d2069a6cad9fc340d687178e4d9a10e3"
+      sha256: f7c3cefecc57ee290e5e49e7fb9e6a09b5839614b5e30bceebddb9d7925d054c
       url: "https://pub.dev"
     source: hosted
-    version: "1.1.2"
+    version: "1.1.3"
   convert:
     dependency: transitive
     description:
@@ -344,6 +344,14 @@ packages:
       relative: true
     source: path
     version: "0.1.6"
+  easy_debounce:
+    dependency: "direct main"
+    description:
+      name: easy_debounce
+      sha256: f082609cfb8f37defb9e37fc28bc978c6712dedf08d4c5a26f820fa10165a236
+      url: "https://pub.dev"
+    source: hosted
+    version: "2.0.3"
   easy_localization:
     dependency: "direct main"
     description:
@@ -558,10 +566,10 @@ packages:
     dependency: "direct main"
     description:
       name: flutter_svg
-      sha256: "12006889e2987c549c4c1ec1a5ba4ec4b24d34d2469ee5f9476c926dcecff266"
+      sha256: f991fdb1533c3caeee0cdc14b04f50f0c3916f0dbcbc05237ccbe4e3c6b93f3f
       url: "https://pub.dev"
     source: hosted
-    version: "2.0.4"
+    version: "2.0.5"
   flutter_test:
     dependency: "direct dev"
     description: flutter
@@ -732,6 +740,14 @@ packages:
       url: "https://pub.dev"
     source: hosted
     version: "9.1.0"
+  infinite_scroll_pagination:
+    dependency: "direct main"
+    description:
+      name: infinite_scroll_pagination
+      sha256: "9517328f4e373f08f57dbb11c5aac5b05554142024d6b60c903f3b73476d52db"
+      url: "https://pub.dev"
+    source: hosted
+    version: "3.2.0"
   injector:
     dependency: transitive
     description:
@@ -752,10 +768,10 @@ packages:
     dependency: "direct main"
     description:
       name: introduction_screen
-      sha256: "0767902260d69655b4d4bd9a88619cac06728eaf13c346cf7132cfb0809debf6"
+      sha256: f194ae655a84b945a2aedb7961d09948d789fc91088efb032666112923bcbc1e
       url: "https://pub.dev"
     source: hosted
-    version: "3.1.7"
+    version: "3.1.8"
   io:
     dependency: transitive
     description:
@@ -896,10 +912,10 @@ packages:
     dependency: "direct main"
     description:
       name: package_info_plus
-      sha256: "8df5ab0a481d7dc20c0e63809e90a588e496d276ba53358afc4c4443d0a00697"
+      sha256: cbff87676c352d97116af6dbea05aa28c4d65eb0f6d5677a520c11a69ca9a24d
       url: "https://pub.dev"
     source: hosted
-    version: "3.0.3"
+    version: "3.1.0"
   package_info_plus_platform_interface:
     dependency: transitive
     description:
@@ -936,18 +952,18 @@ packages:
     dependency: transitive
     description:
       name: path_provider_android
-      sha256: "019f18c9c10ae370b08dce1f3e3b73bc9f58e7f087bb5e921f06529438ac0ae7"
+      sha256: da97262be945a72270513700a92b39dd2f4a54dad55d061687e2e37a6390366a
       url: "https://pub.dev"
     source: hosted
-    version: "2.0.24"
+    version: "2.0.25"
   path_provider_foundation:
     dependency: transitive
     description:
       name: path_provider_foundation
-      sha256: "818b2dc38b0f178e0ea3f7cf3b28146faab11375985d815942a68eee11c2d0f7"
+      sha256: ad4c4d011830462633f03eb34445a45345673dfd4faf1ab0b4735fbd93b19183
       url: "https://pub.dev"
     source: hosted
-    version: "2.2.1"
+    version: "2.2.2"
   path_provider_linux:
     dependency: transitive
     description:
@@ -1016,10 +1032,10 @@ packages:
     dependency: transitive
     description:
       name: pointycastle
-      sha256: c3120a968135aead39699267f4c74bc9a08e4e909e86bc1b0af5bfd78691123c
+      sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
       url: "https://pub.dev"
     source: hosted
-    version: "3.7.2"
+    version: "3.7.3"
   pool:
     dependency: transitive
     description:
@@ -1112,10 +1128,10 @@ packages:
     dependency: transitive
     description:
       name: sentry
-      sha256: dda9b82b4a8a7d778786453ac6d57fb566fb9d12425d79628d295e7c6793c89f
+      sha256: "9b005f502dfd84ca085dfa660dd4f16f7891c1b4b6300fda7c340330e61525d6"
       url: "https://pub.dev"
     source: hosted
-    version: "7.4.1"
+    version: "7.4.2"
   sentry_dart_plugin:
     dependency: "direct main"
     description:
@@ -1128,34 +1144,34 @@ packages:
     dependency: "direct main"
     description:
       name: sentry_flutter
-      sha256: "9082c7c13e07c43c0ecb91558549166191e4dd513a6ec6810b1ae3bc072683fc"
+      sha256: bb537f6fedbe4c634aaf1a430d5efce8329e355b57dc2e0c224d8a2a3e54c4ac
       url: "https://pub.dev"
     source: hosted
-    version: "7.4.1"
+    version: "7.4.2"
   sentry_logging:
     dependency: "direct main"
     description:
       name: sentry_logging
-      sha256: "9cc16800fb4e2d773adfebf27ed9e79fa69e4f5ccdeae1537fdd1fc6c83c67d4"
+      sha256: af01baed88036a004e7efccc637627eb8641e9936dde0f27253fc83df2f79647
       url: "https://pub.dev"
     source: hosted
-    version: "7.4.1"
+    version: "7.4.2"
   share_plus:
     dependency: "direct main"
     description:
       name: share_plus
-      sha256: "8c6892037b1824e2d7e8f59d54b3105932899008642e6372e5079c6939b4b625"
+      sha256: "692261968a494e47323dcc8bc66d8d52e81bc27cb4b808e4e8d7e8079d4cc01a"
       url: "https://pub.dev"
     source: hosted
-    version: "6.3.1"
+    version: "6.3.2"
   share_plus_platform_interface:
     dependency: transitive
     description:
       name: share_plus_platform_interface
-      sha256: "82ddd4ab9260c295e6e39612d4ff00390b9a7a21f1bb1da771e2f232d80ab8a1"
+      sha256: "0c6e61471bd71b04a138b8b588fa388e66d8b005e6f2deda63371c5c505a0981"
       url: "https://pub.dev"
     source: hosted
-    version: "3.2.0"
+    version: "3.2.1"
   shared_preferences:
     dependency: "direct main"
     description:
@@ -1168,18 +1184,18 @@ packages:
     dependency: transitive
     description:
       name: shared_preferences_android
-      sha256: "8304d8a1f7d21a429f91dee552792249362b68a331ac5c3c1caf370f658873f6"
+      sha256: "7fa90471a6875d26ad78c7e4a675874b2043874586891128dc5899662c97db46"
       url: "https://pub.dev"
     source: hosted
-    version: "2.1.0"
+    version: "2.1.2"
   shared_preferences_foundation:
     dependency: transitive
     description:
       name: shared_preferences_foundation
-      sha256: cf2a42fb20148502022861f71698db12d937c7459345a1bdaa88fc91a91b3603
+      sha256: "0c1c16c56c9708aa9c361541a6f0e5cc6fc12a3232d866a687a7b7db30032b07"
       url: "https://pub.dev"
     source: hosted
-    version: "2.2.0"
+    version: "2.2.1"
   shared_preferences_linux:
     dependency: transitive
     description:
@@ -1233,6 +1249,14 @@ packages:
     description: flutter
     source: sdk
     version: "0.0.99"
+  sliver_tools:
+    dependency: transitive
+    description:
+      name: sliver_tools
+      sha256: ccdc502098a8bfa07b3ec582c282620031481300035584e1bb3aca296a505e8c
+      url: "https://pub.dev"
+    source: hosted
+    version: "0.2.10"
   source_gen:
     dependency: transitive
     description:
@@ -1293,10 +1317,10 @@ packages:
     dependency: transitive
     description:
       name: synchronized
-      sha256: "33b31b6beb98100bf9add464a36a8dd03eb10c7a8cf15aeec535e9b054aaf04b"
+      sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60"
       url: "https://pub.dev"
     source: hosted
-    version: "3.0.1"
+    version: "3.1.0"
   system_info2:
     dependency: transitive
     description:
@@ -1381,10 +1405,10 @@ packages:
     dependency: transitive
     description:
       name: url_launcher_android
-      sha256: dd729390aa936bf1bdf5cd1bc7468ff340263f80a2c4f569416507667de8e3c8
+      sha256: a52628068d282d01a07cd86e6ba99e497aa45ce8c91159015b2416907d78e411
       url: "https://pub.dev"
     source: hosted
-    version: "6.0.26"
+    version: "6.0.27"
   url_launcher_ios:
     dependency: transitive
     description:
@@ -1405,10 +1429,10 @@ packages:
     dependency: transitive
     description:
       name: url_launcher_macos
-      sha256: "0ef2b4f97942a16523e51256b799e9aa1843da6c60c55eefbfa9dbc2dcb8331a"
+      sha256: "91ee3e75ea9dadf38036200c5d3743518f4a5eb77a8d13fda1ee5764373f185e"
       url: "https://pub.dev"
     source: hosted
-    version: "3.0.4"
+    version: "3.0.5"
   url_launcher_platform_interface:
     dependency: transitive
     description:
@@ -1445,26 +1469,26 @@ packages:
     dependency: transitive
     description:
       name: vector_graphics
-      sha256: "4cf8e60dbe4d3a693d37dff11255a172594c0793da542183cbfe7fe978ae4aaa"
+      sha256: ea8d3fc7b2e0f35de38a7465063ecfcf03d8217f7962aa2a6717132cb5d43a79
       url: "https://pub.dev"
     source: hosted
-    version: "1.1.4"
+    version: "1.1.5"
   vector_graphics_codec:
     dependency: transitive
     description:
       name: vector_graphics_codec
-      sha256: "278ad5f816f58b1967396d1f78ced470e3e58c9fe4b27010102c0a595c764468"
+      sha256: a5eaa5d19e123ad4f61c3718ca1ed921c4e6254238d9145f82aa214955d9aced
       url: "https://pub.dev"
     source: hosted
-    version: "1.1.4"
+    version: "1.1.5"
   vector_graphics_compiler:
     dependency: transitive
     description:
       name: vector_graphics_compiler
-      sha256: "0bf61ad56e6fd6688a2865d3ceaea396bc6a0a90ea0d7ad5049b1b76c09d6163"
+      sha256: "15edc42f7eaa478ce854eaf1fbb9062a899c0e4e56e775dd73b7f4709c97c4ca"
       url: "https://pub.dev"
     source: hosted
-    version: "1.1.4"
+    version: "1.1.5"
   vector_math:
     dependency: transitive
     description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 7ede0d9f22cd10bfc896b19b6931d8e6e1d0a1f4..fc49686731cf62506e5df8331e1c9aa3822a3d82 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -76,6 +76,8 @@ dependencies:
   jsqr: ^0.1.4
   web_browser_detect: ^2.0.3
   tuple: ^2.0.1
+  infinite_scroll_pagination: ^3.2.0
+  easy_debounce: ^2.0.3
 
 dev_dependencies:
   flutter_test:
diff --git a/test/transactions_test.dart b/test/transactions_test.dart
index eaf1046e2d8771ee1925eb1fba55913328a2ccc3..b55cfd56af464a6bcde37fd7a94d2182c46e3cb1 100644
--- a/test/transactions_test.dart
+++ b/test/transactions_test.dart
@@ -16,7 +16,7 @@ void main() {
   test('Test parsing', () async {
     TestWidgetsFlutterBinding.ensureInitialized();
     final String txData = await rootBundle.loadString('assets/tx.json');
-    final TransactionsAndBalanceState result = transactionParser(txData);
+    final TransactionsAndBalanceState result = await transactionParser(txData);
     expect(result.balance, equals(6700));
     final List<Transaction> txs = result.transactions;
     for (final Transaction tx in txs) {
@@ -33,7 +33,7 @@ void main() {
   test('Test gva history parsing', () async {
     TestWidgetsFlutterBinding.ensureInitialized();
     final String txData = await rootBundle.loadString('assets/gva-tx.json');
-    final TransactionsAndBalanceState result = transactionsGvaParser(
+    final TransactionsAndBalanceState result = await transactionsGvaParser(
         (jsonDecode(txData) as Map<String, dynamic>)['data']
             as Map<String, dynamic>,
         emptyState);
@@ -84,7 +84,7 @@ void main() {
     }
   }
 }''';
-    final TransactionsAndBalanceState emptyResult = transactionsGvaParser(
+    final TransactionsAndBalanceState emptyResult = await transactionsGvaParser(
         (jsonDecode(emptyTx) as Map<String, dynamic>)['data']
             as Map<String, dynamic>,
         emptyState);