From 48034b06a33ce4ca7c32aaa34330fb2235efa60a Mon Sep 17 00:00:00 2001 From: Beast Date: Tue, 10 Mar 2026 16:28:15 +0800 Subject: [PATCH 1/2] feat: adjust query for fetch scheduled transfer --- .../src/services/chain_history_service.dart | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/quantus_sdk/lib/src/services/chain_history_service.dart b/quantus_sdk/lib/src/services/chain_history_service.dart index db1a1eac..59cecd26 100644 --- a/quantus_sdk/lib/src/services/chain_history_service.dart +++ b/quantus_sdk/lib/src/services/chain_history_service.dart @@ -34,26 +34,19 @@ class ChainHistoryService { final String _scheduledTransfersQuery = r''' query ScheduledTransfersByAccounts($accounts: [String!]!, $limit: Int!, $offset: Int!) { - events( + accountEvents( limit: $limit offset: $offset where: { - reversibleTransfer: { - AND: [ - { status_eq: SCHEDULED }, - { - OR: [ - { from: { id_in: $accounts } }, - { to: { id_in: $accounts } } - ] - } - ] - } + account: { + id_in: $accounts + }, + scheduledReversibleTransfer_isNull: false } - orderBy: reversibleTransfer_scheduledAt_DESC + orderBy: timestamp_DESC ) { id - reversibleTransfer { + scheduledReversibleTransfer { id amount timestamp @@ -65,7 +58,6 @@ query ScheduledTransfersByAccounts($accounts: [String!]!, $limit: Int!, $offset: } txId scheduledAt - status block { height hash @@ -402,12 +394,12 @@ query SearchPendingTransaction( throw Exception('GraphQL errors: ${responseBody['errors']}'); } - final List? events = responseBody['data']?['events']; + final List? events = responseBody['data']?['accountEvents']; if (events == null) { return []; } - final result = events.map((event) => ReversibleTransferEvent.fromJson(event['reversibleTransfer'])).toList(); + final result = events.map((event) => ReversibleTransferEvent.fromJson(event['scheduledReversibleTransfer'])).toList(); return result; } catch (e, stackTrace) { From 985dc9784a2e019dd564fd74b53ac5adab95a3e4 Mon Sep 17 00:00:00 2001 From: Beast Date: Tue, 10 Mar 2026 18:57:48 +0800 Subject: [PATCH 2/2] feat: finish adjusting history service --- mobile-app/lib/models/pagination_state.dart | 20 +- .../unified_pagination_controller.dart | 25 +- ...eversible_transfer_monitoring_service.dart | 40 +- .../lib/services/transaction_service.dart | 2 +- .../lib/src/models/sorted_transactions.dart | 10 +- .../lib/src/models/transaction_event.dart | 68 ++- .../src/services/chain_history_service.dart | 396 ++++++++---------- 7 files changed, 260 insertions(+), 301 deletions(-) diff --git a/mobile-app/lib/models/pagination_state.dart b/mobile-app/lib/models/pagination_state.dart index 9ea8ab90..3d433a33 100644 --- a/mobile-app/lib/models/pagination_state.dart +++ b/mobile-app/lib/models/pagination_state.dart @@ -4,10 +4,7 @@ import 'package:quantus_sdk/quantus_sdk.dart'; class PaginationState { final List items; final List reversibleTransfers; - final int transfersOffset; - final int reversibleOffset; - final int rewardsOffset; - final int scheduledOffset; + final int offset; final bool hasMore; final bool isFetching; final Object? error; @@ -16,10 +13,7 @@ class PaginationState { PaginationState({ required this.items, required this.reversibleTransfers, - this.transfersOffset = 0, - this.reversibleOffset = 0, - this.rewardsOffset = 0, - this.scheduledOffset = 0, + this.offset = 0, required this.hasMore, required this.isFetching, this.error, @@ -32,10 +26,7 @@ class PaginationState { PaginationState copyWith({ List? items, List? reversibleTransfers, - int? transfersOffset, - int? reversibleOffset, - int? rewardsOffset, - int? scheduledOffset, + int? offset, bool? hasMore, bool? isFetching, Object? error, @@ -44,10 +35,7 @@ class PaginationState { return PaginationState( items: items ?? this.items, reversibleTransfers: reversibleTransfers ?? this.reversibleTransfers, - transfersOffset: transfersOffset ?? this.transfersOffset, - reversibleOffset: reversibleOffset ?? this.reversibleOffset, - rewardsOffset: rewardsOffset ?? this.rewardsOffset, - scheduledOffset: scheduledOffset ?? this.scheduledOffset, + offset: offset ?? this.offset, hasMore: hasMore ?? this.hasMore, isFetching: isFetching ?? this.isFetching, error: error ?? this.error, diff --git a/mobile-app/lib/providers/controllers/unified_pagination_controller.dart b/mobile-app/lib/providers/controllers/unified_pagination_controller.dart index 3fdd0f76..b8c6844c 100644 --- a/mobile-app/lib/providers/controllers/unified_pagination_controller.dart +++ b/mobile-app/lib/providers/controllers/unified_pagination_controller.dart @@ -76,10 +76,7 @@ class UnifiedPaginationController extends StateNotifier { .fetchAllTransactionTypes( accountIds: targetAccountIds, limit: _limit, - transfersOffset: state.transfersOffset, - reversibleOffset: state.reversibleOffset, - rewardsOffset: state.rewardsOffset, - scheduledOffset: state.scheduledOffset, + offset: state.offset, ); final newItems = newTransactions.otherTransfers; @@ -87,10 +84,7 @@ class UnifiedPaginationController extends StateNotifier { state = state.copyWith( items: [...state.items, ...newItems], reversibleTransfers: [...state.reversibleTransfers, ...newTransactions.reversibleTransfers], - transfersOffset: newTransactions.nextTransfersOffset, - reversibleOffset: newTransactions.nextReversibleOffset, - rewardsOffset: newTransactions.nextRewardsOffset, - scheduledOffset: newTransactions.nextScheduledOffset, + offset: newTransactions.nextOffset, hasMore: newTransactions.hasMore, isFetching: false, error: null, @@ -159,10 +153,7 @@ class UnifiedPaginationController extends StateNotifier { state = state.copyWith( items: newItems, reversibleTransfers: newTransactions.reversibleTransfers, - transfersOffset: newTransactions.nextTransfersOffset, - reversibleOffset: newTransactions.nextReversibleOffset, - rewardsOffset: newTransactions.nextRewardsOffset, - scheduledOffset: newTransactions.nextScheduledOffset, + offset: newTransactions.nextOffset, hasMore: newTransactions.hasMore, error: null, stackTrace: null, @@ -175,16 +166,16 @@ class UnifiedPaginationController extends StateNotifier { /// Update a reversible transfer status to executed inline without full /// refresh. /// Moves the transfer from reversibleTransfers to the top of items list. - void updateReversibleTransferToExecuted(String extrinsicHash, ReversibleTransferStatus newStatus) { - print('Updating reversible transfer to executed: $extrinsicHash'); + void updateReversibleTransferToExecuted(String txId, ReversibleTransferStatus newStatus) { + print('Updating reversible transfer to executed: $txId'); // Find the reversible transfer with the matching hash final reversibleTransfer = state.reversibleTransfers - .where((transfer) => transfer.extrinsicHash == extrinsicHash) + .where((transfer) => transfer.txId == txId) .firstOrNull; if (reversibleTransfer == null) { - print('Reversible transfer not found for hash: $extrinsicHash'); + print('Reversible transfer not found for txId: $txId'); return; } @@ -205,7 +196,7 @@ class UnifiedPaginationController extends StateNotifier { // Remove from reversible transfers final updatedReversibleTransfers = state.reversibleTransfers - .where((transfer) => transfer.extrinsicHash != extrinsicHash) + .where((transfer) => transfer.txId != txId) .toList(); // Add executed transfer to the top of items list diff --git a/mobile-app/lib/services/reversible_transfer_monitoring_service.dart b/mobile-app/lib/services/reversible_transfer_monitoring_service.dart index 42bbf5b9..897c6ae9 100644 --- a/mobile-app/lib/services/reversible_transfer_monitoring_service.dart +++ b/mobile-app/lib/services/reversible_transfer_monitoring_service.dart @@ -142,16 +142,10 @@ class ReversibleTransferMonitoringService { // Check if this specific transaction was executed using its hash // ignore: lines_longer_than_80_chars - final transactions = await historyService.fetchTransactionsByTransactionHash( - transactionHashes: [transfer.extrinsicHash!], - limit: 5, - ); - - // Look for the executed version of this transfer - final status = _checkIfTransferWasExecuted(transfer, transactions); + final transaction = await historyService.fetchExecutedTransactionByTxId(txId: transfer.txId); - if (status != null) { - print('Reversible transfer finished: ${transfer.id} $status'); + if (transaction != null) { + print('Reversible transfer finished: ${transfer.id} ${transaction.status}'); // Stop polling for this transfer _stopExecutionPolling(transfer.id); @@ -160,7 +154,7 @@ class ReversibleTransferMonitoringService { // to executed list for both global and filtered controllers _ref .read(paginationControllerProvider.notifier) - .updateReversibleTransferToExecuted(transfer.extrinsicHash!, status); + .updateReversibleTransferToExecuted(transfer.txId, transaction.status); _ref.read(pendingCancellationsProvider.notifier).removePendingCancellation(transfer.id); // Also update filtered controllers for affected accounts so @@ -169,7 +163,7 @@ class ReversibleTransferMonitoringService { for (final accountId in affectedAccounts) { _ref .read(filteredPaginationControllerProviderFamily(AccountIdListCache.get([accountId])).notifier) - .updateReversibleTransferToExecuted(transfer.extrinsicHash!, status); + .updateReversibleTransferToExecuted(transfer.txId, transaction.status); } // Also update filtered controllers for all accounts so @@ -177,7 +171,7 @@ class ReversibleTransferMonitoringService { final accountIds = _ref.read(accountsProvider).value?.map((a) => a.accountId).toList() ?? []; _ref .read(filteredPaginationControllerProviderFamily(AccountIdListCache.get(accountIds)).notifier) - .updateReversibleTransferToExecuted(transfer.extrinsicHash!, status); + .updateReversibleTransferToExecuted(transfer.txId, transaction.status); // Refresh balance since transfer execution changes balance _ref.invalidate(balanceProviderFamily); @@ -190,28 +184,6 @@ class ReversibleTransferMonitoringService { } } - ReversibleTransferStatus? _checkIfTransferWasExecuted( - ReversibleTransferEvent originalTransfer, - List transactions, - ) { - // Look for a reversible transfer with same txId/extrinsicHash but EXECUTED status - for (final historyTx in transactions) { - if (historyTx is ReversibleTransferEvent) { - final matchesHash = historyTx.extrinsicHash == originalTransfer.extrinsicHash; - - if (matchesHash && historyTx.status != ReversibleTransferStatus.SCHEDULED) { - print( - 'Found executed reversible transfer:' - ' ${historyTx.id} (status: ${historyTx.status})', - ); - return historyTx.status; - } - } - } - - return null; - } - void _stopExecutionPolling(String transferId) { final poller = _executionPollers.remove(transferId); poller?.cancel(); diff --git a/mobile-app/lib/services/transaction_service.dart b/mobile-app/lib/services/transaction_service.dart index 7bc3aa45..39ef757b 100644 --- a/mobile-app/lib/services/transaction_service.dart +++ b/mobile-app/lib/services/transaction_service.dart @@ -106,7 +106,7 @@ class TransactionService { if (txType == EventType.TRANSFER.name) { event = TransferEvent.fromJson(json); } else if (txType == EventType.REVERSIBLE_TRANSFER.name) { - event = ReversibleTransferEvent.fromJson(json); + event = ReversibleTransferEvent.fromNotificationJson(json); } else if (txType == EventType.PENDING_TRANSACTION.name) { event = PendingTransactionEvent.fromJson(json); } diff --git a/quantus_sdk/lib/src/models/sorted_transactions.dart b/quantus_sdk/lib/src/models/sorted_transactions.dart index df4b2113..6241b212 100644 --- a/quantus_sdk/lib/src/models/sorted_transactions.dart +++ b/quantus_sdk/lib/src/models/sorted_transactions.dart @@ -4,19 +4,13 @@ import 'package:quantus_sdk/src/models/transaction_event.dart'; class SortedTransactionsList { final List reversibleTransfers; final List otherTransfers; - final int nextTransfersOffset; - final int nextReversibleOffset; - final int nextRewardsOffset; - final int nextScheduledOffset; + final int nextOffset; final bool hasMore; const SortedTransactionsList({ required this.reversibleTransfers, required this.otherTransfers, - this.nextTransfersOffset = 0, - this.nextReversibleOffset = 0, - this.nextRewardsOffset = 0, - this.nextScheduledOffset = 0, + this.nextOffset = 0, this.hasMore = false, }); diff --git a/quantus_sdk/lib/src/models/transaction_event.dart b/quantus_sdk/lib/src/models/transaction_event.dart index aa6f3819..d030e484 100644 --- a/quantus_sdk/lib/src/models/transaction_event.dart +++ b/quantus_sdk/lib/src/models/transaction_event.dart @@ -102,10 +102,12 @@ class ReversibleTransferEvent extends TransactionEvent { required super.blockHash, }); - factory ReversibleTransferEvent.fromJson(Map json) { + + factory ReversibleTransferEvent.fromNotificationJson(Map json) { final block = json['block'] as Map; final blockHeight = block['height'] as int; final blockHash = block['hash'] as String? ?? ''; + return ReversibleTransferEvent( id: json['id'] as String, from: json['from']?['id'] as String? ?? '', @@ -121,6 +123,70 @@ class ReversibleTransferEvent extends TransactionEvent { ); } + factory ReversibleTransferEvent.fromScheduledJson(Map json) { + final block = json['block'] as Map; + final blockHeight = block['height'] as int; + final blockHash = block['hash'] as String? ?? ''; + + return ReversibleTransferEvent( + id: json['id'] as String, + from: json['from']?['id'] as String? ?? '', + to: json['to']?['id'] as String? ?? '', + amount: BigInt.parse(json['amount'] as String), + timestamp: DateTime.parse(json['timestamp'] as String), + txId: json['txId'] as String, + status: ReversibleTransferStatus.SCHEDULED, + scheduledAt: DateTime.parse(json['scheduledAt'] as String), + extrinsicHash: json['extrinsicHash'] as String?, + blockNumber: blockHeight, + blockHash: blockHash, + ); + } + + factory ReversibleTransferEvent.fromCancelledJson(Map json) { + final block = json['block'] as Map; + final blockHeight = block['height'] as int; + final blockHash = block['hash'] as String? ?? ''; + + final scheduledTransfer = json['scheduledTransfer'] as Map; + + return ReversibleTransferEvent( + id: json['id'] as String, + from: scheduledTransfer['from']?['id'] as String? ?? '', + to: scheduledTransfer['to']?['id'] as String? ?? '', + amount: BigInt.parse(scheduledTransfer['amount'] as String), + timestamp: DateTime.parse(json['timestamp'] as String), + txId: json['txId'] as String, + status: ReversibleTransferStatus.CANCELLED, + scheduledAt: DateTime.parse(scheduledTransfer['scheduledAt'] as String), + extrinsicHash: json['extrinsicHash'] as String?, + blockNumber: blockHeight, + blockHash: blockHash, + ); + } + + factory ReversibleTransferEvent.fromExecutedJson(Map json) { + final block = json['block'] as Map; + final blockHeight = block['height'] as int; + final blockHash = block['hash'] as String? ?? ''; + + final scheduledTransfer = json['scheduledTransfer'] as Map; + + return ReversibleTransferEvent( + id: json['id'] as String, + from: scheduledTransfer['from']?['id'] as String? ?? '', + to: scheduledTransfer['to']?['id'] as String? ?? '', + amount: BigInt.parse(scheduledTransfer['amount'] as String), + timestamp: DateTime.parse(json['timestamp'] as String), + txId: json['txId'] as String, + status: ReversibleTransferStatus.EXECUTED, + scheduledAt: DateTime.parse(scheduledTransfer['scheduledAt'] as String), + extrinsicHash: json['extrinsicHash'] as String?, + blockNumber: blockHeight, + blockHash: blockHash, + ); + } + Map toJson() { return { 'id': id, diff --git a/quantus_sdk/lib/src/services/chain_history_service.dart b/quantus_sdk/lib/src/services/chain_history_service.dart index 59cecd26..522a98bd 100644 --- a/quantus_sdk/lib/src/services/chain_history_service.dart +++ b/quantus_sdk/lib/src/services/chain_history_service.dart @@ -68,17 +68,9 @@ query ScheduledTransfersByAccounts($accounts: [String!]!, $limit: Int!, $offset: } }'''; - // GraphQL query to fetch transactions by their hash - final String _transactionsByHashQuery = r''' -query TransactionsByHash($transactionHashes: [String!]!, $limit: Int!, $offset: Int!) { - events( - limit: $limit - offset: $offset - where: { - extrinsicHash_in: $transactionHashes - } - orderBy: timestamp_DESC - ) { + final String _accountEventsQuery = r''' +query AccountEvents($accounts: [String!]!, $limit: Int!, $offset: Int!) { + accountEvents(limit: $limit, offset: $offset, where: {account: {id_in: $accounts}, balanceEvent_isNull: true}, orderBy: timestamp_DESC) { id transfer { id @@ -98,7 +90,7 @@ query TransactionsByHash($transactionHashes: [String!]!, $limit: Int!, $offset: timestamp fee } - reversibleTransfer { + scheduledReversibleTransfer { id amount timestamp @@ -110,56 +102,97 @@ query TransactionsByHash($transactionHashes: [String!]!, $limit: Int!, $offset: } txId scheduledAt - status block { height hash } extrinsicHash + } + executedReversibleTransfer { + block { + height + hash + } + txId timestamp + id + scheduledTransfer { + amount + from { + id + } + to { + id + } + scheduledAt + } + } + cancelledReversibleTransfer { + block { + height + hash + } + txId + timestamp + id + extrinsicHash + cancelledBy { + id + } + scheduledTransfer { + amount + from { + id + } + to { + id + } + scheduledAt + } + } + minerReward { + id + reward + timestamp + miner { + id + } + block { + height + hash + } } - extrinsicHash - } -}'''; - - final String _transfersByAccountsQuery = r''' -query TransfersByAccounts($accounts: [String!]!, $limit: Int!, $offset: Int!) { - events( - limit: $limit, offset: $offset, - where: { extrinsicHash_isNull: false, transfer: { OR: [{ from: { id_in: $accounts } }, { to: { id_in: $accounts } }] } }, - orderBy: timestamp_DESC - ) { - id - transfer { id amount timestamp from { id } to { id } block { height hash } extrinsicHash timestamp fee } - extrinsicHash } -}'''; - - final String _reversibleByAccountsQuery = r''' -query ReversibleByAccounts($accounts: [String!]!, $limit: Int!, $offset: Int!) { - events( - limit: $limit, offset: $offset, - where: { extrinsicHash_isNull: false, reversibleTransfer: { AND: [{ status_not_eq: SCHEDULED }, { OR: [{ from: { id_in: $accounts } }, { to: { id_in: $accounts } }] }] } }, - orderBy: timestamp_DESC - ) { - id - reversibleTransfer { id amount timestamp from { id } to { id } txId scheduledAt status block { height hash } extrinsicHash timestamp } - extrinsicHash + accountEventsConnection(orderBy: id_ASC, where: {account: {id_in: $accounts}, balanceEvent_isNull: true}) { + totalCount } -}'''; +} +'''; - final String _rewardsByAccountsQuery = r''' -query RewardsByAccounts($accounts: [String!]!, $limit: Int!, $offset: Int!) { - events( - limit: $limit, offset: $offset, - where: { minerReward: { miner: { id_in: $accounts } } }, - orderBy: timestamp_DESC - ) { + // GraphQL query to fetch transactions by their hash + final String _executedTransactionByTxId = r''' +query ExecutedReversibleTransferByTxId($txId: String!) { + executedReversibleTransfers(where: {txId_eq: $txId}) { + block { + height + hash + } + txId + timestamp id - minerReward { id reward timestamp miner { id } block { height hash } } - extrinsicHash + scheduledTransfer { + amount + from { + id + } + to { + id + } + scheduledAt + } } -}'''; +} +'''; // GraphQL query to search for transactions matching pending transaction criteria final String _searchPendingTransferQuery = r''' @@ -198,19 +231,6 @@ query SearchPendingTransaction( timestamp fee } - reversibleTransfer { - id - amount - timestamp - from { id } - to { id } - txId - scheduledAt - status - block { height hash } - extrinsicHash - timestamp - } } } '''; @@ -226,7 +246,7 @@ query SearchPendingTransaction( events( limit: $limit where: { - reversibleTransfer: { + scheduledReversibleTransfer: { from: { id_eq: $from }, to: { id_eq: $to }, amount_eq: $amount, @@ -240,7 +260,7 @@ query SearchPendingTransaction( id timestamp extrinsicHash - reversibleTransfer { + scheduledReversibleTransfer { id amount timestamp @@ -248,7 +268,6 @@ query SearchPendingTransaction( to { id } txId scheduledAt - status block { height hash } extrinsicHash timestamp @@ -263,52 +282,19 @@ query SearchPendingTransaction( } } - Future fetchAllTransactionTypes({ - required List accountIds, - int limit = 20, - int transfersOffset = 0, - int reversibleOffset = 0, - int rewardsOffset = 0, - int scheduledOffset = 0, - }) async { - final results = await Future.wait([ - fetchScheduledTransfers(accountIds: accountIds, limit: limit, offset: scheduledOffset), - _fetchOtherTransfers( - accountIds: accountIds, - limit: limit, - transfersOffset: transfersOffset, - reversibleOffset: reversibleOffset, - rewardsOffset: rewardsOffset, - ), - ]); - - final scheduled = results[0] as List; - final other = results[1] as TransferList; - return SortedTransactionsList( - reversibleTransfers: scheduled, - otherTransfers: other.transfers, - nextTransfersOffset: other.nextTransfersOffset, - nextReversibleOffset: other.nextReversibleOffset, - nextRewardsOffset: other.nextRewardsOffset, - nextScheduledOffset: scheduledOffset + scheduled.length, - hasMore: other.hasMore, - ); - } // Make a graphQL query for specific transaction hashes, get the results back // Mostly to check if reversibles have been executed or failed. - Future> fetchTransactionsByTransactionHash({ - required List transactionHashes, - int limit = 20, - int offset = 0, + Future fetchExecutedTransactionByTxId({ + required String txId, }) async { - if (transactionHashes.isEmpty) { - return []; + if (txId.isEmpty) { + return null; } final Map requestBody = { - 'query': _transactionsByHashQuery, - 'variables': {'transactionHashes': transactionHashes, 'limit': limit, 'offset': offset}, + 'query': _executedTransactionByTxId, + 'variables': {'txId': txId}, }; try { @@ -330,36 +316,16 @@ query SearchPendingTransaction( throw Exception('GraphQL response data is null.'); } - final List? events = data['events']; + final List? events = data['executedReversibleTransfers']; if (events == null || events.isEmpty) { - print('No transactions found for hashes: $transactionHashes'); - return []; - } - - final List transactions = []; - for (var eventJson in events) { - final event = eventJson as Map; - - if (event['transfer'] != null) { - final transferData = event['transfer'] as Map; - transferData['extrinsicHash'] ??= event['extrinsicHash']; - transactions.add(TransferEvent.fromJson(transferData)); - } else if (event['reversibleTransfer'] != null) { - final reversibleTransferData = event['reversibleTransfer'] as Map; - reversibleTransferData['extrinsicHash'] ??= event['extrinsicHash']; - transactions.add(ReversibleTransferEvent.fromJson(reversibleTransferData)); - } else if (event['minerReward'] != null) { - final minerRewardData = event['minerReward'] as Map; - minerRewardData['extrinsicHash'] ??= event['extrinsicHash']; - transactions.add(MinerRewardEvent.fromJson(minerRewardData)); - } + print('No transaction found for txId: $txId'); + return null; } - for (final t in transactions) { - print('${t.id} ${t.extrinsicHash} ${(t as ReversibleTransferEvent).status}'); - } - return transactions; + final transaction = ReversibleTransferEvent.fromExecutedJson(events.first); + + return transaction; } catch (e, stackTrace) { print('Error fetching transactions by hash: $e'); print(stackTrace); @@ -399,7 +365,9 @@ query SearchPendingTransaction( return []; } - final result = events.map((event) => ReversibleTransferEvent.fromJson(event['scheduledReversibleTransfer'])).toList(); + final result = events + .map((event) => ReversibleTransferEvent.fromScheduledJson(event['scheduledReversibleTransfer'])) + .toList(); return result; } catch (e, stackTrace) { @@ -411,107 +379,87 @@ query SearchPendingTransaction( } } - Future> _fetchSingleType({ - required String query, - required String label, + Future fetchAllTransactionTypes({ required List accountIds, - required int limit, - required int offset, - required TransactionEvent? Function(Map event) parser, + int limit = 20, + int offset = 0, }) async { - final body = jsonEncode({ - 'query': query, - 'variables': {'accounts': accountIds, 'limit': limit, 'offset': offset}, - }); + final Map requestBody = { + 'query': _accountEventsQuery, + 'variables': {'accounts': accountIds, 'limit': limit, 'offset': offset}, + }; + + final jsonBody = jsonEncode(requestBody); final sw = Stopwatch()..start(); - final http.Response response = await _graphQlEndpointService.post(body: body); - printTiming('$label HTTP', sw.elapsedMilliseconds); + try { + final http.Response response = await _graphQlEndpointService.post(body: jsonBody); + sw.stop(); + printTiming('fetchAccountEvents HTTP', sw.elapsedMilliseconds); - if (response.statusCode != 200) { - throw Exception('GraphQL $label failed: ${response.statusCode}. Body: ${response.body}'); - } + if (response.statusCode != 200) { + throw Exception('GraphQL request failed with status: ${response.statusCode}. Body: ${response.body}'); + } - final responseBody = jsonDecode(response.body) as Map; - if (responseBody['errors'] != null) { - throw Exception('GraphQL $label errors: ${responseBody['errors']}'); - } + final Map responseBody = jsonDecode(response.body); + if (responseBody['errors'] != null) { + throw Exception('GraphQL errors: ${responseBody['errors']}'); + } - final events = responseBody['data']?['events'] as List?; - if (events == null || events.isEmpty) return []; + final List? events = responseBody['data']?['accountEvents']; + final int totalCount = responseBody['data']?['accountEventsConnection']?['totalCount'] ?? 0; - return events.map((e) => parser(e as Map)).whereType().toList(); - } + if (events == null || totalCount == 0) { + return SortedTransactionsList.empty; + } - Future _fetchOtherTransfers({ - required List accountIds, - int limit = 10, - int transfersOffset = 0, - int reversibleOffset = 0, - int rewardsOffset = 0, - }) async { - try { - final results = await Future.wait([ - _fetchSingleType( - query: _transfersByAccountsQuery, - label: 'transfers', - accountIds: accountIds, - limit: limit, - offset: transfersOffset, - parser: (event) { - if (event['transfer'] == null) return null; - final d = event['transfer'] as Map; - d['extrinsicHash'] ??= event['extrinsicHash']; - return TransferEvent.fromJson(d); - }, - ), - _fetchSingleType( - query: _reversibleByAccountsQuery, - label: 'reversible', - accountIds: accountIds, - limit: limit, - offset: reversibleOffset, - parser: (event) { - if (event['reversibleTransfer'] == null) return null; - final d = event['reversibleTransfer'] as Map; - d['extrinsicHash'] ??= event['extrinsicHash']; - return ReversibleTransferEvent.fromJson(d); - }, - ), - _fetchSingleType( - query: _rewardsByAccountsQuery, - label: 'rewards', - accountIds: accountIds, - limit: limit, - offset: rewardsOffset, - parser: (event) { - if (event['minerReward'] == null) return null; - final d = event['minerReward'] as Map; - d['extrinsicHash'] ??= event['extrinsicHash']; - return MinerRewardEvent.fromJson(d); - }, - ), - ]); - - final all = [...results[0], ...results[1], ...results[2]]; - all.sort((a, b) => b.timestamp.compareTo(a.timestamp)); - final trimmed = all.take(limit).toList(); - - final usedTransfers = trimmed.whereType().length; - final usedReversible = trimmed.whereType().length; - final usedRewards = trimmed.whereType().length; - - final anyHasMore = results[0].length == limit || results[1].length == limit || results[2].length == limit; - - return TransferList( - transfers: trimmed, - hasMore: anyHasMore, - nextTransfersOffset: transfersOffset + usedTransfers, - nextReversibleOffset: reversibleOffset + usedReversible, - nextRewardsOffset: rewardsOffset + usedRewards, + final Map reversibleTransfers = {}; + final List otherTransfers = []; + + for (var event in events) { + if (event['cancelledReversibleTransfer'] != null) { + final cancelledReversibleTransfer = ReversibleTransferEvent.fromCancelledJson( + event['cancelledReversibleTransfer'], + ); + + if (!reversibleTransfers.containsKey(cancelledReversibleTransfer.txId)) { + reversibleTransfers[cancelledReversibleTransfer.txId] = cancelledReversibleTransfer; + } + } else if (event['executedReversibleTransfer'] != null) { + final executedReversibleTransfer = ReversibleTransferEvent.fromExecutedJson( + event['executedReversibleTransfer'], + ); + + if (!reversibleTransfers.containsKey(executedReversibleTransfer.txId)) { + reversibleTransfers[executedReversibleTransfer.txId] = executedReversibleTransfer; + } + } else if (event['scheduledReversibleTransfer'] != null) { + final scheduledReversibleTransfer = ReversibleTransferEvent.fromScheduledJson( + event['scheduledReversibleTransfer'], + ); + + if (!reversibleTransfers.containsKey(scheduledReversibleTransfer.txId)) { + reversibleTransfers[scheduledReversibleTransfer.txId] = scheduledReversibleTransfer; + } + } else if (event['transfer'] != null) { + otherTransfers.add(TransferEvent.fromJson(event['transfer'])); + } else if (event['minerReward'] != null) { + otherTransfers.add(MinerRewardEvent.fromJson(event['minerReward'])); + } + } + + final nextOffset = offset + limit; + + return SortedTransactionsList( + reversibleTransfers: reversibleTransfers.values.toList(), + otherTransfers: otherTransfers, + nextOffset: nextOffset, + hasMore: nextOffset < totalCount, ); } catch (e, stackTrace) { - print('Error fetching transfers: $e'); + sw.stop(); + printTiming('fetchAccountEvents FAILED', sw.elapsedMilliseconds); + print('Error fetching account events: $e'); print(stackTrace); rethrow; } @@ -574,8 +522,8 @@ query SearchPendingTransaction( var eventJson = events.first!; if (isReversible) { - final reversibleTransferData = eventJson['reversibleTransfer'] as Map; - transaction = ReversibleTransferEvent.fromJson(reversibleTransferData); + final reversibleTransferData = eventJson['scheduledReversibleTransfer'] as Map; + transaction = ReversibleTransferEvent.fromScheduledJson(reversibleTransferData); } else { final transferData = eventJson['transfer'] as Map; transaction = TransferEvent.fromJson(transferData);