Skip to content
This repository has been archived by the owner on Sep 17, 2024. It is now read-only.

Commit

Permalink
Merge pull request #63 from MXCzkEVM/tx_records
Browse files Browse the repository at this point in the history
feat: Tx records
  • Loading branch information
reasje authored Sep 20, 2023
2 parents 43799bb + cb74eec commit 82e0250
Show file tree
Hide file tree
Showing 21 changed files with 378 additions and 180 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import 'package:datadashwallet/common/components/recent_transactions/entity/transaction_history_model.dart';
import 'package:mxc_logic/mxc_logic.dart';
import 'package:datadashwallet/core/core.dart';

class TransactionsHistoryRepository extends ControlledCacheRepository {
@override
final String zone = 'transaction-history';

late final Field<List<TransactionHistoryModel>> transactionsHistory =
fieldWithDefault<List<TransactionHistoryModel>>('items', [],
serializer: (b) => b
.map((e) => {
'chainId': e.chainId,
'txList': e.txList.map((e) => e.toMap()).toList()
})
.toList(),
deserializer: (b) => (b as List)
.map((e) => TransactionHistoryModel(
chainId: e['chainId'],
txList: (e['txList'] as List)
.map((e) => TransactionModel.fromMap(e))
.toList(),
))
.toList());

List<TransactionHistoryModel> get items => transactionsHistory.value;

void addItem(TransactionHistoryModel item) =>
transactionsHistory.value = [...transactionsHistory.value, item];

void addItemTx(TransactionModel item, int index) {
final newList = transactionsHistory.value;
newList[index].txList.insert(0, item);
transactionsHistory.value = newList;
}

void updateItem(TransactionHistoryModel item, int index) {
final newList = transactionsHistory.value;
newList.removeAt(index);
newList.insert(index, item);
transactionsHistory.value = newList;
}

void updateItemTx(TransactionModel item, int index, int txIndex) {
final newList = transactionsHistory.value;

newList[index].txList[txIndex] = item;

transactionsHistory.value = newList;
}

void removeItem(TransactionHistoryModel item) =>
transactionsHistory.value = transactionsHistory.value
.where((e) => e.chainId != item.chainId)
.toList();

void removeAll() => transactionsHistory.value = [];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import 'package:datadashwallet/core/core.dart';
import 'package:mxc_logic/mxc_logic.dart';
import '../entity/transaction_history_model.dart';
import 'transactions_repository.dart';

class TransactionsHistoryUseCase extends ReactiveUseCase {
TransactionsHistoryUseCase(this._repository, this._web3Repository);

final Web3Repository _web3Repository;

final TransactionsHistoryRepository _repository;

late final ValueStream<List<TransactionHistoryModel>> transactionsHistory =
reactiveField(_repository.transactionsHistory);

List<TransactionHistoryModel> getTransactionsHistory() => _repository.items;

List<String> updatingTxList = [];

void addItem(TransactionHistoryModel item) {
_repository.addItem(item);
update(transactionsHistory, _repository.items);
}

void updateItem(TransactionHistoryModel item, int index) {
_repository.updateItem(item, index);
update(transactionsHistory, _repository.items);
}

void updateItemTx(TransactionModel item, int selectedNetworkChainId) {
final txHistory = transactionsHistory.value
.firstWhere((element) => element.chainId == selectedNetworkChainId);

final index = transactionsHistory.value.indexOf(txHistory);
final txIndex = txHistory.txList.indexWhere(
(element) => element.hash == item.hash,
);

if (txIndex == -1) {
_repository.addItemTx(item, index);
} else {
_repository.updateItemTx(item, index, txIndex);
}

update(transactionsHistory, _repository.items);
}

void removeAll() {
_repository.removeAll();
update(transactionsHistory, _repository.items);
}

void removeItem(TransactionHistoryModel item) {
_repository.removeItem(item);
update(transactionsHistory, _repository.items);
}

void spyOnTransaction(TransactionModel item, int chainId) {
if (!updatingTxList.contains(item.hash)) {
updatingTxList.add(item.hash);
final stream = _web3Repository.tokenContract.spyTransaction(item.hash);
stream.onData((succeeded) {
if (succeeded) {
final updatedItem = item.copyWith(status: TransactionStatus.done);
updateItemTx(updatedItem, chainId);
updatingTxList.remove(item.hash);
stream.cancel();
}
});
}
}

void checkForPendingTransactions(int chainId) {
final index = transactionsHistory.value
.indexWhere((element) => element.chainId == chainId);

if (index != -1) {
final chainTx = transactionsHistory.value[index];
final pendingTxList = chainTx.txList
.where((element) => element.status == TransactionStatus.failed);
for (TransactionModel pendingTx in pendingTxList) {
spyOnTransaction(pendingTx, chainId);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import 'package:mxc_logic/mxc_logic.dart';

class TransactionHistoryModel {
TransactionHistoryModel({
required this.chainId,
required this.txList,
});

int chainId;
List<TransactionModel> txList;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:mxc_ui/mxc_ui.dart';

enum TransactionType { sent, received, all }

enum TransactionStatus { done, pending, failed }
export 'domain/transactions_use_case.dart';
export 'domain/transactions_repository.dart';
export 'entity/transaction_history_model.dart';

class RecentTransactions extends HookConsumerWidget {
const RecentTransactions({
Expand All @@ -22,7 +22,7 @@ class RecentTransactions extends HookConsumerWidget {
});

final String? walletAddress;
final List<WannseeTransactionModel>? transactions;
final List<TransactionModel>? transactions;
final List<Token> tokens;
final NetworkType? networkType;

Expand Down
109 changes: 21 additions & 88 deletions lib/common/components/recent_transactions/utils.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,96 +75,29 @@ class RecentTransactionsUtils {
}

static List<RecentTrxListItem> generateTx(String walletAddressHash,
List<WannseeTransactionModel> items, List<Token> tokensList) {
List<RecentTrxListItem> widgets = [];

for (int i = 0; i < items.length; i++) {
final currentTx = items[i];
String amount = '0';
String symbol = 'Unknown';
String timeStamp = 'Unknown';
String hash = currentTx.hash ?? 'Unknown';
TransactionType transactionType = TransactionType.sent;
TransactionStatus transactionStatus = TransactionStatus.done;
String logoUrl = 'assets/svg/networks/unknown.svg';

// two type of tx : coin_transfer from filtered tx list & token transfer from token transfer list
// If not 'contract_call' or 'coin_transfer' then empty and that means failed in other words
// another tx that we have are : pending coin transfer (which is received on both sides) &
// pending token transfer (which is only received on the sender side)
if (currentTx.result == 'pending') {
// could be contract_call || coin_transfer
transactionStatus = TransactionStatus.pending;
final time = DateTime.now();
timeStamp = Formatter.localTime(time);

transactionType = RecentTransactionsUtils.checkForTransactionType(
walletAddressHash, currentTx.from!.hash!.toLowerCase());
amount = Formatter.convertWeiToEth(
currentTx.value ?? '0', Config.ethDecimals);
logoUrl = Config.mxcLogoUri;
symbol = Config.mxcName;

if (currentTx.decodedInput != null) {
if (currentTx.to?.hash != null) {
final tokenIndex = tokensList
.indexWhere((element) => element.address == currentTx.to!.hash);
if (tokenIndex != -1) {
logoUrl = tokensList[tokenIndex].logoUri!;
}
symbol = currentTx.to!.name!;
amount = Formatter.convertWeiToEth(
currentTx.decodedInput?.parameters?[1].value ?? '0',
Config.ethDecimals);
}
}
} else if (currentTx.txTypes != null &&
currentTx.txTypes!.contains('coin_transfer')) {
logoUrl = Config.mxcLogoUri;
symbol = Config.mxcSymbol;
timeStamp = Formatter.localTime(currentTx.timestamp!);

transactionType = RecentTransactionsUtils.checkForTransactionType(
walletAddressHash, currentTx.from!.hash!.toLowerCase());
amount = Formatter.convertWeiToEth(
currentTx.value ?? '0', Config.ethDecimals);
} else if (currentTx.txTypes == null &&
currentTx.tokenTransfers != null &&
currentTx.tokenTransfers![0].type == 'token_transfer') {
symbol = currentTx.tokenTransfers![0].token!.name!;

if (currentTx.tokenTransfers![0].token!.name != null) {
final tokenIndex = tokensList.indexWhere((element) =>
element.address == currentTx.tokenTransfers![0].token!.address!);
if (tokenIndex != -1) {
logoUrl = tokensList[tokenIndex].logoUri!;
}
}

timeStamp =
Formatter.localTime(currentTx.tokenTransfers![0].timestamp!);

amount = Formatter.convertWeiToEth(
currentTx.tokenTransfers![0].total!.value ?? '0',
Config.ethDecimals);
hash = currentTx.tokenTransfers![0].txHash ?? "Unknown";
transactionType = RecentTransactionsUtils.checkForTransactionType(
walletAddressHash,
currentTx.tokenTransfers![0].from!.hash!.toLowerCase(),
);
}

widgets.add(RecentTrxListItem(
List<TransactionModel> items, List<Token> tokensList) {
return items.map((e) {
final foundToken = tokensList.firstWhere(
(element) => element.address == e.token.address,
orElse: () => Token());
final logoUrl = foundToken.logoUri ??
e.token.logoUri ??
'assets/svg/networks/unknown.svg';
final decimal =
foundToken.decimals ?? e.token.decimals ?? Config.ethDecimals;
final symbol = foundToken.symbol ?? e.token.symbol ?? 'Unknown';

return RecentTrxListItem(
logoUrl: logoUrl,
amount: amount,
amount: Formatter.convertWeiToEth(e.value, decimal),
symbol: symbol,
timestamp: timeStamp,
txHash: hash,
transactionType: transactionType,
transactionStatus: transactionStatus,
));
}
return widgets;
timestamp:
e.timeStamp == null ? "Unknown" : Formatter.localTime(e.timeStamp!),
txHash: e.hash,
transactionType: e.type,
transactionStatus: e.status,
);
}).toList();
}

static String getViewOtherTransactionsLink(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import 'package:datadashwallet/features/wallet/wallet.dart';

import 'package:mxc_logic/mxc_logic.dart';
import './transaction_status_chip.dart';
import './transaction_type_widget.dart';
import 'package:flutter/material.dart';
Expand Down
3 changes: 3 additions & 0 deletions lib/core/src/cache/datadash_cache.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ class DatadashCache extends CacheContainer {
final BalanceRepository balanceHistory = BalanceRepository();
final RecipientsRepository recipients = RecipientsRepository();
final NftsRepository nfts = NftsRepository();
final TransactionsHistoryRepository transactionsHistoryRepository =
TransactionsHistoryRepository();

@override
List<BaseCacheRepository> get repositories => [
Expand All @@ -38,5 +40,6 @@ class DatadashCache extends CacheContainer {
balanceHistory,
recipients,
nfts,
transactionsHistoryRepository
];
}
8 changes: 8 additions & 0 deletions lib/core/src/providers/providers_use_cases.dart
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,14 @@ final Provider<ChainConfigurationUseCase> chainConfigurationUseCaseProvider =
),
);

final Provider<TransactionsHistoryUseCase> transactionHistoryUseCaseProvider =
Provider(
(ref) => TransactionsHistoryUseCase(
ref.watch(datadashCacheProvider).transactionsHistoryRepository,
ref.watch(web3RepositoryProvider),
),
);

final Provider<NetworkUnavailableUseCase> networkUnavailableUseCaseProvider =
Provider(
(ref) => NetworkUnavailableUseCase(),
Expand Down
33 changes: 21 additions & 12 deletions lib/features/common/contract/token_contract_use_case.dart
Original file line number Diff line number Diff line change
Expand Up @@ -98,20 +98,25 @@ class TokenContractUseCase extends ReactiveUseCase {
update(online, result);
}

Future<void> getTokensBalance(String walletAddress) async {
try {
final result = await _repository.tokenContract
.getTokensBalance(tokensList.value, walletAddress);
update(tokensList, result);
Future<void> getTokensBalance(
String walletAddress, bool shouldGetPrice) async {
final result = await _repository.tokenContract
.getTokensBalance(tokensList.value, walletAddress);
update(tokensList, result);
if (shouldGetPrice) {
getTokensPrice();
} catch (e) {
final newList = [];
for (Token token in tokensList.value) {
newList.add(token.copyWith(balance: 0.0));
}
update(tokensList, newList);
getTokensBalance(walletAddress);
} else {
resetTokenPrice();
}
}

void resetTokenPrice() {
final List<Token> newList = <Token>[];
for (Token token in tokensList.value) {
newList.add(token.copyWith(balancePrice: 0.0));
}
update(tokensList, newList);
calculateTotalBalanceInXsd();
}

Future<void> getTokensPrice() async {
Expand Down Expand Up @@ -175,4 +180,8 @@ class TokenContractUseCase extends ReactiveUseCase {
}
update(totalBalanceInXsd, totalPrice);
}

StreamSubscription<bool> spyOnTransaction(String hash) {
return _repository.tokenContract.spyTransaction(hash);
}
}
Loading

0 comments on commit 82e0250

Please sign in to comment.