Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 4 additions & 16 deletions mobile-app/lib/models/pagination_state.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ import 'package:quantus_sdk/quantus_sdk.dart';
class PaginationState {
final List<TransactionEvent> items;
final List<ReversibleTransferEvent> 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;
Expand All @@ -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,
Expand All @@ -32,10 +26,7 @@ class PaginationState {
PaginationState copyWith({
List<TransactionEvent>? items,
List<ReversibleTransferEvent>? reversibleTransfers,
int? transfersOffset,
int? reversibleOffset,
int? rewardsOffset,
int? scheduledOffset,
int? offset,
bool? hasMore,
bool? isFetching,
Object? error,
Expand All @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,21 +76,15 @@ class UnifiedPaginationController extends StateNotifier<PaginationState> {
.fetchAllTransactionTypes(
accountIds: targetAccountIds,
limit: _limit,
transfersOffset: state.transfersOffset,
reversibleOffset: state.reversibleOffset,
rewardsOffset: state.rewardsOffset,
scheduledOffset: state.scheduledOffset,
offset: state.offset,
);

final newItems = newTransactions.otherTransfers;

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,
Expand Down Expand Up @@ -159,10 +153,7 @@ class UnifiedPaginationController extends StateNotifier<PaginationState> {
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,
Expand All @@ -175,16 +166,16 @@ class UnifiedPaginationController extends StateNotifier<PaginationState> {
/// 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;
}

Expand All @@ -205,7 +196,7 @@ class UnifiedPaginationController extends StateNotifier<PaginationState> {

// 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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
Expand All @@ -169,15 +163,15 @@ 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
// tx screen views for all accounts reflect the change immediately
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);
Expand All @@ -190,28 +184,6 @@ class ReversibleTransferMonitoringService {
}
}

ReversibleTransferStatus? _checkIfTransferWasExecuted(
ReversibleTransferEvent originalTransfer,
List<TransactionEvent> 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();
Expand Down
2 changes: 1 addition & 1 deletion mobile-app/lib/services/transaction_service.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down
10 changes: 2 additions & 8 deletions quantus_sdk/lib/src/models/sorted_transactions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,13 @@ import 'package:quantus_sdk/src/models/transaction_event.dart';
class SortedTransactionsList {
final List<ReversibleTransferEvent> reversibleTransfers;
final List<TransactionEvent> 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,
});

Expand Down
68 changes: 67 additions & 1 deletion quantus_sdk/lib/src/models/transaction_event.dart
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,12 @@ class ReversibleTransferEvent extends TransactionEvent {
required super.blockHash,
});

factory ReversibleTransferEvent.fromJson(Map<String, dynamic> json) {

factory ReversibleTransferEvent.fromNotificationJson(Map<String, dynamic> json) {
final block = json['block'] as Map<String, dynamic>;
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? ?? '',
Expand All @@ -121,6 +123,70 @@ class ReversibleTransferEvent extends TransactionEvent {
);
}

factory ReversibleTransferEvent.fromScheduledJson(Map<String, dynamic> json) {
final block = json['block'] as Map<String, dynamic>;
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<String, dynamic> json) {
final block = json['block'] as Map<String, dynamic>;
final blockHeight = block['height'] as int;
final blockHash = block['hash'] as String? ?? '';

final scheduledTransfer = json['scheduledTransfer'] as Map<String, dynamic>;

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<String, dynamic> json) {
final block = json['block'] as Map<String, dynamic>;
final blockHeight = block['height'] as int;
final blockHash = block['hash'] as String? ?? '';

final scheduledTransfer = json['scheduledTransfer'] as Map<String, dynamic>;

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<String, dynamic> toJson() {
return {
'id': id,
Expand Down
Loading
Loading