mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-23 06:38:35 +02:00
Cw 453 (#1306)
* feat: rebase btc-addr-types, migrate to bitcoin_base * feat: allow scanning elect-rs using get_tweaks * feat: scanning and adding addresses working with getTweaks, add btc SP address type * chore: pubspec.lock * chore: pubspec.lock * fix: scan when switching, fix multiple unspents in same tx * fix: initial scan * fix: initial scan * fix: scanning issues * fix: sync, storing silent unspents * chore: deps * fix: label issues, clear spent utxo * chore: deps * fix: build * fix: missing types * feat: new electrs API & changes, fixes for last block scanning * feat: Scan Silent Payments homepage toggle * chore: build configure * feat: generic fixes, testnet UI improvements, useSSL on bitcoin nodes * fix: invalid Object in sendData * feat: improve addresses page & address book displays * feat: silent payments labeled addresses disclaimer * fix: missing i18n * chore: print * feat: single block scan, rescan by date working for btc mainnet * feat: new cake features page replace market page, move sp scan toggle, auto switch node pop up alert * feat: delete silent addresses * fix: red dot in non ssl nodes * fix: inconsistent connection states, fix tx history * fix: tx & balance displays, cpfp sending * feat: new rust lib * chore: node path * fix: check node based on network * fix: missing txcount from addresses * style: padding in feature page cards * fix: restore not getting all wallet addresses by type * fix: auto switch node broken * fix: silent payment txs not being restored * feat: change scanning to subscription model, sync improvements * fix: scan re-subscription * fix: default nodes * fix: improve scanning by date, fix single block scan * refactor: common function for input tx selection * fix: nodes & build * fix: send all with multiple outs * refactor: unchanged file * Update pr_test_build.yml * chore: upgrade * chore: merge changes * refactor: unchanged files [skip ci] * fix: scan fixes, add date, allow sending while scanning * feat: sync fixes, sp settings * feat: fix resyncing * fix: date from height logic, status disconnected & chain tip get * fix: params * feat: electrum migration if using cake electrum * fix nodes update versions * re-enable tron * update sp_scanner to work on iOS [skip ci] * fix: wrong socket for old electrum nodes * Fix unchecked wallet type call * fix: double balance * feat: node domain * fix: menu name * fix: update tip on set scanning * fix: connection switching back and forth * feat: check if node is electrs, and supports sp * chore: fix build * minor enhancements * fixes and enhancements * solve conflicts with main * fix: status toggle * minor enhancement * Monero.com fixes * update sp_scanner to include windows and linux --------- Co-authored-by: Omar Hatem <omarh.ismail1@gmail.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
import 'package:cake_wallet/entities/auto_generate_subaddress_status.dart';
|
||||
import 'package:cake_wallet/entities/contact_base.dart';
|
||||
import 'package:cake_wallet/entities/wallet_contact.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
@@ -40,16 +41,19 @@ abstract class ContactListViewModelBase with Store {
|
||||
});
|
||||
} else if (info.addresses?.isNotEmpty == true) {
|
||||
info.addresses!.forEach((address, label) {
|
||||
if (label.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final name = _createName(info.name, label);
|
||||
walletContacts.add(WalletContact(
|
||||
address,
|
||||
name,
|
||||
walletTypeToCryptoCurrency(info.type),
|
||||
walletTypeToCryptoCurrency(info.type,
|
||||
isTestnet:
|
||||
info.network == null ? false : info.network!.toLowerCase().contains("testnet")),
|
||||
));
|
||||
// Only one contact address per wallet
|
||||
return;
|
||||
});
|
||||
} else if (info.address != null) {
|
||||
} else {
|
||||
walletContacts.add(WalletContact(
|
||||
info.address,
|
||||
info.name,
|
||||
@@ -64,7 +68,9 @@ abstract class ContactListViewModelBase with Store {
|
||||
}
|
||||
|
||||
String _createName(String walletName, String label) {
|
||||
return label.isNotEmpty ? '$walletName ($label)' : walletName;
|
||||
return label.isNotEmpty
|
||||
? '$walletName (${label.replaceAll(RegExp(r'active', caseSensitive: false), S.current.active).replaceAll(RegExp(r'silent payments', caseSensitive: false), S.current.silent_payments)})'
|
||||
: walletName;
|
||||
}
|
||||
|
||||
final bool isAutoGenerateEnabled;
|
||||
|
||||
@@ -60,6 +60,9 @@ abstract class BalanceViewModelBase with Store {
|
||||
@observable
|
||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
|
||||
|
||||
@computed
|
||||
bool get hasSilentPayments => wallet.type == WalletType.bitcoin;
|
||||
|
||||
@computed
|
||||
double get price {
|
||||
final price = fiatConvertationStore.prices[appStore.wallet!.currency];
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import 'package:cake_wallet/ionia/ionia_service.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'cake_features_view_model.g.dart';
|
||||
|
||||
class CakeFeaturesViewModel = CakeFeaturesViewModelBase with _$CakeFeaturesViewModel;
|
||||
|
||||
abstract class CakeFeaturesViewModelBase with Store {
|
||||
final IoniaService _ioniaService;
|
||||
|
||||
CakeFeaturesViewModelBase(this._ioniaService);
|
||||
|
||||
Future<bool> isIoniaUserAuthenticated() async {
|
||||
return await _ioniaService.isLogined();
|
||||
}
|
||||
}
|
||||
@@ -45,6 +45,7 @@ import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:eth_sig_util/util/utils.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
@@ -201,6 +202,14 @@ abstract class DashboardViewModelBase with Store {
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (hasSilentPayments) {
|
||||
silentPaymentsScanningActive = bitcoin!.getScanningActive(wallet);
|
||||
|
||||
reaction((_) => wallet.syncStatus, (SyncStatus syncStatus) {
|
||||
silentPaymentsScanningActive = bitcoin!.getScanningActive(wallet);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@observable
|
||||
@@ -287,11 +296,36 @@ abstract class DashboardViewModelBase with Store {
|
||||
@observable
|
||||
WalletBase<Balance, TransactionHistoryBase<TransactionInfo>, TransactionInfo> wallet;
|
||||
|
||||
bool get hasRescan => wallet.type == WalletType.monero || wallet.type == WalletType.haven;
|
||||
@computed
|
||||
bool get isTestnet => wallet.type == WalletType.bitcoin && bitcoin!.isTestnet(wallet);
|
||||
|
||||
@computed
|
||||
bool get hasRescan =>
|
||||
wallet.type == WalletType.bitcoin ||
|
||||
wallet.type == WalletType.monero ||
|
||||
wallet.type == WalletType.haven;
|
||||
|
||||
@computed
|
||||
bool get hasSilentPayments => wallet.type == WalletType.bitcoin;
|
||||
|
||||
@computed
|
||||
bool get showSilentPaymentsCard => hasSilentPayments && settingsStore.silentPaymentsCardDisplay;
|
||||
|
||||
final KeyService keyService;
|
||||
final SharedPreferences sharedPreferences;
|
||||
|
||||
@observable
|
||||
bool silentPaymentsScanningActive = false;
|
||||
|
||||
@action
|
||||
void setSilentPaymentsScanning(bool active) {
|
||||
silentPaymentsScanningActive = active;
|
||||
|
||||
if (hasSilentPayments) {
|
||||
bitcoin!.setScanningActive(wallet, active);
|
||||
}
|
||||
}
|
||||
|
||||
BalanceViewModel balanceViewModel;
|
||||
|
||||
AppStore appStore;
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import 'package:cake_wallet/ionia/ionia_service.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'market_place_view_model.g.dart';
|
||||
|
||||
class MarketPlaceViewModel = MarketPlaceViewModelBase with _$MarketPlaceViewModel;
|
||||
|
||||
abstract class MarketPlaceViewModelBase with Store {
|
||||
final IoniaService _ioniaService;
|
||||
|
||||
MarketPlaceViewModelBase(this._ioniaService);
|
||||
|
||||
|
||||
Future<bool> isIoniaUserAuthenticated() async {
|
||||
return await _ioniaService.isLogined();
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'rescan_view_model.g.dart';
|
||||
@@ -8,11 +10,12 @@ class RescanViewModel = RescanViewModelBase with _$RescanViewModel;
|
||||
enum RescanWalletState { rescaning, none }
|
||||
|
||||
abstract class RescanViewModelBase with Store {
|
||||
RescanViewModelBase(this._wallet)
|
||||
: state = RescanWalletState.none,
|
||||
isButtonEnabled = false;
|
||||
RescanViewModelBase(this.wallet)
|
||||
: state = RescanWalletState.none,
|
||||
isButtonEnabled = false,
|
||||
doSingleScan = false;
|
||||
|
||||
final WalletBase _wallet;
|
||||
final WalletBase wallet;
|
||||
|
||||
@observable
|
||||
RescanWalletState state;
|
||||
@@ -20,11 +23,21 @@ abstract class RescanViewModelBase with Store {
|
||||
@observable
|
||||
bool isButtonEnabled;
|
||||
|
||||
@observable
|
||||
bool doSingleScan;
|
||||
|
||||
@computed
|
||||
bool get isSilentPaymentsScan => wallet.type == WalletType.bitcoin;
|
||||
|
||||
@action
|
||||
Future<void> rescanCurrentWallet({required int restoreHeight}) async {
|
||||
state = RescanWalletState.rescaning;
|
||||
await _wallet.rescan(height: restoreHeight);
|
||||
_wallet.transactionHistory.clear();
|
||||
if (wallet.type != WalletType.bitcoin) {
|
||||
wallet.rescan(height: restoreHeight);
|
||||
wallet.transactionHistory.clear();
|
||||
} else {
|
||||
bitcoin!.rescan(wallet, height: restoreHeight, doSingleScan: doSingleScan);
|
||||
}
|
||||
state = RescanWalletState.none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,7 +229,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
isFiatDisabled ? '' : pendingTransactionFeeFiatAmount + ' ' + fiat.title;
|
||||
|
||||
@computed
|
||||
bool get isReadyForSend => wallet.syncStatus is SyncedSyncStatus;
|
||||
bool get isReadyForSend =>
|
||||
wallet.syncStatus is SyncedSyncStatus ||
|
||||
// If silent payments scanning, can still send payments
|
||||
(wallet.type == WalletType.bitcoin && wallet.syncStatus is SyncingSyncStatus);
|
||||
|
||||
@computed
|
||||
List<Template> get templates => sendTemplateViewModel.templates
|
||||
@@ -599,6 +602,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
walletType == WalletType.litecoin ||
|
||||
walletType == WalletType.bitcoinCash) {
|
||||
if (error is TransactionWrongBalanceException) {
|
||||
if (error.amount != null)
|
||||
return S.current
|
||||
.tx_wrong_balance_with_amount_exception(currency.toString(), error.amount.toString());
|
||||
|
||||
return S.current.tx_wrong_balance_exception(currency.toString());
|
||||
}
|
||||
if (error is TransactionNoInputsException) {
|
||||
@@ -625,9 +632,15 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
if (error is TransactionCommitFailedVoutNegative) {
|
||||
return S.current.tx_rejected_vout_negative;
|
||||
}
|
||||
if (error is TransactionCommitFailedBIP68Final) {
|
||||
return S.current.tx_rejected_bip68_final;
|
||||
}
|
||||
if (error is TransactionNoDustOnChangeException) {
|
||||
return S.current.tx_commit_exception_no_dust_on_change(error.min, error.max);
|
||||
}
|
||||
if (error is TransactionInputNotSupported) {
|
||||
return S.current.tx_invalid_input;
|
||||
}
|
||||
}
|
||||
|
||||
return errorMessage;
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/store/settings_store.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'silent_payments_settings_view_model.g.dart';
|
||||
|
||||
class SilentPaymentsSettingsViewModel = SilentPaymentsSettingsViewModelBase
|
||||
with _$SilentPaymentsSettingsViewModel;
|
||||
|
||||
abstract class SilentPaymentsSettingsViewModelBase with Store {
|
||||
SilentPaymentsSettingsViewModelBase(this._settingsStore, this._wallet);
|
||||
|
||||
final SettingsStore _settingsStore;
|
||||
final WalletBase _wallet;
|
||||
|
||||
@computed
|
||||
bool get silentPaymentsCardDisplay => _settingsStore.silentPaymentsCardDisplay;
|
||||
|
||||
@computed
|
||||
bool get silentPaymentsAlwaysScan => _settingsStore.silentPaymentsAlwaysScan;
|
||||
|
||||
@action
|
||||
void setSilentPaymentsCardDisplay(bool value) {
|
||||
_settingsStore.silentPaymentsCardDisplay = value;
|
||||
}
|
||||
|
||||
@action
|
||||
void setSilentPaymentsAlwaysScan(bool value) {
|
||||
_settingsStore.silentPaymentsAlwaysScan = value;
|
||||
if (value) bitcoin!.setScanningActive(_wallet, true);
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,8 @@ abstract class UnspentCoinsItemBase with Store {
|
||||
required this.isChange,
|
||||
required this.amountRaw,
|
||||
required this.vout,
|
||||
required this.keyImage
|
||||
required this.keyImage,
|
||||
required this.isSilentPayment,
|
||||
});
|
||||
|
||||
@observable
|
||||
@@ -47,4 +48,7 @@ abstract class UnspentCoinsItemBase with Store {
|
||||
|
||||
@observable
|
||||
String? keyImage;
|
||||
|
||||
@observable
|
||||
bool isSilentPayment;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
import 'package:cake_wallet/monero/monero.dart';
|
||||
import 'package:cake_wallet/utils/exception_handler.dart';
|
||||
import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_item.dart';
|
||||
import 'package:cw_core/unspent_coins_info.dart';
|
||||
import 'package:cw_core/unspent_transaction_output.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
@@ -86,22 +88,33 @@ abstract class UnspentCoinsListViewModelBase with Store {
|
||||
@action
|
||||
void _updateUnspentCoinsInfo() {
|
||||
_items.clear();
|
||||
_items.addAll(_getUnspents().map((elem) {
|
||||
final info =
|
||||
getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage);
|
||||
|
||||
return UnspentCoinsItem(
|
||||
address: elem.address,
|
||||
amount: '${formatAmountToString(elem.value)} ${wallet.currency.title}',
|
||||
hash: elem.hash,
|
||||
isFrozen: info.isFrozen,
|
||||
note: info.note,
|
||||
isSending: info.isSending,
|
||||
amountRaw: elem.value,
|
||||
vout: elem.vout,
|
||||
keyImage: elem.keyImage,
|
||||
isChange: elem.isChange,
|
||||
);
|
||||
}));
|
||||
List<UnspentCoinsItem> unspents = [];
|
||||
_getUnspents().forEach((elem) {
|
||||
try {
|
||||
final info =
|
||||
getUnspentCoinInfo(elem.hash, elem.address, elem.value, elem.vout, elem.keyImage);
|
||||
|
||||
unspents.add(UnspentCoinsItem(
|
||||
address: elem.address,
|
||||
amount: '${formatAmountToString(elem.value)} ${wallet.currency.title}',
|
||||
hash: elem.hash,
|
||||
isFrozen: info.isFrozen,
|
||||
note: info.note,
|
||||
isSending: info.isSending,
|
||||
amountRaw: elem.value,
|
||||
vout: elem.vout,
|
||||
keyImage: elem.keyImage,
|
||||
isChange: elem.isChange,
|
||||
isSilentPayment: info.isSilentPayment ?? false,
|
||||
));
|
||||
} catch (e, s) {
|
||||
print(s);
|
||||
print(e.toString());
|
||||
ExceptionHandler.onError(FlutterErrorDetails(exception: e, stack: s));
|
||||
}
|
||||
});
|
||||
|
||||
_items.addAll(unspents);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
import 'package:cake_wallet/utils/list_item.dart';
|
||||
|
||||
class WalletAddressListHeader extends ListItem {}
|
||||
class WalletAddressListHeader extends ListItem {
|
||||
final String? title;
|
||||
WalletAddressListHeader({this.title});
|
||||
}
|
||||
|
||||
@@ -8,8 +8,10 @@ class WalletAddressListItem extends ListItem {
|
||||
this.name,
|
||||
this.txCount,
|
||||
this.balance,
|
||||
this.isChange = false})
|
||||
: super();
|
||||
this.isChange = false,
|
||||
// Address that is only ever used once, shouldn't be used to receive funds, copy and paste, share etc
|
||||
this.isOneTimeReceiveAddress = false,
|
||||
}) : super();
|
||||
|
||||
final int? id;
|
||||
final bool isPrimary;
|
||||
@@ -18,7 +20,8 @@ class WalletAddressListItem extends ListItem {
|
||||
final int? txCount;
|
||||
final String? balance;
|
||||
final bool isChange;
|
||||
final bool? isOneTimeReceiveAddress;
|
||||
|
||||
@override
|
||||
String toString() => name ?? address;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -334,20 +334,55 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
}
|
||||
|
||||
if (isElectrumWallet) {
|
||||
final addressItems = bitcoin!.getSubAddresses(wallet).map((subaddress) {
|
||||
final isPrimary = subaddress.id == 0;
|
||||
if (bitcoin!.hasSelectedSilentPayments(wallet)) {
|
||||
final addressItems = bitcoin!.getSilentPaymentAddresses(wallet).map((address) {
|
||||
final isPrimary = address.id == 0;
|
||||
|
||||
return WalletAddressListItem(
|
||||
id: subaddress.id,
|
||||
return WalletAddressListItem(
|
||||
id: address.id,
|
||||
isPrimary: isPrimary,
|
||||
name: subaddress.name,
|
||||
address: subaddress.address,
|
||||
txCount: subaddress.txCount,
|
||||
name: address.name,
|
||||
address: address.address,
|
||||
txCount: address.txCount,
|
||||
balance: AmountConverter.amountIntToString(
|
||||
walletTypeToCryptoCurrency(type), subaddress.balance),
|
||||
isChange: subaddress.isChange);
|
||||
});
|
||||
addressList.addAll(addressItems);
|
||||
walletTypeToCryptoCurrency(type), address.balance),
|
||||
isChange: address.isChange,
|
||||
);
|
||||
});
|
||||
addressList.addAll(addressItems);
|
||||
addressList.add(WalletAddressListHeader(title: S.current.received));
|
||||
|
||||
final receivedAddressItems =
|
||||
bitcoin!.getSilentPaymentReceivedAddresses(wallet).map((address) {
|
||||
return WalletAddressListItem(
|
||||
id: address.id,
|
||||
isPrimary: false,
|
||||
name: address.name,
|
||||
address: address.address,
|
||||
txCount: address.txCount,
|
||||
balance: AmountConverter.amountIntToString(
|
||||
walletTypeToCryptoCurrency(type), address.balance),
|
||||
isChange: address.isChange,
|
||||
isOneTimeReceiveAddress: true,
|
||||
);
|
||||
});
|
||||
addressList.addAll(receivedAddressItems);
|
||||
} else {
|
||||
final addressItems = bitcoin!.getSubAddresses(wallet).map((subaddress) {
|
||||
final isPrimary = subaddress.id == 0;
|
||||
|
||||
return WalletAddressListItem(
|
||||
id: subaddress.id,
|
||||
isPrimary: isPrimary,
|
||||
name: subaddress.name,
|
||||
address: subaddress.address,
|
||||
txCount: subaddress.txCount,
|
||||
balance: AmountConverter.amountIntToString(
|
||||
walletTypeToCryptoCurrency(type), subaddress.balance),
|
||||
isChange: subaddress.isChange);
|
||||
});
|
||||
addressList.addAll(addressItems);
|
||||
}
|
||||
}
|
||||
|
||||
if (wallet.type == WalletType.ethereum) {
|
||||
@@ -416,9 +451,14 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
wallet.type == WalletType.litecoin ||
|
||||
wallet.type == WalletType.bitcoinCash;
|
||||
|
||||
@computed
|
||||
bool get isSilentPayments =>
|
||||
wallet.type == WalletType.bitcoin && bitcoin!.hasSelectedSilentPayments(wallet);
|
||||
|
||||
@computed
|
||||
bool get isAutoGenerateSubaddressEnabled =>
|
||||
_settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled;
|
||||
_settingsStore.autoGenerateSubaddressStatus != AutoGenerateSubaddressStatus.disabled &&
|
||||
!isSilentPayments;
|
||||
|
||||
List<ListItem> _baseItems;
|
||||
|
||||
@@ -478,4 +518,11 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo
|
||||
amount = '';
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
void deleteAddress(ListItem item) {
|
||||
if (wallet.type == WalletType.bitcoin && item is WalletAddressListItem) {
|
||||
bitcoin!.deleteSilentPaymentAddress(wallet, item.address);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ class WalletListItem {
|
||||
required this.key,
|
||||
this.isCurrent = false,
|
||||
this.isEnabled = true,
|
||||
this.isTestnet = false,
|
||||
});
|
||||
|
||||
final String name;
|
||||
@@ -14,4 +15,5 @@ class WalletListItem {
|
||||
final bool isCurrent;
|
||||
final dynamic key;
|
||||
final bool isEnabled;
|
||||
final bool isTestnet;
|
||||
}
|
||||
|
||||
@@ -61,6 +61,7 @@ abstract class WalletListViewModelBase with Store {
|
||||
key: info.key,
|
||||
isCurrent: info.name == _appStore.wallet?.name && info.type == _appStore.wallet?.type,
|
||||
isEnabled: availableWalletTypes.contains(info.type),
|
||||
isTestnet: info.network?.toLowerCase().contains('testnet') ?? false,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user