mirror of
https://github.com/Such-Software/hash-wallet.git
synced 2026-10-26 06:08:40 +01:00
zec ironwood (#3425)
* initial * wip * ironwood detection, migration * dev: zec address validation on rt * fix: ironwood spend * ironwood regressions * fix: tx history * bump: zkool2 * fix: tx amounts on pending fix: ironwood pending balance fix: orchard->ironwood migration fix: orchard->any pre ironwood txs fix: ironwood -> any txs fix: WrongSpendAuthorizingKey fix: tx history fix: autoshield to ironwood fix: zkool to latest version break: my sleep schedule :sweating: :worksonmymachine: * thx fable * downgrade frb to 2.11.1 * fix: tx amounts in history * Migating... / migration label for O->I txs
This commit is contained in:
@@ -3,6 +3,7 @@ import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/core/validator.dart';
|
||||
import 'package:cake_wallet/solana/solana.dart';
|
||||
import 'package:cake_wallet/zano/zano.dart';
|
||||
import 'package:cake_wallet/zcash/zcash_network_type.dart';
|
||||
import 'package:cw_core/crypto_currency.dart';
|
||||
import 'package:cw_core/erc20_token.dart';
|
||||
|
||||
@@ -10,8 +11,11 @@ const BEFORE_REGEX = '(^|\\s)';
|
||||
const AFTER_REGEX = '(\$|\\s)';
|
||||
|
||||
class AddressValidator extends TextValidator {
|
||||
AddressValidator({required CryptoCurrency type, bool isTestnet = false})
|
||||
: super(
|
||||
AddressValidator({
|
||||
required CryptoCurrency type,
|
||||
bool isTestnet = false,
|
||||
String? network,
|
||||
}) : super(
|
||||
errorMessage: S.current.error_text_address,
|
||||
useAdditionalValidation: [CryptoCurrency.btc, CryptoCurrency.ltc].contains(type)
|
||||
? (String txt) {
|
||||
@@ -32,7 +36,7 @@ class AddressValidator extends TextValidator {
|
||||
: type == CryptoCurrency.zano
|
||||
? zano?.validateAddress
|
||||
: null,
|
||||
pattern: getPattern(type, isTestnet: isTestnet),
|
||||
pattern: getPattern(type, isTestnet: isTestnet, network: network),
|
||||
length: getLength(type),
|
||||
);
|
||||
|
||||
@@ -61,7 +65,7 @@ class AddressValidator extends TextValidator {
|
||||
CryptoCurrency.btcln,
|
||||
];
|
||||
|
||||
static String getPattern(CryptoCurrency type, {bool isTestnet = false}) {
|
||||
static String? getPattern(CryptoCurrency type, {bool isTestnet = false, String? network}) {
|
||||
var pattern = "";
|
||||
if (type is Erc20Token) {
|
||||
pattern = '0x[0-9a-zA-Z]+';
|
||||
@@ -170,6 +174,9 @@ class AddressValidator extends TextValidator {
|
||||
case CryptoCurrency.hbar:
|
||||
pattern = '[0-9a-zA-Z.]+';
|
||||
case CryptoCurrency.zec:
|
||||
if (ZcashNetworkType.isDevNetwork(network)) {
|
||||
return null;
|
||||
}
|
||||
pattern = '(?:'
|
||||
't1[0-9A-Za-z]{33}'
|
||||
'|t3[0-9A-Za-z]{33}'
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:cake_wallet/src/widgets/section_divider.dart';
|
||||
import 'package:cake_wallet/view_model/dashboard/receive_option_view_model.dart';
|
||||
import 'package:cw_core/receive_page_option.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
|
||||
|
||||
@@ -34,6 +35,7 @@ class _ReceiveAddressTypeSelectorState extends State<ReceiveAddressTypeSelector>
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Observer(builder: (final _) {
|
||||
final commonOptions = widget.receiveOptionViewModel.options
|
||||
.where((element) =>
|
||||
element.isCommon ||
|
||||
@@ -223,6 +225,7 @@ class _ReceiveAddressTypeSelectorState extends State<ReceiveAddressTypeSelector>
|
||||
],
|
||||
)),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -162,6 +162,7 @@ import 'package:cw_core/unspent_coin_type.dart';
|
||||
import 'package:cw_core/utils/print_verbose.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/zcash/zcash_network_type.dart';
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@@ -822,6 +823,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
final isChildWallet = args['isChildWallet'] as bool? ?? false;
|
||||
final useTestnet = args['useTestnet'] as bool;
|
||||
final toggleTestnet = args['toggleTestnet'] as Function(bool? val);
|
||||
final zcashNetwork = args['zcashNetwork'] as int? ?? ZcashNetworkType.mainnet;
|
||||
final setZcashNetwork =
|
||||
args['setZcashNetwork'] as void Function(int network)? ?? (_) {};
|
||||
final restoredWallet = args['restoredWallet'] as RestoredWallet?;
|
||||
|
||||
final viewModelParam = {'type': type, 'isPow': false};
|
||||
@@ -832,6 +836,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
|
||||
isChildWallet: isChildWallet,
|
||||
useTestnet: useTestnet,
|
||||
toggleUseTestnet: toggleTestnet,
|
||||
zcashNetwork: zcashNetwork,
|
||||
setZcashNetwork: setZcashNetwork,
|
||||
advancedPrivacySettingsViewModel:
|
||||
getIt.get<AdvancedPrivacySettingsViewModel>(param1: type),
|
||||
nodeViewModel: getIt.get<NodeCreateOrEditViewModel>(param1: viewModelParam),
|
||||
|
||||
@@ -14,11 +14,13 @@ import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
|
||||
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||
import 'package:cake_wallet/src/widgets/scrollable_with_bottom_section.dart';
|
||||
import 'package:cake_wallet/utils/show_pop_up.dart';
|
||||
import 'package:cake_wallet/utils/feature_flag.dart';
|
||||
import 'package:cake_wallet/view_model/advanced_privacy_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/seed_settings_view_model.dart';
|
||||
import 'package:cake_wallet/view_model/settings/choices_list_item.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/zcash/zcash_network_type.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
|
||||
@@ -28,6 +30,8 @@ class AdvancedPrivacySettingsPage extends BasePage {
|
||||
required this.isChildWallet,
|
||||
required this.useTestnet,
|
||||
required this.toggleUseTestnet,
|
||||
required this.zcashNetwork,
|
||||
required this.setZcashNetwork,
|
||||
required this.advancedPrivacySettingsViewModel,
|
||||
required this.nodeViewModel,
|
||||
required this.seedSettingsViewModel,
|
||||
@@ -44,6 +48,8 @@ class AdvancedPrivacySettingsPage extends BasePage {
|
||||
final bool isChildWallet;
|
||||
final bool useTestnet;
|
||||
final Function(bool? val) toggleUseTestnet;
|
||||
final int zcashNetwork;
|
||||
final void Function(int network) setZcashNetwork;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) => _AdvancedPrivacySettingsBody(
|
||||
@@ -51,6 +57,8 @@ class AdvancedPrivacySettingsPage extends BasePage {
|
||||
isChildWallet,
|
||||
useTestnet,
|
||||
toggleUseTestnet,
|
||||
zcashNetwork,
|
||||
setZcashNetwork,
|
||||
advancedPrivacySettingsViewModel,
|
||||
nodeViewModel,
|
||||
seedSettingsViewModel,
|
||||
@@ -63,6 +71,8 @@ class _AdvancedPrivacySettingsBody extends StatefulWidget {
|
||||
this.isChildWallet,
|
||||
this.useTestnet,
|
||||
this.toggleUseTestnet,
|
||||
this.zcashNetwork,
|
||||
this.setZcashNetwork,
|
||||
this.privacySettingsViewModel,
|
||||
this.nodeViewModel,
|
||||
this.seedTypeViewModel,
|
||||
@@ -76,6 +86,8 @@ class _AdvancedPrivacySettingsBody extends StatefulWidget {
|
||||
final bool isChildWallet;
|
||||
final bool useTestnet;
|
||||
final Function(bool? val) toggleUseTestnet;
|
||||
final int zcashNetwork;
|
||||
final void Function(int network) setZcashNetwork;
|
||||
|
||||
@override
|
||||
_AdvancedPrivacySettingsBodyState createState() => _AdvancedPrivacySettingsBodyState();
|
||||
@@ -87,6 +99,7 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
|
||||
final _formKey = GlobalKey<NodeFormState>();
|
||||
final _passphraseFormKey = GlobalKey<FormState>();
|
||||
bool? testnetValue;
|
||||
int? zcashNetworkValue;
|
||||
|
||||
bool obscurePassphrase = true;
|
||||
|
||||
@@ -112,6 +125,7 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
|
||||
if (testnetValue == null && widget.useTestnet) {
|
||||
testnetValue = widget.useTestnet;
|
||||
}
|
||||
zcashNetworkValue ??= widget.zcashNetwork;
|
||||
|
||||
return Container(
|
||||
padding: EdgeInsets.only(top: 24),
|
||||
@@ -281,6 +295,20 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo
|
||||
],
|
||||
);
|
||||
}),
|
||||
if (FeatureFlag.hasDevOptions &&
|
||||
widget.privacySettingsViewModel.type == WalletType.zcash)
|
||||
SettingsChoicesCell(
|
||||
ChoicesListItem<int>(
|
||||
title: 'Zcash network',
|
||||
items: ZcashNetworkType.values,
|
||||
selectedItem: zcashNetworkValue ?? ZcashNetworkType.mainnet,
|
||||
displayItem: ZcashNetworkType.label,
|
||||
onItemSelected: (final network) {
|
||||
setState(() => zcashNetworkValue = network);
|
||||
widget.setZcashNetwork(network);
|
||||
},
|
||||
),
|
||||
),
|
||||
if (widget.privacySettingsViewModel.type == WalletType.bitcoin ||
|
||||
widget.privacySettingsViewModel.type == WalletType.decred)
|
||||
Builder(builder: (_) {
|
||||
|
||||
@@ -316,6 +316,8 @@ class _WalletNameFormState extends State<WalletNameForm> {
|
||||
"type": _walletNewVM.type,
|
||||
"useTestnet": _walletNewVM.useTestnet,
|
||||
"toggleTestnet": _walletNewVM.toggleUseTestnet,
|
||||
"zcashNetwork": _walletNewVM.zcashNetwork,
|
||||
"setZcashNetwork": _walletNewVM.setZcashNetwork,
|
||||
"isChildWallet": widget.isChildWallet,
|
||||
});
|
||||
},
|
||||
|
||||
@@ -121,7 +121,9 @@ class WalletRestorePage extends BasePage {
|
||||
'isFromRestore': true,
|
||||
'type': walletRestoreViewModel.type,
|
||||
'useTestnet': walletRestoreViewModel.useTestnet,
|
||||
'toggleTestnet': walletRestoreViewModel.toggleUseTestnet
|
||||
'toggleTestnet': walletRestoreViewModel.toggleUseTestnet,
|
||||
'zcashNetwork': walletRestoreViewModel.zcashNetwork,
|
||||
'setZcashNetwork': walletRestoreViewModel.setZcashNetwork,
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
@@ -538,10 +538,16 @@ abstract class DashboardViewModelBase with Store {
|
||||
.map((item) => _txIdentityString(item.transaction.txHash, item.transaction.direction))
|
||||
.toSet();
|
||||
|
||||
transactions.removeWhere(
|
||||
(item) => newKeys
|
||||
.contains(_txIdentityString(item.transaction.txHash, item.transaction.direction)),
|
||||
);
|
||||
transactions.removeWhere((item) {
|
||||
if (wallet.type == WalletType.zcash) {
|
||||
return newTransactions.any(
|
||||
(n) => n.transaction.txHash == item.transaction.txHash,
|
||||
);
|
||||
}
|
||||
return newKeys.contains(
|
||||
_txIdentityString(item.transaction.txHash, item.transaction.direction),
|
||||
);
|
||||
});
|
||||
|
||||
transactions.addAll(newTransactions);
|
||||
// transactions.clear();
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'package:cake_wallet/zcash/zcash.dart';
|
||||
import 'package:cw_core/receive_page_option.dart';
|
||||
import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cw_zcash/cw_zcash.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
part 'receive_option_view_model.g.dart';
|
||||
@@ -18,7 +19,14 @@ abstract class ReceiveOptionViewModelBase with Store {
|
||||
? ReceivePageOption.testnet
|
||||
: _wallet.type == WalletType.zcash
|
||||
? zcash!.getSelectedAddressType(_wallet)
|
||||
: ReceivePageOption.mainnet);
|
||||
: ReceivePageOption.mainnet) {
|
||||
if (_wallet.type == WalletType.zcash) {
|
||||
reaction<bool>(
|
||||
(_) => (_wallet.walletAddresses as ZcashWalletAddresses).ironwoodActive,
|
||||
(_) => _syncZcashShieldedLabel(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final WalletBase _wallet;
|
||||
|
||||
@@ -27,10 +35,21 @@ abstract class ReceiveOptionViewModelBase with Store {
|
||||
@observable
|
||||
ReceivePageOption selectedReceiveOption;
|
||||
|
||||
@computed
|
||||
List<ReceivePageOption> get options => _wallet.walletAddresses.receivePageOptions;
|
||||
|
||||
String get walletTypeString => walletTypeToString(_wallet.type);
|
||||
|
||||
@action
|
||||
void selectReceiveOption(ReceivePageOption option) => selectedReceiveOption = option;
|
||||
|
||||
@action
|
||||
void _syncZcashShieldedLabel() {
|
||||
for (final option in options) {
|
||||
if (option.value == selectedReceiveOption.value) {
|
||||
selectedReceiveOption = option;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,9 @@ class TransactionListItem extends ActionListItem with Keyable {
|
||||
return 'Transaction has missing data';
|
||||
}
|
||||
|
||||
if (transaction.additionalInfo['isIronwoodMigration'] == true) {
|
||||
return 'Migration';
|
||||
}
|
||||
if (transaction.additionalInfo['isAutoShield'] == true) {
|
||||
if (transaction.isPending) {
|
||||
final status = formattedStatus;
|
||||
|
||||
@@ -48,8 +48,11 @@ abstract class SendTemplateViewModelBase with Store {
|
||||
amountParsingProxy: _appStore.amountParsingProxy,
|
||||
);
|
||||
|
||||
AddressValidator get addressValidator =>
|
||||
AddressValidator(type: _wallet.currency, isTestnet: _wallet.isTestnet);
|
||||
AddressValidator get addressValidator => AddressValidator(
|
||||
type: _wallet.currency,
|
||||
isTestnet: _wallet.isTestnet,
|
||||
network: _wallet.walletInfo.network,
|
||||
);
|
||||
|
||||
TemplateValidator get templateValidator => TemplateValidator();
|
||||
|
||||
|
||||
@@ -190,7 +190,10 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
|
||||
// Check if the address is valid for the current currency (except for Zano, which can use handles as addresses)
|
||||
if (selectedCryptoCurrency != CryptoCurrency.zano) {
|
||||
final isValidAddress = AddressValidator(type: selectedCryptoCurrency).isValid(query);
|
||||
final isValidAddress = AddressValidator(
|
||||
type: selectedCryptoCurrency,
|
||||
network: wallet.walletInfo.network,
|
||||
).isValid(query);
|
||||
if (isValidAddress) return null;
|
||||
}
|
||||
|
||||
@@ -317,8 +320,11 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor
|
||||
|
||||
Validator<String> get allAmountValidator => AllAmountValidator();
|
||||
|
||||
Validator<String> get addressValidator =>
|
||||
AddressValidator(type: selectedCryptoCurrency, isTestnet: wallet.isTestnet);
|
||||
Validator<String> get addressValidator => AddressValidator(
|
||||
type: selectedCryptoCurrency,
|
||||
isTestnet: wallet.isTestnet,
|
||||
network: wallet.walletInfo.network,
|
||||
);
|
||||
|
||||
Validator<String> get textValidator => TextValidator();
|
||||
|
||||
|
||||
@@ -428,6 +428,9 @@ abstract class TransactionDetailsViewModelBase with Store {
|
||||
}
|
||||
|
||||
String get formattedTitle {
|
||||
if (transactionInfo.additionalInfo['isIronwoodMigration'] == true) {
|
||||
return 'Migration';
|
||||
}
|
||||
if (transactionInfo.additionalInfo['isAutoShield'] == true) {
|
||||
return S.current.shielding;
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ import 'package:cw_core/wallet_base.dart';
|
||||
import 'package:cw_core/wallet_credentials.dart';
|
||||
import 'package:cw_core/wallet_info.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/zcash/zcash_network_type.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
import 'package:polyseed/polyseed.dart';
|
||||
|
||||
@@ -33,9 +34,13 @@ abstract class WalletCreationVMBase with Store {
|
||||
@observable
|
||||
bool _useTestnet = false;
|
||||
|
||||
int _zcashNetwork = ZcashNetworkType.mainnet;
|
||||
|
||||
@computed
|
||||
bool get useTestnet => _useTestnet;
|
||||
|
||||
int get zcashNetwork => _zcashNetwork;
|
||||
|
||||
@observable
|
||||
String name;
|
||||
|
||||
@@ -252,4 +257,8 @@ abstract class WalletCreationVMBase with Store {
|
||||
void toggleUseTestnet(bool? value) {
|
||||
_useTestnet = value ?? !_useTestnet;
|
||||
}
|
||||
|
||||
void setZcashNetwork(int network) {
|
||||
_zcashNetwork = network;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,6 +146,7 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store {
|
||||
password: walletPassword,
|
||||
mnemonic: newWalletArguments!.mnemonic,
|
||||
passphrase: passphrase,
|
||||
network: zcashNetwork,
|
||||
);
|
||||
case WalletType.decred:
|
||||
return decred!.createDecredNewWalletCredentials(name: name);
|
||||
|
||||
@@ -224,6 +224,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store {
|
||||
password: password,
|
||||
passphrase: passphrase,
|
||||
height: height,
|
||||
network: zcashNetwork,
|
||||
);
|
||||
case WalletType.none:
|
||||
case WalletType.haven:
|
||||
|
||||
+13
-18
@@ -7,12 +7,14 @@ class CWZcash extends Zcash {
|
||||
WalletInfo? walletInfo,
|
||||
String? password,
|
||||
String? mnemonic,
|
||||
required String? passphrase}) {
|
||||
required String? passphrase,
|
||||
int network = 0}) {
|
||||
return ZcashNewWalletCredentials(
|
||||
name: name,
|
||||
passphrase: passphrase,
|
||||
password: password,
|
||||
mnemonic: mnemonic,
|
||||
network: network,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -36,9 +38,10 @@ class CWZcash extends Zcash {
|
||||
required String mnemonic,
|
||||
required String password,
|
||||
String? passphrase,
|
||||
required int? height}) {
|
||||
required int? height,
|
||||
int network = 0}) {
|
||||
return ZcashFromSeedWalletCredentials(
|
||||
name: name, seed: mnemonic, passphrase: passphrase, password: password, height: height);
|
||||
name: name, seed: mnemonic, passphrase: passphrase, password: password, height: height, network: network);
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -161,9 +164,12 @@ class CWZcash extends Zcash {
|
||||
@override
|
||||
ReceivePageOption getSelectedAddressType(Object wallet) {
|
||||
final zcashWallet = wallet as ZcashWallet;
|
||||
final t =
|
||||
(zcashWallet.walletAddresses as ZcashWalletAddresses).walletInfo.addressPageType ?? "";
|
||||
return ZcashReceivePageOption.fromType(ZcashReceivePageOption.typeFromString(t));
|
||||
final addresses = zcashWallet.walletAddresses as ZcashWalletAddresses;
|
||||
final type = ZcashReceivePageOption.typeFromString(addresses.walletInfo.addressPageType ?? "");
|
||||
if (type == ZcashAddressType.shieldedOrchard) {
|
||||
return ZcashReceivePageOption.shieldedOrchard(ironwood: addresses.ironwoodActive);
|
||||
}
|
||||
return ZcashReceivePageOption.fromType(type);
|
||||
}
|
||||
|
||||
bool hasSelectedTransparentAddress(Object wallet) {
|
||||
@@ -172,18 +178,7 @@ class CWZcash extends Zcash {
|
||||
|
||||
@override
|
||||
dynamic getZcashAddressType(ReceivePageOption option) {
|
||||
switch (option) {
|
||||
case ZcashReceivePageOption.unified:
|
||||
return ZcashAddressType.unifiedType;
|
||||
case ZcashReceivePageOption.transparent:
|
||||
return ZcashAddressType.transparent;
|
||||
case ZcashReceivePageOption.shieldedSapling:
|
||||
return ZcashAddressType.shieldedSapling;
|
||||
case ZcashReceivePageOption.shieldedOrchard:
|
||||
return ZcashAddressType.shieldedOrchard;
|
||||
default:
|
||||
throw Exception("Unknown ReceivePageOption!");
|
||||
}
|
||||
return (option as ZcashReceivePageOption).toType();
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
/// Zcash network indices passed from lib/ into cw_zcash.
|
||||
abstract final class ZcashNetworkType {
|
||||
static const int mainnet = 0;
|
||||
static const int testnet = 1;
|
||||
static const int regtest = 2;
|
||||
|
||||
static const List<int> values = [mainnet, testnet, regtest];
|
||||
|
||||
static String label(final int network) => switch (network) {
|
||||
testnet => 'Testnet',
|
||||
regtest => 'Regtest',
|
||||
_ => 'Mainnet',
|
||||
};
|
||||
|
||||
static bool isDevNetwork(final String? network) =>
|
||||
network?.toLowerCase() == 'testnet' || network?.toLowerCase() == 'regtest';
|
||||
}
|
||||
Reference in New Issue
Block a user