fix: crypto amount calculation logic with max decimals (#2697)

* fix: crypto amount calculation logic with max decimals * refactor: remove `_setCryptoNumMaximumFractionDigits` and `_cryptoNumberFormat` to simplify `OutputBase` logic

Konstantin Ullrich committed Dec 1, 2025 at 21:43 UTC 86f30a216cdd82d9fd8bde88fdebf0fbcd468b71
2 files changed +21 -48
cw_core/lib/crypto_currency.dart
+14 -3
@@ -1,6 +1,9 @@
1 import 'package:cw_core/currency.dart';
2 import 'package:cw_core/enumerable_item.dart';
3 import 'package:collection/collection.dart';
4 +import 'package:cw_core/parse_fixed.dart';
5 +
6 +import 'format_fixed.dart';
7
8 class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implements Currency {
9 const CryptoCurrency({
@@ -394,7 +397,15 @@ class CryptoCurrency extends EnumerableItem<int> with Serializable<int> implemen
397 @override
398 String toString() => title;
399
397 - bool titleAndTagEqual(CryptoCurrency other) {
398 - return title == other.title && tag == other.tag;
399 - }
400 + bool titleAndTagEqual(CryptoCurrency other) => title == other.title && tag == other.tag;
401 +
402 + /// Format the raw amount into its decimal representation eg. turn Sats into Bitcoin
403 + String formatAmount(BigInt amount, {int? fractionalDigits, bool trimZeros = true}) =>
404 + formatFixed(amount, decimals, fractionalDigits: fractionalDigits, trimZeros: trimZeros);
405 +
406 + /// Parse the [value] and turn it into the smallest denomination eg. turn Bitcoin into Sats
407 + BigInt parseAmount(String value) => parseFixed(value, decimals);
408 +
409 + /// Try parsing the [value] and turn it into the smallest denomination eg. turn Bitcoin into Sats
410 + BigInt? tryParseAmount(String value) => tryParseFixed(value, decimals);
411 }
lib/view_model/send/output.dart
+7 -45
@@ -19,7 +19,6 @@ import 'package:cw_core/transaction_history.dart';
19 import 'package:cw_core/transaction_info.dart';
20 import 'package:cw_core/utils/print_verbose.dart';
21 import 'package:flutter/material.dart';
22 -import 'package:intl/intl.dart';
22 import 'package:mobx/mobx.dart';
23 import 'package:cw_core/wallet_base.dart';
24 import 'package:cake_wallet/monero/monero.dart';
@@ -42,8 +41,7 @@ class Output = OutputBase with _$Output;
41 abstract class OutputBase with Store {
42 OutputBase(
43 this._wallet, this._settingsStore, this._fiatConversationStore, this.cryptoCurrencyHandler)
45 - : _cryptoNumberFormat = NumberFormat(cryptoNumberPattern),
46 - key = UniqueKey(),
44 + : key = UniqueKey(),
45 sendAll = false,
46 cryptoAmount = '',
47 cryptoFullBalance = '',
@@ -53,7 +51,6 @@ abstract class OutputBase with Store {
51 extractedAddress = '',
52 estimatedFee = '0.0',
53 parsedAddress = ParsedAddress(addresses: []) {
56 - _setCryptoNumMaximumFractionDigits();
54 autorun((_) {
55 final status = _wallet.syncStatus;
56 printV("Sync status changed to $status. Recalculating fees");
@@ -289,7 +286,6 @@ abstract class OutputBase with Store {
286 WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> _wallet;
287 final SettingsStore _settingsStore;
288 final FiatConversionStore _fiatConversationStore;
292 - final NumberFormat _cryptoNumberFormat;
289
290 @action
291 void setSendAll(String fullBalance) {
@@ -302,7 +298,6 @@ abstract class OutputBase with Store {
298 void updateWallet(
299 WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> newWallet) {
300 _wallet = newWallet;
305 - _setCryptoNumMaximumFractionDigits();
301 }
302
303 @action
@@ -355,11 +350,12 @@ abstract class OutputBase with Store {
350 @action
351 void _updateCryptoAmount() {
352 try {
358 - final crypto = double.parse(fiatAmount.replaceAll(',', '.')) /
359 - _fiatConversationStore.prices[cryptoCurrencyHandler()]!;
360 - final cryptoAmountTmp = _cryptoNumberFormat.format(crypto);
361 - if (cryptoAmount != cryptoAmountTmp) {
362 - cryptoAmount = cryptoAmountTmp;
353 + final crypto = (double.parse(fiatAmount.replaceAll(',', '.')) /
354 + _fiatConversationStore.prices[cryptoCurrencyHandler()]!)
355 + .toStringAsFixed(cryptoCurrencyHandler().decimals);
356 +
357 + if (cryptoAmount != crypto) {
358 + cryptoAmount = crypto;
359 }
360 } catch (e) {
361 cryptoAmount = '';
@@ -375,40 +371,6 @@ abstract class OutputBase with Store {
371 return fields;
372 }
373
378 - void _setCryptoNumMaximumFractionDigits() {
379 - var maximumFractionDigits = 0;
380 -
381 - switch (_wallet.type) {
382 - case WalletType.monero:
383 - case WalletType.ethereum:
384 - case WalletType.polygon:
385 - case WalletType.base:
386 - case WalletType.arbitrum:
387 - case WalletType.solana:
388 - case WalletType.tron:
389 - case WalletType.haven:
390 - case WalletType.zano:
391 - case WalletType.nano:
392 - case WalletType.decred:
393 - maximumFractionDigits = 12;
394 - break;
395 - case WalletType.bitcoin:
396 - case WalletType.litecoin:
397 - case WalletType.bitcoinCash:
398 - case WalletType.dogecoin:
399 - maximumFractionDigits = 8;
400 - break;
401 - case WalletType.wownero:
402 - maximumFractionDigits = 11;
403 - break;
404 - case WalletType.none:
405 - case WalletType.banano:
406 - break;
407 - }
408 -
409 - _cryptoNumberFormat.maximumFractionDigits = maximumFractionDigits;
410 - }
411 -
374 Future<void> fetchParsedAddress(BuildContext context) async {
375 final domain = address;
376 final currency = cryptoCurrencyHandler();