mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-26 06:08:40 +01:00
CW-1548: Solana wallet performance improvements (#3404)
* fix android CI * add incremental sync, parallel fetch for native and spl token, and other improvments on perf and display regarding lagging
This commit is contained in:
@@ -191,6 +191,10 @@ class Money implements Comparable<Money> {
|
||||
@override
|
||||
int get hashCode => amount.hashCode ^ currency.hashCode;
|
||||
|
||||
// Added this to reduce the hops we do to convert Money to double
|
||||
// for fiat conversion and display
|
||||
double toDouble() => amount / multiplierOf(currency.decimals);
|
||||
|
||||
@override
|
||||
String toString() => formatFixed(amount, currency.decimals);
|
||||
|
||||
|
||||
@@ -4,13 +4,11 @@ String formatFixed(BigInt value, int? decimals, {int? fractionalDigits, bool tri
|
||||
decimals ??= 0;
|
||||
fractionalDigits ??= decimals;
|
||||
|
||||
var multiplier = getMultiplier(decimals);
|
||||
// Make sure wei is a big number (convert as necessary)
|
||||
final multiplier = multiplierOf(decimals);
|
||||
var negative = value.isNegative;
|
||||
if (negative) value = value * BigInt.from(-1);
|
||||
if (negative) value = -value;
|
||||
|
||||
var fraction =
|
||||
value.modPow(BigInt.one, BigInt.parse(multiplier)).toString().padLeft(decimals, "0");
|
||||
var fraction = (value % multiplier).toString().padLeft(decimals, "0");
|
||||
|
||||
if (fractionalDigits < 0) fractionalDigits = 0;
|
||||
if (fractionalDigits > decimals) fractionalDigits = decimals;
|
||||
@@ -20,7 +18,7 @@ String formatFixed(BigInt value, int? decimals, {int? fractionalDigits, bool tri
|
||||
fraction = removeTrailing("0", fraction);
|
||||
}
|
||||
|
||||
final whole = (value ~/ BigInt.parse(multiplier));
|
||||
final whole = value ~/ multiplier;
|
||||
|
||||
final valString = fraction.isEmpty ? "$whole" : "$whole.$fraction";
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ BigInt parseFixed(String value, int decimals) {
|
||||
|
||||
final wholeValue = BigInt.parse(whole);
|
||||
final fractionValue = BigInt.parse(fraction);
|
||||
final multiplierValue = BigInt.parse(multiplier);
|
||||
final multiplierValue = multiplierOf(decimals);
|
||||
|
||||
var wei = (wholeValue * multiplierValue) + fractionValue;
|
||||
|
||||
@@ -69,3 +69,8 @@ BigInt parseFixed(String value, int decimals) {
|
||||
|
||||
// Returns a string "1" followed by decimal "0"s
|
||||
String getMultiplier(int decimals) => "1".padRight(decimals + 1, "0");
|
||||
|
||||
final _multipliers = <int, BigInt>{};
|
||||
|
||||
// this is more direct and faster than having it as string then parsing everytime to get the number
|
||||
BigInt multiplierOf(int decimals) => _multipliers[decimals] ??= BigInt.from(10).pow(decimals);
|
||||
|
||||
@@ -32,14 +32,29 @@ class TransactionFetchResult {
|
||||
});
|
||||
}
|
||||
|
||||
class TransactionSyncResult {
|
||||
final List<SolanaTransactionModel> transactions;
|
||||
final String? newestSignature;
|
||||
|
||||
TransactionSyncResult({
|
||||
required this.transactions,
|
||||
this.newestSignature,
|
||||
});
|
||||
}
|
||||
|
||||
class SolanaWalletClient {
|
||||
// Minimum amount in SOL to consider a transaction valid (to filter spam)
|
||||
static Money minValidAmount = Money.parse("0.00000003", CryptoCurrency.sol);
|
||||
|
||||
static const int _signaturePageSize = 1000;
|
||||
|
||||
late final client = ProxyWrapper().getHttpIOClient();
|
||||
SolanaRPC? _provider;
|
||||
bool _isStopped = false;
|
||||
|
||||
bool connect(Node node) {
|
||||
try {
|
||||
_isStopped = false;
|
||||
String formattedUrl;
|
||||
String protocolUsed = node.isSSL ? "https" : "http";
|
||||
|
||||
@@ -625,10 +640,7 @@ class SolanaWalletClient {
|
||||
incomingAmount = diff.toDouble();
|
||||
incomingMintAddress = mint;
|
||||
final token = await getTokenInfo(mint);
|
||||
printV(token?.symbol);
|
||||
printV(token?.decimals);
|
||||
incomingToken =
|
||||
token ?? const CryptoCurrency(name: "TOKEN", title: "TOKEN", decimals: 6);
|
||||
incomingToken = token ?? const CryptoCurrency(name: "TOKEN", title: "TOKEN", decimals: 6);
|
||||
incomingTo = walletAddress;
|
||||
// We find the intermediate account
|
||||
if (instructions.isNotEmpty && instructions[0].accounts.isNotEmpty) {
|
||||
@@ -951,27 +963,67 @@ class SolanaWalletClient {
|
||||
return mints.toList();
|
||||
}
|
||||
|
||||
/// Load the Address's transactions into the account
|
||||
Future<List<SolanaTransactionModel>> fetchTransactions(
|
||||
Future<List<Map<String, dynamic>>> _getAllSignaturesSinceLastFetch(
|
||||
SolAddress address,
|
||||
String? until,
|
||||
Commitment? commitment,
|
||||
) async {
|
||||
final signatures = <Map<String, dynamic>>[];
|
||||
String? before;
|
||||
|
||||
while (true) {
|
||||
final currentPageSignatureResults = await _provider!.request(
|
||||
SolanaRPCGetSignaturesForAddress(
|
||||
account: address,
|
||||
commitment: commitment,
|
||||
until: until,
|
||||
before: before,
|
||||
limit: _signaturePageSize,
|
||||
),
|
||||
);
|
||||
|
||||
if (currentPageSignatureResults.isEmpty) break;
|
||||
|
||||
signatures.addAll(currentPageSignatureResults);
|
||||
|
||||
if (currentPageSignatureResults.length < _signaturePageSize) break;
|
||||
|
||||
if (until == null) break;
|
||||
|
||||
final lastSignatureOnPage = currentPageSignatureResults.last['signature'] as String;
|
||||
|
||||
if (lastSignatureOnPage == before) break;
|
||||
|
||||
before = lastSignatureOnPage;
|
||||
}
|
||||
|
||||
return signatures;
|
||||
}
|
||||
|
||||
Future<TransactionSyncResult> fetchTransactions(
|
||||
SolAddress address, {
|
||||
SPLToken? splToken,
|
||||
Commitment? commitment,
|
||||
SolAddress? walletAddress,
|
||||
String? untilSignature,
|
||||
required void Function(List<SolanaTransactionModel>) onUpdate,
|
||||
}) async {
|
||||
List<SolanaTransactionModel> transactions = [];
|
||||
final transactions = <SolanaTransactionModel>[];
|
||||
|
||||
try {
|
||||
final signatures = await _provider!.request(
|
||||
SolanaRPCGetSignaturesForAddress(
|
||||
account: address,
|
||||
commitment: commitment,
|
||||
),
|
||||
);
|
||||
final signatures =
|
||||
await _getAllSignaturesSinceLastFetch(address, untilSignature, commitment);
|
||||
|
||||
if (signatures.isEmpty) return TransactionSyncResult(transactions: transactions);
|
||||
|
||||
// The maximum concurrent batch size.
|
||||
const int batchSize = 10;
|
||||
|
||||
bool hasFailures = false;
|
||||
|
||||
for (int i = 0; i < signatures.length; i += batchSize) {
|
||||
if (_isStopped) return TransactionSyncResult(transactions: transactions);
|
||||
|
||||
final batch = signatures.skip(i).take(batchSize).toList();
|
||||
|
||||
final batchResponses = await Future.wait(batch.map((signature) async {
|
||||
@@ -985,6 +1037,7 @@ class SolanaWalletClient {
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
hasFailures = true;
|
||||
return null;
|
||||
}
|
||||
}));
|
||||
@@ -999,16 +1052,16 @@ class SolanaWalletClient {
|
||||
|
||||
final parsedTransactionsLists = await Future.wait(parsedTransactionsFutures);
|
||||
|
||||
// We flatten the list of lists into a single list
|
||||
final batchTransactions = <SolanaTransactionModel>[];
|
||||
for (final parsedList in parsedTransactionsLists) {
|
||||
if (parsedList != null) {
|
||||
transactions.addAll(parsedList);
|
||||
batchTransactions.addAll(parsedList);
|
||||
}
|
||||
}
|
||||
|
||||
// Only update UI if we have new valid transactions
|
||||
if (transactions.isNotEmpty) {
|
||||
onUpdate(List<SolanaTransactionModel>.from(transactions));
|
||||
if (batchTransactions.isNotEmpty) {
|
||||
transactions.addAll(batchTransactions);
|
||||
onUpdate(batchTransactions);
|
||||
}
|
||||
|
||||
if (i + batchSize < signatures.length) {
|
||||
@@ -1016,38 +1069,53 @@ class SolanaWalletClient {
|
||||
}
|
||||
}
|
||||
|
||||
return transactions;
|
||||
return TransactionSyncResult(
|
||||
transactions: transactions,
|
||||
newestSignature: hasFailures ? null : signatures.first['signature'] as String,
|
||||
);
|
||||
} catch (err, s) {
|
||||
printV('Error fetching transactions: $err \n$s');
|
||||
return [];
|
||||
return TransactionSyncResult(transactions: transactions);
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<SolanaTransactionModel>> getSPLTokenTransfers({
|
||||
final Map<String, ProgramDerivedAddress> associatedTokenAccountCache = {};
|
||||
|
||||
Future<TransactionSyncResult> getSPLTokenTransfers({
|
||||
required String mintAddress,
|
||||
required SPLToken splToken,
|
||||
required SolanaPrivateKey privateKey,
|
||||
String? untilSignature,
|
||||
required void Function(List<SolanaTransactionModel>) onUpdate,
|
||||
}) async {
|
||||
ProgramDerivedAddress? associatedTokenAccount;
|
||||
final ownerWalletAddress = privateKey.publicKey().toAddress();
|
||||
try {
|
||||
associatedTokenAccount = await _getOrCreateAssociatedTokenAccount(
|
||||
payerPrivateKey: privateKey,
|
||||
mintAddress: SolAddress(mintAddress),
|
||||
ownerAddress: ownerWalletAddress,
|
||||
shouldCreateATA: false,
|
||||
);
|
||||
} catch (e, s) {
|
||||
printV('$e \n $s');
|
||||
}
|
||||
|
||||
if (associatedTokenAccount == null) return [];
|
||||
var associatedTokenAccount = associatedTokenAccountCache[mintAddress];
|
||||
|
||||
if (associatedTokenAccount == null) {
|
||||
try {
|
||||
associatedTokenAccount = await _getOrCreateAssociatedTokenAccount(
|
||||
payerPrivateKey: privateKey,
|
||||
mintAddress: SolAddress(mintAddress),
|
||||
ownerAddress: ownerWalletAddress,
|
||||
shouldCreateATA: false,
|
||||
);
|
||||
} catch (e, s) {
|
||||
printV('$e \n $s');
|
||||
}
|
||||
|
||||
if (associatedTokenAccount == null) {
|
||||
return TransactionSyncResult(transactions: <SolanaTransactionModel>[]);
|
||||
}
|
||||
|
||||
associatedTokenAccountCache[mintAddress] = associatedTokenAccount;
|
||||
}
|
||||
|
||||
return fetchTransactions(
|
||||
associatedTokenAccount.address,
|
||||
splToken: splToken,
|
||||
walletAddress: ownerWalletAddress,
|
||||
untilSignature: untilSignature,
|
||||
onUpdate: onUpdate,
|
||||
);
|
||||
}
|
||||
@@ -1055,16 +1123,9 @@ class SolanaWalletClient {
|
||||
final Map<String, SPLToken?> tokenInfoCache = {};
|
||||
|
||||
Future<SPLToken?> getTokenInfo(String mintAddress) async {
|
||||
if (tokenInfoCache.containsKey(mintAddress)) {
|
||||
printV("Cached");
|
||||
return tokenInfoCache[mintAddress];
|
||||
} else {
|
||||
final token = await fetchSPLTokenInfo(mintAddress);
|
||||
if (token != null) {
|
||||
tokenInfoCache[mintAddress] = token;
|
||||
}
|
||||
return token;
|
||||
}
|
||||
if (tokenInfoCache.containsKey(mintAddress)) return tokenInfoCache[mintAddress];
|
||||
|
||||
return tokenInfoCache[mintAddress] = await fetchSPLTokenInfo(mintAddress);
|
||||
}
|
||||
|
||||
Future<SPLToken?> fetchSPLTokenInfo(String mintAddress) async {
|
||||
@@ -1139,7 +1200,7 @@ class SolanaWalletClient {
|
||||
}
|
||||
}
|
||||
|
||||
void stop() {}
|
||||
void stop() => _isStopped = true;
|
||||
|
||||
SolanaRPC? get getSolanaProvider => _provider;
|
||||
|
||||
|
||||
@@ -31,18 +31,36 @@ abstract class SolanaTransactionHistoryBase extends TransactionHistoryBase<Solan
|
||||
await _load();
|
||||
}
|
||||
|
||||
Future<void> _saveQueue = Future.value();
|
||||
|
||||
@override
|
||||
Future<void> save() async {
|
||||
try {
|
||||
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||
final path = '$dirPath/$transactionsHistoryFileName';
|
||||
final transactionMaps = transactions.map((key, value) => MapEntry(key, value.toJson()));
|
||||
final data = json.encode({'transactions': transactionMaps});
|
||||
await encryptionFileUtils.write(path: path, password: _password, data: data);
|
||||
} catch (e, s) {
|
||||
printV('Error while saving solana transaction history: ${e.toString()}');
|
||||
printV(s);
|
||||
}
|
||||
Future<void> save() => saveAndConfirm();
|
||||
|
||||
Future<bool> saveAndConfirm() {
|
||||
final write = _saveQueue.then((_) async {
|
||||
try {
|
||||
await _write();
|
||||
|
||||
return true;
|
||||
} catch (e, s) {
|
||||
printV('Error while saving solana transaction history: ${e.toString()}');
|
||||
printV(s);
|
||||
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
_saveQueue = write;
|
||||
|
||||
return write;
|
||||
}
|
||||
|
||||
Future<void> _write() async {
|
||||
final dirPath = await pathForWalletDir(name: walletInfo.name, type: walletInfo.type);
|
||||
final path = '$dirPath/$transactionsHistoryFileName';
|
||||
final transactionMaps = transactions.map((key, value) => MapEntry(key, value.toJson()));
|
||||
final data = json.encode({'transactions': transactionMaps});
|
||||
await encryptionFileUtils.write(path: path, password: _password, data: data);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -88,6 +88,8 @@ abstract class SolanaWalletBase
|
||||
|
||||
Timer? _transactionsUpdateTimer;
|
||||
|
||||
Future<void>? _currentRefresh;
|
||||
|
||||
late final Box<SPLToken> splTokensBox;
|
||||
|
||||
@override
|
||||
@@ -321,6 +323,33 @@ abstract class SolanaWalletBase
|
||||
]);
|
||||
}
|
||||
|
||||
static const _nativeSource = 'native';
|
||||
|
||||
String _lastSyncedSignatureKey(String source) =>
|
||||
'solana_last_synced_signature_${walletInfo.name}_$source';
|
||||
|
||||
Future<String?> _lastSyncedSignature(String source) async {
|
||||
if (transactionHistory.transactions.isEmpty) return null;
|
||||
|
||||
final prefs = await _sharedPrefs.future;
|
||||
|
||||
return prefs.getString(_lastSyncedSignatureKey(source));
|
||||
}
|
||||
|
||||
Future<void> _saveLastSyncedSignature(String source, String? signature) async {
|
||||
if (signature == null) return;
|
||||
|
||||
final prefs = await _sharedPrefs.future;
|
||||
|
||||
await prefs.setString(_lastSyncedSignatureKey(source), signature);
|
||||
}
|
||||
|
||||
Future<void> _clearLastSyncedSignature(String source) async {
|
||||
final prefs = await _sharedPrefs.future;
|
||||
|
||||
await prefs.remove(_lastSyncedSignatureKey(source));
|
||||
}
|
||||
|
||||
/// Polls for a specific transaction by signature with exponential backoff
|
||||
/// I'm using this in case we make the call to fetch the transaction and it has not finished its confirmations on the solana network and been indexed by the node networks we use.
|
||||
Future<void> pollForTransaction({
|
||||
@@ -365,20 +394,34 @@ abstract class SolanaWalletBase
|
||||
await updateTransactionsHistory();
|
||||
}
|
||||
|
||||
void updateTransactions(List<SolanaTransactionModel> updatedTx) {
|
||||
addTransactionsToTransactionHistory(updatedTx);
|
||||
}
|
||||
void updateTransactions(List<SolanaTransactionModel> updatedTx) => _addTransactions(updatedTx);
|
||||
|
||||
/// Fetches the native SOL transactions linked to the wallet Public Key
|
||||
Future<void> _updateNativeSOLTransactions() async {
|
||||
final transactions =
|
||||
await _client.fetchTransactions(_solanaPublicKey.toAddress(), onUpdate: updateTransactions);
|
||||
final result = await _client.fetchTransactions(
|
||||
_solanaPublicKey.toAddress(),
|
||||
untilSignature: await _lastSyncedSignature(_nativeSource),
|
||||
onUpdate: updateTransactions,
|
||||
);
|
||||
|
||||
await addTransactionsToTransactionHistory(transactions);
|
||||
await _updateStateWhenSyncForTheSourceEnds(_nativeSource, result);
|
||||
}
|
||||
|
||||
Future<void> _updateStateWhenSyncForTheSourceEnds(
|
||||
String source,
|
||||
TransactionSyncResult result,
|
||||
) async {
|
||||
if (result.transactions.isNotEmpty) {
|
||||
final isSaved = await transactionHistory.saveAndConfirm();
|
||||
|
||||
if (!isSaved) return;
|
||||
}
|
||||
|
||||
await _saveLastSyncedSignature(source, result.newestSignature);
|
||||
}
|
||||
|
||||
Future<void> updateSPLTokenTransactions({List<String>? specificMints}) async {
|
||||
final allTokens = balance.keys.whereType<SPLToken>().toList(growable: false);
|
||||
final allTokens = splTokensBox.values.where((t) => t.enabled).toList(growable: false);
|
||||
|
||||
// Filter to specific mints if provided
|
||||
final tokens = specificMints != null
|
||||
@@ -394,30 +437,28 @@ abstract class SolanaWalletBase
|
||||
i,
|
||||
i + batchSize > tokens.length ? tokens.length : i + batchSize,
|
||||
);
|
||||
final results = await Future.wait(
|
||||
|
||||
await Future.wait(
|
||||
batch.map((token) async {
|
||||
try {
|
||||
return await _client.getSPLTokenTransfers(
|
||||
final result = await _client.getSPLTokenTransfers(
|
||||
mintAddress: token.mintAddress,
|
||||
splToken: token,
|
||||
privateKey: _solanaPrivateKey,
|
||||
untilSignature: await _lastSyncedSignature(token.mintAddress),
|
||||
onUpdate: updateTransactions,
|
||||
);
|
||||
} catch (_) {
|
||||
return <SolanaTransactionModel>[];
|
||||
|
||||
await _updateStateWhenSyncForTheSourceEnds(token.mintAddress, result);
|
||||
} catch (e) {
|
||||
printV('Error fetching spl token (${token.symbol}) transfers ${e.toString()}');
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
for (final list in results) {
|
||||
await addTransactionsToTransactionHistory(list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> addTransactionsToTransactionHistory(
|
||||
List<SolanaTransactionModel> transactions,
|
||||
) async {
|
||||
void _addTransactions(List<SolanaTransactionModel> transactions) {
|
||||
final Map<String, SolanaTransactionInfo> result = {};
|
||||
|
||||
for (var transactionModel in transactions) {
|
||||
@@ -436,6 +477,12 @@ abstract class SolanaWalletBase
|
||||
}
|
||||
|
||||
transactionHistory.addMany(result);
|
||||
}
|
||||
|
||||
Future<void> addTransactionsToTransactionHistory(
|
||||
List<SolanaTransactionModel> transactions,
|
||||
) async {
|
||||
_addTransactions(transactions);
|
||||
|
||||
await transactionHistory.save();
|
||||
}
|
||||
@@ -456,6 +503,17 @@ abstract class SolanaWalletBase
|
||||
await transactionHistory.save();
|
||||
}
|
||||
|
||||
// we want to handle the case where multiple refresh triggers (our users can swipe down
|
||||
// multiple times), so we track the currrent refresh and join it instead of starting
|
||||
// another one
|
||||
Future<void> _refresh() {
|
||||
return _currentRefresh ??= Future.wait([
|
||||
updateTokenBalance(),
|
||||
updateTransactionsHistory(),
|
||||
_getEstimatedFees(),
|
||||
]).whenComplete(() => _currentRefresh = null);
|
||||
}
|
||||
|
||||
@action
|
||||
@override
|
||||
Future<void> startSync() async {
|
||||
@@ -469,12 +527,7 @@ abstract class SolanaWalletBase
|
||||
return;
|
||||
}
|
||||
|
||||
await Future.wait([
|
||||
updateTokenBalance(),
|
||||
_updateNativeSOLTransactions(),
|
||||
updateSPLTokenTransactions(),
|
||||
_getEstimatedFees(),
|
||||
]);
|
||||
await _refresh();
|
||||
|
||||
syncStatus = SyncedSyncStatus();
|
||||
} catch (e) {
|
||||
@@ -626,6 +679,14 @@ abstract class SolanaWalletBase
|
||||
|
||||
List<SPLToken> get splTokenCurrencies => splTokensBox.values.toList();
|
||||
|
||||
SPLToken? splTokenBySymbol(String symbol) {
|
||||
for (final token in splTokensBox.values) {
|
||||
if (token.symbol == symbol) return token;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
void addInitialTokens() {
|
||||
final initialSPLTokens = DefaultSPLTokens().initialSPLTokens;
|
||||
|
||||
@@ -802,12 +863,27 @@ abstract class SolanaWalletBase
|
||||
}
|
||||
|
||||
Future<void> deleteSPLToken(SPLToken token) async {
|
||||
final sources = <String>{token.mintAddress};
|
||||
|
||||
if (token.symbol == CryptoCurrency.sol.symbol) {
|
||||
sources.add(_nativeSource);
|
||||
}
|
||||
|
||||
if (splTokensBox.isOpen) {
|
||||
sources.addAll(splTokensBox.values
|
||||
.where((t) => t.symbol == token.symbol)
|
||||
.map((t) => t.mintAddress));
|
||||
|
||||
await splTokensBox.delete(token.mintAddress);
|
||||
}
|
||||
|
||||
balance.remove(token);
|
||||
await _removeTokenTransactionsInHistory(token);
|
||||
|
||||
for (final source in sources) {
|
||||
await _clearLastSyncedSignature(source);
|
||||
}
|
||||
|
||||
updateTokenBalance();
|
||||
}
|
||||
|
||||
@@ -831,11 +907,12 @@ abstract class SolanaWalletBase
|
||||
_transactionsUpdateTimer!.cancel();
|
||||
}
|
||||
|
||||
_transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 30), (_) {
|
||||
updateTokenBalance();
|
||||
_updateNativeSOLTransactions();
|
||||
updateSPLTokenTransactions();
|
||||
_getEstimatedFees();
|
||||
_transactionsUpdateTimer = Timer.periodic(const Duration(seconds: 30), (_) async {
|
||||
try {
|
||||
await _refresh();
|
||||
} catch (e) {
|
||||
printV('Error on periodic solana refresh: $e');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -122,9 +122,13 @@ class CWSolana extends Solana {
|
||||
return CryptoCurrency.sol;
|
||||
}
|
||||
|
||||
return (wallet as SolanaWallet).splTokenCurrencies.firstWhere(
|
||||
(element) => transaction.amount.currency.symbol == element.symbol,
|
||||
);
|
||||
final token = (wallet as SolanaWallet).splTokenBySymbol(transaction.amount.currency.symbol);
|
||||
|
||||
if (token == null) {
|
||||
throw StateError('No SPL token for symbol ${transaction.amount.currency.symbol}');
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -401,6 +405,15 @@ class CWSolana extends Solana {
|
||||
}
|
||||
|
||||
await Future.wait(tokenChecks);
|
||||
|
||||
final discoveredMints = result.newTokens
|
||||
.where((item) => item.token.enabled)
|
||||
.map((item) => item.token.mintAddress)
|
||||
.toList();
|
||||
|
||||
if (discoveredMints.isNotEmpty) {
|
||||
await wallet.updateSPLTokenTransactions(specificMints: discoveredMints);
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import 'package:cake_wallet/zano/zano.dart';
|
||||
import 'package:cw_core/amount/money.dart';
|
||||
import 'package:cw_core/crypto_amount_format.dart';
|
||||
import 'package:cw_core/transaction_history.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cake_wallet/store/app_store.dart';
|
||||
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
|
||||
@@ -498,7 +497,6 @@ abstract class BalanceViewModelBase with Store {
|
||||
final record = wallet.balance[curr]!;
|
||||
final available = record.available - (record.secondAvailable ?? Money.zero(curr));
|
||||
final price = fiatConversionStore.prices[curr] ?? 0;
|
||||
printV(record.available);
|
||||
ret += double.tryParse(calculateFiatAmount(price: price, cryptoAmount: available.toString())
|
||||
.replaceAll(",", "")) ??
|
||||
0;
|
||||
|
||||
@@ -227,9 +227,10 @@ abstract class DashboardViewModelBase with Store {
|
||||
1;
|
||||
} catch (_) {}
|
||||
} else {
|
||||
confirmations = appStore.wallet!.transactionHistory.transactions.values
|
||||
.map((item) => item.isPending)
|
||||
.fold(0, (val, pending) => pending ? val + 1 : val);
|
||||
final pendingCount = appStore.wallet!.transactionHistory.transactions.values
|
||||
.where((item) => item.isPending)
|
||||
.length;
|
||||
confirmations = pendingCount + 1;
|
||||
}
|
||||
return length * confirmations;
|
||||
}, _transactionDisposerCallback, delay: 300);
|
||||
@@ -1331,9 +1332,10 @@ abstract class DashboardViewModelBase with Store {
|
||||
1;
|
||||
} catch (_) {}
|
||||
} else {
|
||||
confirmations = appStore.wallet!.transactionHistory.transactions.values
|
||||
.map((item) => item.isPending)
|
||||
.fold(0, (val, pending) => pending ? val + 1 : val);
|
||||
final pendingCount = appStore.wallet!.transactionHistory.transactions.values
|
||||
.where((item) => item.isPending)
|
||||
.length;
|
||||
confirmations = pendingCount + 1;
|
||||
}
|
||||
return length * confirmations;
|
||||
}, _transactionDisposerCallback, delay: 300);
|
||||
|
||||
@@ -195,7 +195,7 @@ class TransactionListItem extends ActionListItem with Keyable {
|
||||
case WalletType.decred:
|
||||
case WalletType.zcash:
|
||||
amount = calculateFiatAmountRaw(
|
||||
cryptoAmount: double.parse(transaction.amount.toString()),
|
||||
cryptoAmount: transaction.amount.toDouble(),
|
||||
price: price,
|
||||
).withLocalSeperator(_appStore.settingsStore.languageCode);
|
||||
case WalletType.ethereum:
|
||||
@@ -206,15 +206,15 @@ class TransactionListItem extends ActionListItem with Keyable {
|
||||
final asset = assetOfTransaction;
|
||||
final price = balanceViewModel.fiatConversionStore.prices[asset];
|
||||
amount = calculateFiatAmountRaw(
|
||||
cryptoAmount: double.parse(transaction.amount.toString()),
|
||||
cryptoAmount: transaction.amount.toDouble(),
|
||||
price: price,
|
||||
).withLocalSeperator(_appStore.settingsStore.languageCode);
|
||||
break;
|
||||
case WalletType.solana:
|
||||
final asset = solana!.assetOfTransaction(balanceViewModel.wallet, transaction);
|
||||
final asset = assetOfTransaction;
|
||||
final price = balanceViewModel.fiatConversionStore.prices[asset];
|
||||
amount = calculateFiatAmountRaw(
|
||||
cryptoAmount: double.parse(transaction.amount.toString()),
|
||||
cryptoAmount: transaction.amount.toDouble(),
|
||||
price: price,
|
||||
).withLocalSeperator(_appStore.settingsStore.languageCode);
|
||||
break;
|
||||
@@ -222,7 +222,7 @@ class TransactionListItem extends ActionListItem with Keyable {
|
||||
final asset = tron!.assetOfTransaction(balanceViewModel.wallet, transaction);
|
||||
final price = balanceViewModel.fiatConversionStore.prices[asset];
|
||||
amount = calculateFiatAmountRaw(
|
||||
cryptoAmount: double.parse(transaction.amount.toString()),
|
||||
cryptoAmount: transaction.amount.toDouble(),
|
||||
price: price,
|
||||
).withLocalSeperator(_appStore.settingsStore.languageCode);
|
||||
break;
|
||||
@@ -234,7 +234,7 @@ class TransactionListItem extends ActionListItem with Keyable {
|
||||
}
|
||||
final price = balanceViewModel.fiatConversionStore.prices[asset];
|
||||
amount = calculateFiatAmountRaw(
|
||||
cryptoAmount: double.parse(transaction.amount.toString()),
|
||||
cryptoAmount: transaction.amount.toDouble(),
|
||||
price: price,
|
||||
).withLocalSeperator(_appStore.settingsStore.languageCode);
|
||||
break;
|
||||
|
||||
Reference in New Issue
Block a user