mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-18 05:51:34 +02:00
v6.0.2 Release candidate (#3080)
* v6.0.2 Release candidate * v6.0.2 Release candidate * move btc ln up in the currency picker * disable bsc for swapsxyz * minor [skip ci] * hide dex/rate selection on swap screen * temp fix for evm tokens swaps icons until hive migration is done * minor [skip ci] * disable force dex providers for existing users and let them pick it from settings preference * Show bitcoin address types in swap picker (#3096) * Show bitcoin address types in swap picker * remove wrong conversion to bolt11 [skip ci] * feat: add Lightning invoice resolution and address normalization * automatically select contact wallet address based on the selected swap currency (whether it's bitcoin or lightning) * minor [skip ci] * default to bolt11 invoice for lightning swaps --------- Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com> --------- Co-authored-by: Konstantin Ullrich <konstantinullrich12@gmail.com>
This commit is contained in:
co-authored by
Konstantin Ullrich
parent
82982ae5af
commit
db00a0214a
@@ -0,0 +1,58 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
|
||||
Future<String?> getBolt11FromLightingAddress(String lightningAddress, {int amount = 0}) async {
|
||||
try {
|
||||
final uri = getURlOfLightningAddress(lightningAddress);
|
||||
|
||||
var response = await ProxyWrapper().get(clearnetUri: uri);
|
||||
final responseBody =
|
||||
_LNURLPResponseDTO.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
|
||||
|
||||
response =
|
||||
await ProxyWrapper().get(clearnetUri: Uri.parse("${responseBody.callback}?amount=$amount"));
|
||||
|
||||
final callbackResponseBody =
|
||||
_LNURLPCallbackResponseDTO.fromJson(jsonDecode(response.body) as Map<String, dynamic>);
|
||||
|
||||
return callbackResponseBody.pr;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Uri getURlOfLightningAddress(String lightningAddress) {
|
||||
final parts = lightningAddress.split("@");
|
||||
|
||||
final name = parts.first;
|
||||
final domain = parts.last;
|
||||
return Uri.parse("https://$domain/.well-known/lnurlp/$name");
|
||||
}
|
||||
|
||||
class _LNURLPResponseDTO {
|
||||
final String callback;
|
||||
final int maxSendable;
|
||||
final int minSendable;
|
||||
final String tag;
|
||||
final String metadata;
|
||||
|
||||
const _LNURLPResponseDTO(
|
||||
this.callback, this.maxSendable, this.minSendable, this.tag, this.metadata);
|
||||
|
||||
static _LNURLPResponseDTO fromJson(Map<String, dynamic> map) => _LNURLPResponseDTO(
|
||||
map["callback"] as String,
|
||||
map["maxSendable"] as int,
|
||||
map["minSendable"] as int,
|
||||
map["tag"] as String,
|
||||
map["metadata"] as String);
|
||||
}
|
||||
|
||||
class _LNURLPCallbackResponseDTO {
|
||||
final String pr;
|
||||
|
||||
const _LNURLPCallbackResponseDTO(this.pr);
|
||||
|
||||
static _LNURLPCallbackResponseDTO fromJson(Map<String, dynamic> map) =>
|
||||
_LNURLPCallbackResponseDTO(map["pr"] as String);
|
||||
}
|
||||
@@ -595,6 +595,9 @@ Future<void> defaultSettingsMigration(
|
||||
case 60: // BalanceCardStyleSettings.cardOrder no-op (handled in sqlite.dart)
|
||||
// Do not migrate SQLite here, do that in sqlite.dart in order to prevent runtime
|
||||
// errors, missing row and missing tables.
|
||||
case 61:
|
||||
// reset force dex option only 1 time and let users pick it from the swap settings preference
|
||||
await sharedPreferences.setBool(PreferencesKey.forceDecentralizedExchanges, false);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:cake_wallet/.secrets.g.dart' as secrets;
|
||||
import 'package:cake_wallet/core/lightning_invoice_service.dart';
|
||||
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
|
||||
import 'package:cake_wallet/exchange/limits.dart';
|
||||
import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
|
||||
@@ -196,8 +197,8 @@ class ExolixExchangeProvider extends ExchangeProvider {
|
||||
'coinTo': _normalizeCurrency(_overrideToCryptoCurrency(request.toCurrency, request.toAddress)),
|
||||
'networkFrom': _networkFor(request.fromCurrency),
|
||||
'networkTo': _networkFor(request.toCurrency),
|
||||
'withdrawalAddress': _normalizeAddress(request.toAddress),
|
||||
'refundAddress': _normalizeAddress(request.refundAddress),
|
||||
'withdrawalAddress': await _normalizeAddress(request.toAddress),
|
||||
'refundAddress': await _normalizeAddress(request.refundAddress),
|
||||
'rateType': _getRateType(isFixedRateMode),
|
||||
'apiToken': apiKey,
|
||||
};
|
||||
@@ -213,7 +214,6 @@ class ExolixExchangeProvider extends ExchangeProvider {
|
||||
headers: headers,
|
||||
body: json.encode(body),
|
||||
);
|
||||
|
||||
|
||||
if (response.statusCode == 400) {
|
||||
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
|
||||
@@ -446,8 +446,17 @@ class ExolixExchangeProvider extends ExchangeProvider {
|
||||
}
|
||||
}
|
||||
|
||||
String _normalizeAddress(String address) =>
|
||||
address.startsWith('bitcoincash:') ? address.replaceFirst('bitcoincash:', '') : address;
|
||||
Future<String> _normalizeAddress(String address) async {
|
||||
if (address.startsWith('bitcoincash:'))
|
||||
return address.replaceFirst('bitcoincash:', '');
|
||||
|
||||
// Lightning addresses
|
||||
if(address.contains("@"))
|
||||
return await getBolt11FromLightingAddress(address) ?? address;
|
||||
|
||||
return address;
|
||||
}
|
||||
|
||||
|
||||
static double? _toDouble(dynamic value) {
|
||||
if (value is int) {
|
||||
|
||||
@@ -8,7 +8,6 @@ import 'package:cake_wallet/exchange/trade.dart';
|
||||
import 'package:cake_wallet/exchange/trade_not_created_exception.dart';
|
||||
import 'package:cake_wallet/exchange/trade_request.dart';
|
||||
import 'package:cake_wallet/exchange/trade_state.dart';
|
||||
import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart';
|
||||
import 'package:cw_core/amount_converter.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
@@ -20,7 +19,7 @@ class SwapsXyzExchangeProvider extends ExchangeProvider {
|
||||
static final List<CryptoCurrency> _notSupportedAsSourceToken = [
|
||||
CryptoCurrency.sol,
|
||||
...CryptoCurrency.all.where(
|
||||
(c) => (c.tag ?? '').toUpperCase() == 'SOL',
|
||||
(c) => (c.tag ?? '').toUpperCase() == 'SOL' || c.tag == CryptoCurrency.bnb.tag,
|
||||
),
|
||||
];
|
||||
|
||||
@@ -209,7 +208,7 @@ class SwapsXyzExchangeProvider extends ExchangeProvider {
|
||||
required bool isReceiveAmount}) async {
|
||||
try {
|
||||
|
||||
if(_notSupportedAsSourceToken.contains(from)) {
|
||||
if(_notSupportedAsSourceToken.contains(from) || _notSupportedAsSourceToken.contains(to)) {
|
||||
printV(
|
||||
'fetchRate: source token ${from.title} is not supported as source token');
|
||||
return 0.0;
|
||||
|
||||
@@ -7,7 +7,6 @@ import 'package:cake_wallet/exchange/provider/exchange_provider.dart';
|
||||
import 'package:cake_wallet/exchange/trade.dart';
|
||||
import 'package:cake_wallet/exchange/trade_request.dart';
|
||||
import 'package:cake_wallet/exchange/trade_state.dart';
|
||||
import 'package:cake_wallet/exchange/utils/currency_pairs_utils.dart';
|
||||
import 'package:cake_wallet/wallet_type_utils.dart';
|
||||
import 'package:cw_core/utils/proxy_wrapper.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
|
||||
+1
-1
@@ -324,7 +324,7 @@ Future<void> initializeAppConfigs({bool loadWallet = true}) async {
|
||||
payjoinSessionSource: payjoinSessionSource,
|
||||
anonpayInvoiceInfo: anonpayInvoiceInfo,
|
||||
havenSeedStore: havenSeedStore,
|
||||
initialMigrationVersion: 60,
|
||||
initialMigrationVersion: 61,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@ import 'package:cake_wallet/new-ui/widgets/swap_page/swap_address_selection_moda
|
||||
import 'package:cake_wallet/new-ui/widgets/swap_page/swap_confirm_sheet.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/swap_page/swap_limit_popup.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/swap_page/swap_options_page.dart';
|
||||
import 'package:cake_wallet/new-ui/widgets/swap_page/swap_provider_initial_preference_modal.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
|
||||
import 'package:cake_wallet/src/screens/exchange/widgets/present_provider_picker.dart';
|
||||
import 'package:cake_wallet/src/screens/send/widgets/extract_address_from_parsed.dart';
|
||||
@@ -45,7 +44,6 @@ import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
|
||||
@@ -98,20 +96,20 @@ class _NewSwapPageState extends State<NewSwapPage> {
|
||||
}
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (!widget.exchangeViewModel.decentralizedExchangesPromptDismissed) {
|
||||
showMaterialModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
isDismissible: false,
|
||||
builder: (context) {
|
||||
return SwapProviderInitialPreferenceModal();
|
||||
}).then((val) {
|
||||
widget.exchangeViewModel.dismissDecentralizedExchangesPrompt();
|
||||
if (val is bool && val == true && !widget.exchangeViewModel.forceDecentralizedExchanges) {
|
||||
widget.exchangeViewModel.toggleForceDecentralizedExchanges();
|
||||
}
|
||||
});
|
||||
}
|
||||
// if (!widget.exchangeViewModel.decentralizedExchangesPromptDismissed) {
|
||||
// showMaterialModalBottomSheet(
|
||||
// context: context,
|
||||
// backgroundColor: Colors.transparent,
|
||||
// isDismissible: false,
|
||||
// builder: (context) {
|
||||
// return SwapProviderInitialPreferenceModal();
|
||||
// }).then((val) {
|
||||
// widget.exchangeViewModel.dismissDecentralizedExchangesPrompt();
|
||||
// if (val is bool && val == true && !widget.exchangeViewModel.forceDecentralizedExchanges) {
|
||||
// widget.exchangeViewModel.toggleForceDecentralizedExchanges();
|
||||
// }
|
||||
// });
|
||||
// }
|
||||
|
||||
final depositAddressController = depositKey.currentState!.addressController;
|
||||
final depositAmountController = depositKey.currentState!.amountController;
|
||||
@@ -1182,13 +1180,14 @@ class SwapAmountBoxState extends State<SwapAmountBox> {
|
||||
});
|
||||
if (res != null && res is SwapAddressSelectionResult) {
|
||||
if (widget.isReceiverCard) {
|
||||
widget.exchangeViewModel.selectedAddressBookWallet = res.walletInfo;
|
||||
widget.exchangeViewModel.receiveAddress = res.address!;
|
||||
if (res.walletName != null) {
|
||||
if (res.walletInfo?.name != null) {
|
||||
if (res.accountName != null) {
|
||||
widget.exchangeViewModel.receiveAddressDisplayName =
|
||||
"${res.walletName} → ${res.accountName}";
|
||||
"${res.walletInfo!.name} → ${res.accountName}";
|
||||
} else {
|
||||
widget.exchangeViewModel.receiveAddressDisplayName = res.walletName!;
|
||||
widget.exchangeViewModel.receiveAddressDisplayName = res.walletInfo!.name;
|
||||
}
|
||||
}
|
||||
} else if (res.address == null || res.address!.isEmpty) {
|
||||
@@ -1196,7 +1195,7 @@ class SwapAmountBoxState extends State<SwapAmountBox> {
|
||||
askForRefundAddress();
|
||||
} else {
|
||||
widget.exchangeViewModel.isSendFromExternal = false;
|
||||
switchToDepositWallet(res.walletName!);
|
||||
switchToDepositWallet(res.walletInfo!.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/src/widgets/cake_image_widget.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
|
||||
class AssetsTopBar extends StatelessWidget {
|
||||
const AssetsTopBar({
|
||||
@@ -98,5 +97,6 @@ class AssetsTopBar extends StatelessWidget {
|
||||
if (tabs[selectedTab] == S.current.history) {
|
||||
return S.current.filters;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,23 +70,25 @@ class HistoryTradeTile extends StatelessWidget {
|
||||
}
|
||||
|
||||
String _getIconPath(CryptoCurrency currency) {
|
||||
if (currency.iconPath != null) {
|
||||
return currency.iconPath!;
|
||||
}
|
||||
|
||||
if (currency.name.isNotEmpty) {
|
||||
final currencyFromName = CryptoCurrency.fromString(currency.name);
|
||||
if (currencyFromName.iconPath != null) {
|
||||
return currencyFromName.iconPath!;
|
||||
try { // temporarily until we migrate from hive to sqlite and store the full currency object
|
||||
if (currency.iconPath != null) {
|
||||
return currency.iconPath!;
|
||||
}
|
||||
}
|
||||
|
||||
if (currency.title.isNotEmpty) {
|
||||
final currencyFromTitle = CryptoCurrency.fromString(currency.title);
|
||||
if (currencyFromTitle.iconPath != null) {
|
||||
return currencyFromTitle.iconPath!;
|
||||
if (currency.name.isNotEmpty) {
|
||||
final currencyFromName = CryptoCurrency.fromString(currency.name);
|
||||
if (currencyFromName.iconPath != null) {
|
||||
return currencyFromName.iconPath!;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currency.title.isNotEmpty) {
|
||||
final currencyFromTitle = CryptoCurrency.fromString(currency.title);
|
||||
if (currencyFromTitle.iconPath != null) {
|
||||
return currencyFromTitle.iconPath!;
|
||||
}
|
||||
}
|
||||
} catch (_) {}
|
||||
|
||||
//TODO approporiate fallback
|
||||
return "";
|
||||
|
||||
@@ -6,16 +6,16 @@ import 'package:cake_wallet/src/widgets/new_list_row/new_simple_checkbox.dart';
|
||||
import 'package:cake_wallet/view_model/exchange/exchange_view_model.dart';
|
||||
import 'package:cw_core/currency_for_wallet_type.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart' show WalletType;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
|
||||
class SwapAddressSelectionResult {
|
||||
String? address;
|
||||
String? walletName;
|
||||
WalletInfo? walletInfo;
|
||||
String? accountName;
|
||||
|
||||
SwapAddressSelectionResult({this.address, this.walletName, this.accountName});
|
||||
SwapAddressSelectionResult({this.address, this.walletInfo, this.accountName});
|
||||
}
|
||||
|
||||
class SwapAddressSelectionModal extends StatefulWidget {
|
||||
@@ -52,8 +52,8 @@ class _SwapAddressSelectionModalState extends State<SwapAddressSelectionModal> {
|
||||
? await widget.exchangeViewModel.receiveWallets
|
||||
: await widget.exchangeViewModel.depositWallets;
|
||||
for (final item in items) {
|
||||
if (item.type.toString() == "WalletType.monero") {
|
||||
accounts[item.id] = await widget.exchangeViewModel.addressesForWallet(item);
|
||||
if (item.type == WalletType.monero) {
|
||||
accounts[item.id] = await widget.exchangeViewModel.addressesForAccountsWallet(item);
|
||||
}
|
||||
}
|
||||
setState(() {
|
||||
@@ -107,10 +107,10 @@ class _SwapAddressSelectionModalState extends State<SwapAddressSelectionModal> {
|
||||
|
||||
final String currencyIconPath = walletTypeToCryptoCurrency(item.type).iconPath ?? "";
|
||||
|
||||
final bool hasAccounts =
|
||||
item.type.toString() == "WalletType.monero" && widget.isSelectingReceiver;
|
||||
final bool hasAccounts =
|
||||
item.type == WalletType.monero && widget.isSelectingReceiver;
|
||||
|
||||
List<WalletInfoAddressInfo>? accounts =
|
||||
List<WalletInfoAddressInfo>? accounts =
|
||||
hasAccounts ? this.accounts[item.id] : null;
|
||||
|
||||
return Padding(
|
||||
@@ -123,7 +123,7 @@ class _SwapAddressSelectionModalState extends State<SwapAddressSelectionModal> {
|
||||
onAddressChosen: (address, accountName) {
|
||||
Navigator.of(context).pop(SwapAddressSelectionResult(
|
||||
address: address,
|
||||
walletName: item.name,
|
||||
walletInfo: item,
|
||||
accountName: accountName));
|
||||
},
|
||||
),
|
||||
|
||||
@@ -716,7 +716,7 @@ abstract class SettingsStoreBase with Store {
|
||||
|
||||
reaction(
|
||||
(_) => decentralizedExchangesPromptDismissed,
|
||||
(bool forceDecentralizedExchanges) => _sharedPreferences.setBool(
|
||||
(bool decentralizedExchangesPromptDismissed) => _sharedPreferences.setBool(
|
||||
PreferencesKey.decentralizedExchangesPromptDismissed,
|
||||
decentralizedExchangesPromptDismissed));
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/core/amount_parsing_proxy.dart';
|
||||
import 'package:cake_wallet/core/create_trade_result.dart';
|
||||
import 'package:cake_wallet/core/fiat_conversion_service.dart';
|
||||
import 'package:cake_wallet/core/lightning_invoice_service.dart';
|
||||
import 'package:cake_wallet/core/utilities.dart';
|
||||
import 'package:cake_wallet/core/wallet_change_listener_view_model.dart';
|
||||
import 'package:cake_wallet/entities/calculate_fiat_amount.dart';
|
||||
import 'package:cake_wallet/entities/exchange_api_mode.dart';
|
||||
@@ -420,7 +422,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
||||
return await WalletInfo.selectList("type = ?", [type!.index]);
|
||||
}
|
||||
|
||||
Future<List<WalletInfoAddressInfo>> addressesForWallet(WalletInfo wallet) async {
|
||||
Future<List<WalletInfoAddressInfo>> addressesForAccountsWallet(WalletInfo wallet) async {
|
||||
final List<WalletInfoAddressInfo> ret = [];
|
||||
final addresses = await wallet.getAddressInfos();
|
||||
for (var list in addresses.values) {
|
||||
@@ -554,6 +556,8 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
||||
calculateForcedProviderRate();
|
||||
}
|
||||
|
||||
WalletInfo? selectedAddressBookWallet;
|
||||
|
||||
@observable
|
||||
double forcedProviderRate = 0.0;
|
||||
|
||||
@@ -964,22 +968,6 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
||||
}
|
||||
}
|
||||
|
||||
if (depositCurrency == CryptoCurrency.btcln &&
|
||||
depositAddress == wallet.walletAddresses.addressForExchange) {
|
||||
final invoice = await bitcoin!.getLightningInvoice(wallet, BigInt.zero);
|
||||
if (invoice != null) {
|
||||
depositAddress = invoice;
|
||||
}
|
||||
}
|
||||
|
||||
if (receiveCurrency == CryptoCurrency.btcln &&
|
||||
receiveAddress == wallet.walletAddresses.addressForExchange) {
|
||||
final invoice = await bitcoin!.getLightningInvoice(wallet, BigInt.zero);
|
||||
if (invoice != null) {
|
||||
receiveAddress = invoice;
|
||||
}
|
||||
}
|
||||
|
||||
late final Map<double, ExchangeProvider> providers;
|
||||
if (forcedProvider != null) {
|
||||
providers = {forcedProviderRate: forcedProvider!};
|
||||
@@ -1000,6 +988,32 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with
|
||||
}
|
||||
|
||||
try {
|
||||
if (selectedAddressBookWallet?.type == WalletType.bitcoin) {
|
||||
final _walletAddresses = await selectedAddressBookWallet!.getAddresses();
|
||||
|
||||
// if receive currency is lightning pick the lightning address
|
||||
// if normal bitcoin, then pick the segwit address
|
||||
if (receiveCurrency == CryptoCurrency.btcln) {
|
||||
final lightningAddressOfWallet =
|
||||
_walletAddresses.entries.firstWhereOrNull((e) => e.value.contains("LN"))?.key;
|
||||
if (lightningAddressOfWallet != null) {
|
||||
receiveAddress = lightningAddressOfWallet;
|
||||
}
|
||||
}
|
||||
if (receiveCurrency == CryptoCurrency.btc) {
|
||||
final segwitAddressOfWallet =
|
||||
_walletAddresses.entries.firstWhereOrNull((e) => e.value.contains("P2WPKH"))?.key;
|
||||
if (segwitAddressOfWallet != null) {
|
||||
receiveAddress = segwitAddressOfWallet;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parse Lightning address to bolt11 invoice
|
||||
if (receiveAddress.contains("@")) {
|
||||
receiveAddress = await getBolt11FromLightingAddress(receiveAddress) ?? receiveAddress;
|
||||
}
|
||||
|
||||
// snapshot of providers to avoid concurrent modification issues
|
||||
final providersSnapshot = providers.values.toList();
|
||||
final ratesSnapshot = providers.keys.toList();
|
||||
|
||||
@@ -109,7 +109,7 @@ abstract class OutputBase with Store {
|
||||
if (fiatAmount.split(".").last.length <= digits) {
|
||||
return fiatAmount;
|
||||
}
|
||||
return double.parse(fiatAmount.replaceAll(",", "")).toStringAsPrecision(digits);
|
||||
return double.tryParse(fiatAmount.replaceAll(",", ""))?.toStringAsPrecision(digits) ?? '0.0';
|
||||
}
|
||||
|
||||
@observable
|
||||
|
||||
Reference in New Issue
Block a user