cleaned up handling of xelis tx details and history syncing

This commit is contained in:
Tritonn204 2025-06-18 12:06:31 -07:00
parent 03665e30f7
commit 194245ba38
9 changed files with 144 additions and 85 deletions

View file

@ -49,7 +49,7 @@ class XelisAssetBalance extends Balance {
}
@override
String get formattedAvailableBalance => formatXelisAmount(balance, decimals: decimals);
String get formattedAvailableBalance => XelisFormatter.formatAmount(balance, decimals: decimals);
@override
String get formattedAdditionalBalance => '0';

View file

@ -1,13 +1,9 @@
import 'dart:math';
class XelisFormatter {
static int _divider = 0;
static int parseXelisAmount(String amount) {
try {
final decimalLength = _getDividerForInput(amount);
_divider = decimalLength;
return (double.parse(amount) * pow(10, decimalLength)).round();
return (double.parse(amount) * pow(10, 8)).round();
} catch (_) {
return 0;
}
@ -15,24 +11,29 @@ class XelisFormatter {
static double parseXelisAmountToDouble(int amount) {
try {
return amount / pow(10, _divider);
return amount / pow(10, 8);
} catch (_) {
return 0;
}
}
static int _getDividerForInput(String amount) {
final result = amount.split('.');
if (result.length > 1) {
final decimalLength = result[1].length;
return decimalLength;
} else {
static int parseAmount(String amount, int decimals) {
try {
return (double.parse(amount) * pow(10, decimals)).round();
} catch (_) {
return 0;
}
}
static double parseAmountToDouble(int amount, int decimals) {
try {
return amount / pow(10, decimals);
} catch (_) {
return 0;
}
}
String formatXelisAmountWithSymbol(
static String formatAmountWithSymbol(
int rawAmount, {
required int decimals,
String? symbol,
@ -43,10 +44,11 @@ String formatXelisAmountWithSymbol(
return '$formatted $sym';
}
String formatXelisAmount(
static String formatAmount(
int rawAmount, {
required int decimals,
}) {
final formatted = rawAmount / pow(10, decimals);
return '$formatted';
}
}

View file

@ -24,7 +24,7 @@ class XelisPendingTransaction with PendingTransaction {
String get amountFormatted => amount.toString();
@override
String get feeFormatted => formatXelisAmount(fee, decimals: 8);
String get feeFormatted => XelisFormatter.formatAmount(fee, decimals: 8);
@override
String get hex => "";

View file

@ -72,7 +72,7 @@ abstract class XelisTransactionHistoryBase
final val = entry.value;
if (val is Map<String, dynamic>) {
final tx = XelisTransactionInfo.fromJson(val);
final tx = XelisTransactionInfo.fromJson(val, isAssetEnabled: (id) => true); // asset filtering needs to happen elsewhere before serializing
addOne(tx);
}
});

View file

@ -4,6 +4,7 @@ import 'package:cw_xelis/xelis_formatting.dart';
import 'package:xelis_dart_sdk/xelis_dart_sdk.dart' as xelis_sdk;
import 'package:cw_xelis/src/api/wallet.dart' as x_wallet;
import 'package:cw_core/format_amount.dart';
import 'package:cw_core/utils/print_verbose.dart';
import 'dart:math';
@ -94,7 +95,7 @@ class XelisTransactionInfo extends TransactionInfo {
@override
String feeFormatted() =>
formatXelisAmountWithSymbol(fee, decimals: 8, symbol: 'XEL');
XelisFormatter.formatAmountWithSymbol(fee, decimals: 8, symbol: 'XEL');
@override
String fiatAmount() => _fiatAmount ?? '';
@ -127,9 +128,7 @@ class XelisTransactionInfo extends TransactionInfo {
for (final transfer in txType.transfers) {
final asset = transfer.asset;
if (!isAssetEnabled(asset)) {
continue;
}
if (!isAssetEnabled(asset)) continue;
assetAmounts[asset] = (assetAmounts[asset] ?? BigInt.zero) + BigInt.from(transfer.amount);
@ -144,43 +143,27 @@ class XelisTransactionInfo extends TransactionInfo {
case xelis_sdk.OutgoingEntry():
direction = TransactionDirection.outgoing;
List<XelisTransfer> localTransfers = [];
final formattedTransfers = <String>[];
for (final transfer in txType.transfers) {
final asset = transfer.asset;
if (!isAssetEnabled(asset)) {
continue;
}
assetAmounts[asset] = (assetAmounts[asset] ?? BigInt.zero) + BigInt.from(transfer.amount);
if (!isAssetEnabled(asset)) continue;
final meta = await wallet.getAssetMetadata(asset: asset);
localTransfers.add(
XelisTransfer(
meta: meta,
amount: transfer.amount
)
);
final formatted = XelisTransfer(meta: meta, amount: transfer.amount).format();
assetDecimals[asset] = meta.decimals;
assetSymbolsMap[asset] = meta.ticker;
}
assetAmounts[asset] = (assetAmounts[asset] ?? BigInt.zero) + BigInt.from(transfer.amount);
final formattedTransfers = txType.transfers
.asMap()
.entries
.map((entry) {
final index = entry.key;
final t = entry.value;
if (txType.transfers.length > 1) {
return "${t.destination} ( ${localTransfers[index].format()} )";
formattedTransfers.add("${transfer.destination} ( $formatted )");
} else {
formattedTransfers.add("${transfer.destination}");
}
}
return "${t.destination}";
})
.where((transfer) => transfer.isNotEmpty)
.toList();
to = formattedTransfers.join('\n\n');
fee = BigInt.from(txType.fee);
break;
@ -248,12 +231,13 @@ class XelisTransactionInfo extends TransactionInfo {
break;
}
final assetIds = assetAmounts.keys.toList();
final filteredAssetIds = assetAmounts.keys.where(isAssetEnabled).toList();
final assetIds = filteredAssetIds;
final assetSymbols = assetIds.map((id) => assetSymbolsMap[id] ?? '???').toList();
final decimals = assetIds.map((id) => assetDecimals[id] ?? 8).toList();
final amounts = assetIds.map((id) => assetAmounts[id]!).toList();
final xelAmount = amounts.isNotEmpty ? amounts[0] : BigInt.zero;
final xelAmount = assetAmounts[xelis_sdk.xelisAsset] ?? BigInt.zero;
return XelisTransactionInfo(
id: entry.hash,
@ -271,20 +255,42 @@ class XelisTransactionInfo extends TransactionInfo {
);
}
factory XelisTransactionInfo.fromJson(Map<String, dynamic> data) {
factory XelisTransactionInfo.fromJson(
Map<String, dynamic> data, {
required bool Function(String assetId) isAssetEnabled,
}) {
final allAssetIds = List<String>.from(data['assetIds']);
final allAssetSymbols = List<String>.from(data['assetSymbols']);
final allAssetAmounts = (data['assetAmounts'] as List)
.map<BigInt>((val) => BigInt.parse(val.toString()))
.toList();
final allDecimals = List<int>.from(data['decimals']);
final filteredIndices = <int>[];
for (int i = 0; i < allAssetIds.length; i++) {
if (isAssetEnabled(allAssetIds[i])) {
filteredIndices.add(i);
}
}
final assetIds = filteredIndices.map((i) => allAssetIds[i]).toList();
final assetSymbols = filteredIndices.map((i) => allAssetSymbols[i]).toList();
final assetAmounts = filteredIndices.map((i) => allAssetAmounts[i]).toList();
final decimals = filteredIndices.map((i) => allDecimals[i]).toList();
final xelAmount = assetAmounts.isNotEmpty ? assetAmounts[0] : BigInt.zero;
return XelisTransactionInfo(
id: data['id'] as String,
height: data['height'] as int,
decimals: List<int>.from(data['decimals']),
assetAmounts: (data['assetAmounts'] as List)
.map<BigInt>((val) => BigInt.parse(val.toString()))
.toList(),
xelAmount: BigInt.parse(data['xelAmount']),
decimals: decimals,
assetAmounts: assetAmounts,
xelAmount: xelAmount,
xelFee: BigInt.parse(data['xelFee']),
direction: parseTransactionDirectionFromInt(data['direction'] as int),
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
assetSymbols: List<String>.from(data['assetSymbols']),
assetIds: List<String>.from(data['assetIds']),
assetSymbols: assetSymbols,
assetIds: assetIds,
to: data['to'],
from: data['from'],
);

View file

@ -18,6 +18,7 @@ import 'package:cw_core/node.dart';
import 'package:cw_core/encryption_file_utils.dart';
import 'package:cw_core/pathForWallet.dart';
import 'package:cw_xelis/xelis_formatting.dart';
import 'package:cw_xelis/xelis_exception.dart';
import 'package:cw_xelis/xelis_asset_balance.dart';
import 'package:cw_xelis/src/api/wallet.dart' as x_wallet;
@ -342,28 +343,74 @@ abstract class XelisWalletBase
}
@override
@action
Future<void> rescan({required int height}) async {
walletInfo.restoreHeight = height;
walletInfo.isRecovery = true;
syncStatus = AttemptingSyncStatus();
balance.clear();
final curr = isTestnet ? CryptoCurrency.xet : CryptoCurrency.xel;
balance[curr] = XelisAssetBalance.zero(symbol: isTestnet ? "XET" : "XEL");
await _libWallet.rescan(topoheight: BigInt.from(pruneHeight > height ? pruneHeight : height));
await walletInfo.save();
await connectToNode(node: currentNode!);
syncStatus = SyncedSyncStatus();
}
List<XelisTransactionInfo> _txBuffer = [];
Timer? _txBatchTimer;
Timer? _txSaveDebounceTimer;
void _bufferTransaction(XelisTransactionInfo tx) {
_txBuffer.add(tx);
if (_txBatchTimer == null || !_txBatchTimer!.isActive) {
_txBatchTimer = Timer(Duration(seconds: 1), () async {
final buffered = List<XelisTransactionInfo>.from(_txBuffer);
_txBuffer.clear();
_txBatchTimer = null;
final txMap = {
for (var tx in buffered) tx.id: tx
};
transactionHistory.addMany(txMap);
});
}
}
Future<void> _flushTransactionBuffer() async {
if (_txBuffer.isEmpty) return;
final toAdd = Map.fromEntries(_txBuffer.map((tx) => MapEntry(tx.id, tx)));
runInAction(() {
transactionHistory.addMany(toAdd);
});
_txBuffer.clear();
_txBatchTimer = null;
}
@action
Future<void> _handleEvent(Event event) async {
switch (event) {
case NewTransaction():
if (!isSupportedEntryType(event.tx)) { break; }
if (!isSupportedEntryType(event.tx)) break;
final transactionInfo = await XelisTransactionInfo.fromTransactionEntry(
event.tx,
wallet: _libWallet,
isAssetEnabled: (id) => findTrackedAssetById(id)?.enabled ?? id == xelis_sdk.xelisAsset
);
transactionHistory.addOne(transactionInfo);
_bufferTransaction(transactionInfo);
_txSaveDebounceTimer?.cancel();
_txSaveDebounceTimer = Timer(Duration(seconds: 1), () async {
await _flushTransactionBuffer();
await transactionHistory.save();
});
break;
case BalanceChanged():
@ -452,6 +499,7 @@ abstract class XelisWalletBase
// _lastSyncError = event.message;
break;
}
await Future.delayed(Duration.zero);
}
Future<void> _fetchPruneHeight() async {
@ -880,7 +928,7 @@ abstract class XelisWalletBase
return XelisPendingTransaction(
txid: txHash,
amount: totalAmountFromCredentials.toString(),
amount: XelisFormatter.formatAmount(totalAmountFromCredentials, decimals: balance[transactionCurrency]!.decimals),
fee: txMap['fee'],
decimals: balance[transactionCurrency]!.decimals,
send: send
@ -906,9 +954,7 @@ abstract class XelisWalletBase
}
final txList = (await _libWallet.allHistory())
.map((jsonStr) => xelis_sdk.TransactionEntry.fromJson(
json.decode(jsonStr),
) as xelis_sdk.TransactionEntry)
.map((jsonStr) => xelis_sdk.TransactionEntry.fromJson(json.decode(jsonStr)) as xelis_sdk.TransactionEntry)
.toList();
final Map<String, XelisTransactionInfo> result = {};
@ -980,6 +1026,8 @@ abstract class XelisWalletBase
}
requestedClose = true;
_isTransactionUpdating = false;
_txSaveDebounceTimer?.cancel();
_txBatchTimer?.cancel();
await _unsubscribeFromWalletEvents();
await _libWallet.close();
x_wallet.dropWallet(wallet: _libWallet);

View file

@ -118,7 +118,7 @@ abstract class OutputBase with Store {
_amount = zano!.formatterParseAmount(amount: _cryptoAmount, currency: cryptoCurrencyHandler());
break;
case WalletType.xelis:
_amount = xelis!.formatterStringDoubleToXelisAmount(_cryptoAmount);
_amount = xelis!.formatterStringDoubleToAmount(_cryptoAmount, currency: cryptoCurrencyHandler());
break;
case WalletType.none:
case WalletType.haven:

View file

@ -110,7 +110,10 @@ class CWXelis extends Xelis {
);
@override
int formatterStringDoubleToXelisAmount(String amount) => XelisFormatter.parseXelisAmount(amount);
int formatterStringDoubleToAmount(String amount, {required CryptoCurrency currency}) {
if (currency is XelisAsset) return XelisFormatter.parseAmount(amount, currency.decimals);
return XelisFormatter.parseXelisAmount(amount);
}
@override
double formatterXelisAmountToDouble(

View file

@ -1451,7 +1451,7 @@ abstract class Xelis {
CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction);
double formatterXelisAmountToDouble({TransactionInfo? transaction, BigInt? amount, int decimals = 8});
int formatterStringDoubleToXelisAmount(String amount);
int formatterStringDoubleToAmount(String amount, {required CryptoCurrency currency});
// CryptoCurrency assetOfTransaction(WalletBase wallet, TransactionInfo transaction);
double? getEstimateFees(WalletBase wallet);