From b6244cdd446c3ea03810448eb8fe3306737e2ce3 Mon Sep 17 00:00:00 2001 From: Serhii Date: Thu, 7 Aug 2025 22:07:20 +0300 Subject: [PATCH] CW-1099-Add-Doge-coin-to-Cake-Wallet (#2396) * add wallet create, restore and server integration * update transaction fee calculation * Updated tool/configure.dart * Add dogecoin support to app_config * minor fix [skip ci] * Update model_generator.sh * remove cw_bitcoin import from lib/ directory * update bitcoin base * update default node * add passphrase an QR restore option * add Dogecoin support for message signing * add Dogecoin support across wallet features * fix edit subaddress Updated the isElectrum getter to include WalletType.dogecoin, ensuring Dogecoin wallets are correctly identified as Electrum-compatible. * Update configure.dart * fix: address label not saved * remove unused derivationPath Co-authored-by: Omar Hatem * address review comments * fix generateInitialAddresses type * enable auto switching for Dogecoin nodes --------- Co-authored-by: Omar Hatem --- .gitignore | 1 + android/app/src/main/AndroidManifestBase.xml | 3 + assets/dogecoin_electrum_server_list.yml | 12 ++ cakewallet.bat | 2 +- cw_bitcoin/lib/electrum_wallet.dart | 26 ++- cw_bitcoin/lib/electrum_wallet_addresses.dart | 14 +- .../lib/pending_bitcoin_transaction.dart | 6 +- .../lib/src/bitcoin_cash_wallet.dart | 2 + cw_core/lib/amount_converter.dart | 1 + cw_core/lib/currency_for_wallet_type.dart | 2 + cw_core/lib/node.dart | 2 + cw_core/lib/wallet_type.dart | 18 +- cw_dogecoin/.gitignore | 31 ++++ cw_dogecoin/.metadata | 10 ++ cw_dogecoin/CHANGELOG.md | 3 + cw_dogecoin/LICENSE | 1 + cw_dogecoin/README.md | 39 +++++ cw_dogecoin/analysis_options.yaml | 4 + cw_dogecoin/lib/cw_dogecoin.dart | 6 + .../src/dogecoin_transaction_priority.dart | 52 ++++++ cw_dogecoin/lib/src/dogecoin_wallet.dart | 164 ++++++++++++++++++ .../lib/src/dogecoin_wallet_addresses.dart | 29 ++++ .../dogecoin_wallet_creation_credentials.dart | 38 ++++ .../lib/src/dogecoin_wallet_service.dart | 151 ++++++++++++++++ cw_dogecoin/pubspec.yaml | 81 +++++++++ cw_dogecoin/test/cw_dogecoin_test.dart | 8 + ios/Runner/InfoBase.plist | 19 ++ lib/bitcoin/cw_bitcoin.dart | 12 ++ lib/core/seed_validator.dart | 2 + lib/core/wallet_creation_service.dart | 1 + lib/di.dart | 4 + lib/dogecoin/cw_dogecoin.dart | 48 +++++ lib/dogecoin/dogecoin.dart | 32 ++++ lib/entities/default_settings_migration.dart | 22 +++ lib/entities/node_list.dart | 7 +- lib/entities/preferences_key.dart | 1 + lib/entities/priority_for_wallet_type.dart | 3 + lib/main.dart | 2 +- lib/reactions/on_current_wallet_change.dart | 1 + lib/reactions/wallet_utils.dart | 1 + .../dashboard/widgets/menu_widget.dart | 6 +- lib/store/settings_store.dart | 17 ++ .../advanced_privacy_settings_view_model.dart | 2 + .../dashboard/dashboard_view_model.dart | 1 + .../dashboard/home_settings_view_model.dart | 1 + lib/view_model/dashboard/sign_view_model.dart | 1 + .../dashboard/transaction_list_item.dart | 1 + .../exchange/exchange_view_model.dart | 8 +- .../node_create_or_edit_view_model.dart | 1 + .../restore/wallet_restore_from_qr_code.dart | 3 + lib/view_model/send/fees_view_model.dart | 9 +- lib/view_model/send/output.dart | 5 +- lib/view_model/send/send_view_model.dart | 7 +- .../settings/other_settings_view_model.dart | 6 +- .../settings/privacy_settings_view_model.dart | 1 + .../transaction_details_view_model.dart | 51 ++++++ .../unspent_coins_details_view_model.dart | 6 +- .../unspent_coins_list_view_model.dart | 6 +- ...let_address_edit_or_create_view_model.dart | 3 +- .../wallet_address_list_view_model.dart | 23 ++- lib/view_model/wallet_keys_view_model.dart | 3 + lib/view_model/wallet_new_vm.dart | 8 + lib/view_model/wallet_restore_view_model.dart | 9 + model_generator.sh | 2 +- pubspec_base.yaml | 3 +- scripts/android/pubspec_gen.sh | 2 +- scripts/ios/app_config.sh | 2 +- scripts/linux/app_config.sh | 2 +- scripts/macos/app_config.sh | 2 +- tool/configure.dart | 74 ++++++++ 70 files changed, 1088 insertions(+), 38 deletions(-) create mode 100644 assets/dogecoin_electrum_server_list.yml create mode 100644 cw_dogecoin/.gitignore create mode 100644 cw_dogecoin/.metadata create mode 100644 cw_dogecoin/CHANGELOG.md create mode 100644 cw_dogecoin/LICENSE create mode 100644 cw_dogecoin/README.md create mode 100644 cw_dogecoin/analysis_options.yaml create mode 100644 cw_dogecoin/lib/cw_dogecoin.dart create mode 100644 cw_dogecoin/lib/src/dogecoin_transaction_priority.dart create mode 100644 cw_dogecoin/lib/src/dogecoin_wallet.dart create mode 100644 cw_dogecoin/lib/src/dogecoin_wallet_addresses.dart create mode 100644 cw_dogecoin/lib/src/dogecoin_wallet_creation_credentials.dart create mode 100644 cw_dogecoin/lib/src/dogecoin_wallet_service.dart create mode 100644 cw_dogecoin/pubspec.yaml create mode 100644 cw_dogecoin/test/cw_dogecoin_test.dart create mode 100644 lib/dogecoin/cw_dogecoin.dart create mode 100644 lib/dogecoin/dogecoin.dart diff --git a/.gitignore b/.gitignore index 84a7ecdcd..9482d8f94 100644 --- a/.gitignore +++ b/.gitignore @@ -140,6 +140,7 @@ lib/tron/tron.dart lib/wownero/wownero.dart lib/zano/zano.dart lib/decred/decred.dart +lib/dogecoin/dogecoin.dart ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x.png ios/Runner/Assets.xcassets/AppIcon.appiconset/AppIcon@2x~ipad.png diff --git a/android/app/src/main/AndroidManifestBase.xml b/android/app/src/main/AndroidManifestBase.xml index 8283a7c8c..1bc56c1d5 100644 --- a/android/app/src/main/AndroidManifestBase.xml +++ b/android/app/src/main/AndroidManifestBase.xml @@ -103,6 +103,9 @@ + + + diff --git a/assets/dogecoin_electrum_server_list.yml b/assets/dogecoin_electrum_server_list.yml new file mode 100644 index 000000000..087b0af2b --- /dev/null +++ b/assets/dogecoin_electrum_server_list.yml @@ -0,0 +1,12 @@ +- + uri: dogecoin.stackwallet.com:50022 + is_default: true + useSSL: true + isEnabledForAutoSwitching: true +- + uri: doge.aftrek.org:50002 + useSSL: true + isEnabledForAutoSwitching: true +- + uri: electrum1.cipig.net:20060 + useSSL: true \ No newline at end of file diff --git a/cakewallet.bat b/cakewallet.bat index 1904c5710..b9da8762f 100644 --- a/cakewallet.bat +++ b/cakewallet.bat @@ -1,5 +1,5 @@ @echo off -set cw_win_app_config=--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron +set cw_win_app_config=--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --dogecoin set cw_root=%cd% set cw_archive_name=Cake Wallet.zip set cw_archive_path=%cw_root%\%cw_archive_name% diff --git a/cw_bitcoin/lib/electrum_wallet.dart b/cw_bitcoin/lib/electrum_wallet.dart index 73911469f..b92f4c872 100644 --- a/cw_bitcoin/lib/electrum_wallet.dart +++ b/cw_bitcoin/lib/electrum_wallet.dart @@ -130,6 +130,8 @@ abstract class ElectrumWalletBase as Bip32Slip10Secp256k1; case CryptoCurrency.bch: return bitcoinCashHDWallet(seedBytes); + case CryptoCurrency.doge: + return dogecoinHDWallet(seedBytes); default: throw Exception("Unsupported currency"); } @@ -141,6 +143,9 @@ abstract class ElectrumWalletBase static Bip32Slip10Secp256k1 bitcoinCashHDWallet(Uint8List seedBytes) => Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/44'/145'/0'") as Bip32Slip10Secp256k1; + static Bip32Slip10Secp256k1 dogecoinHDWallet(Uint8List seedBytes) => + Bip32Slip10Secp256k1.fromSeed(seedBytes).derivePath("m/44'/3'/0'") as Bip32Slip10Secp256k1; + static int estimatedTransactionSize(int inputsCount, int outputsCounts) => inputsCount * 68 + outputsCounts * 34 + 10; @@ -629,9 +634,9 @@ abstract class ElectrumWalletBase } } - int get _dustAmount => 546; + int get networkDustAmount => 546; - bool _isBelowDust(int amount) => amount <= _dustAmount && network != BitcoinNetwork.testnet; + bool _isBelowDust(int amount) => amount <= networkDustAmount && network != BitcoinNetwork.testnet; UtxoDetails _createUTXOS({ required bool sendAll, @@ -1685,7 +1690,7 @@ abstract class ElectrumWalletBase var currentFee = allInputsAmount - totalOutAmount; int remainingFee = (newFee - currentFee > 0) ? newFee - currentFee : newFee; - return totalBalance - receiverAmount - remainingFee >= _dustAmount; + return totalBalance - receiverAmount - remainingFee >= networkDustAmount; } Future replaceByFee(String hash, int newFee) async { @@ -1773,10 +1778,10 @@ abstract class ElectrumWalletBase if (isChange) { int outputAmount = output.value.toInt(); - if (outputAmount > _dustAmount) { - int deduction = (outputAmount - _dustAmount >= remainingFee) + if (outputAmount > networkDustAmount) { + int deduction = (outputAmount - networkDustAmount >= remainingFee) ? remainingFee - : outputAmount - _dustAmount; + : outputAmount - networkDustAmount; outputs[i] = BitcoinOutput( address: output.address, value: BigInt.from(outputAmount - deduction)); remainingFee -= deduction; @@ -1845,10 +1850,10 @@ abstract class ElectrumWalletBase final output = outputs[i]; int outputAmount = output.value.toInt(); - if (outputAmount > _dustAmount) { - int deduction = (outputAmount - _dustAmount >= remainingFee) + if (outputAmount > networkDustAmount) { + int deduction = (outputAmount - networkDustAmount >= remainingFee) ? remainingFee - : outputAmount - _dustAmount; + : outputAmount - networkDustAmount; outputs[i] = BitcoinOutput( address: output.address, value: BigInt.from(outputAmount - deduction)); @@ -2051,6 +2056,9 @@ abstract class ElectrumWalletBase await Future.wait(LITECOIN_ADDRESS_TYPES .where((type) => type != SegwitAddresType.mweb) .map((type) => fetchTransactionsForAddressType(historiesWithDetails, type))); + } else if (type == WalletType.dogecoin) { + await Future.wait(DOGECOIN_ADDRESS_TYPES.map( + (type) => fetchTransactionsForAddressType(historiesWithDetails, type))); } transactionHistory.transactions.values.forEach((tx) async { diff --git a/cw_bitcoin/lib/electrum_wallet_addresses.dart b/cw_bitcoin/lib/electrum_wallet_addresses.dart index 614a06a3b..a6ab2180d 100644 --- a/cw_bitcoin/lib/electrum_wallet_addresses.dart +++ b/cw_bitcoin/lib/electrum_wallet_addresses.dart @@ -3,7 +3,6 @@ import 'dart:io' show Platform; import 'package:bitcoin_base/bitcoin_base.dart'; import 'package:blockchain_utils/blockchain_utils.dart'; import 'package:cw_bitcoin/bitcoin_address_record.dart'; -import 'package:cw_bitcoin/electrum_wallet.dart'; import 'package:cw_core/unspent_coin_type.dart'; import 'package:cw_core/utils/print_verbose.dart'; import 'package:cw_bitcoin/bitcoin_unspent.dart'; @@ -33,6 +32,10 @@ const List BITCOIN_CASH_ADDRESS_TYPES = [ P2pkhAddressType.p2pkh, ]; +const List DOGECOIN_ADDRESS_TYPES = [ + P2pkhAddressType.p2pkh, +]; + abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { ElectrumWalletAddressesBase( WalletInfo walletInfo, { @@ -253,6 +256,8 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { if ((Platform.isAndroid || Platform.isIOS) && !isHardwareWallet) { await _generateInitialAddresses(type: SegwitAddresType.mweb); } + } else if (walletInfo.type == WalletType.dogecoin) { + await _generateInitialAddresses(type: P2pkhAddressType.p2pkh); } else if (walletInfo.type == WalletType.bitcoin) { await _generateInitialAddresses(); if (!isHardwareWallet) { @@ -458,7 +463,7 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { } } - void addBitcoinCashAddressTypes() { + void addP2PKHAddressTypes() { final lastP2pkh = _addresses.firstWhere( (addressRecord) => _isUnusedReceiveAddressByType(addressRecord, P2pkhAddressType.p2pkh)); if (lastP2pkh.address != address) { @@ -487,7 +492,10 @@ abstract class ElectrumWalletAddressesBase extends WalletAddresses with Store { addLitecoinAddressTypes(); break; case WalletType.bitcoinCash: - addBitcoinCashAddressTypes(); + addP2PKHAddressTypes(); + break; + case WalletType.dogecoin: + addP2PKHAddressTypes(); break; default: break; diff --git a/cw_bitcoin/lib/pending_bitcoin_transaction.dart b/cw_bitcoin/lib/pending_bitcoin_transaction.dart index 82bcbd79e..0712a35f8 100644 --- a/cw_bitcoin/lib/pending_bitcoin_transaction.dart +++ b/cw_bitcoin/lib/pending_bitcoin_transaction.dart @@ -81,12 +81,15 @@ class PendingBitcoinTransaction with PendingTransaction { return "LTC"; case WalletType.bitcoinCash: return "BCH"; + case WalletType.dogecoin: + return "DOGE"; default: return type.name; } } + @override String get feeFormattedValue => bitcoinAmountToString(amount: fee); @@ -104,8 +107,7 @@ class PendingBitcoinTransaction with PendingTransaction { return PendingChange( changeAddressOverride!, BtcUtils.fromSatoshi(change.amount)); } - return PendingChange( - change.scriptPubKey.toAddress(), BtcUtils.fromSatoshi(change.amount)); + return PendingChange(change.scriptPubKey.toAddress(network: network), BtcUtils.fromSatoshi(change.amount)); } catch (_) { return null; } diff --git a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart index d55914dcd..527774049 100644 --- a/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart +++ b/cw_bitcoin_cash/lib/src/bitcoin_cash_wallet.dart @@ -142,6 +142,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { addr.address, index: addr.index, isHidden: addr.isHidden, + name: addr.name, type: P2pkhAddressType.p2pkh, network: BitcoinCashNetwork.mainnet, ); @@ -150,6 +151,7 @@ abstract class BitcoinCashWalletBase extends ElectrumWallet with Store { AddressUtils.getCashAddrFormat(addr.address), index: addr.index, isHidden: addr.isHidden, + name: addr.name, type: P2pkhAddressType.p2pkh, network: BitcoinCashNetwork.mainnet, ); diff --git a/cw_core/lib/amount_converter.dart b/cw_core/lib/amount_converter.dart index e01c3834a..8a095dffa 100644 --- a/cw_core/lib/amount_converter.dart +++ b/cw_core/lib/amount_converter.dart @@ -30,6 +30,7 @@ class AmountConverter { case CryptoCurrency.bch: case CryptoCurrency.ltc: case CryptoCurrency.dcr: + case CryptoCurrency.doge: return _bitcoinAmountToString(amount); case CryptoCurrency.xhv: case CryptoCurrency.xag: diff --git a/cw_core/lib/currency_for_wallet_type.dart b/cw_core/lib/currency_for_wallet_type.dart index 0f913cb79..495731aa9 100644 --- a/cw_core/lib/currency_for_wallet_type.dart +++ b/cw_core/lib/currency_for_wallet_type.dart @@ -34,6 +34,8 @@ CryptoCurrency currencyForWalletType(WalletType type, {bool? isTestnet}) { return CryptoCurrency.zano; case WalletType.decred: return CryptoCurrency.dcr; + case WalletType.dogecoin: + return CryptoCurrency.doge; case WalletType.none: throw Exception( 'Unexpected wallet type: ${type.toString()} for CryptoCurrency currencyForWalletType'); diff --git a/cw_core/lib/node.dart b/cw_core/lib/node.dart index ff94001f2..7f691f955 100644 --- a/cw_core/lib/node.dart +++ b/cw_core/lib/node.dart @@ -100,6 +100,7 @@ class Node extends HiveObject with Keyable { case WalletType.bitcoin: case WalletType.litecoin: case WalletType.bitcoinCash: + case WalletType.dogecoin: return createUriFromElectrumAddress(uriRaw, path!); case WalletType.nano: case WalletType.banano: @@ -170,6 +171,7 @@ class Node extends HiveObject with Keyable { case WalletType.polygon: case WalletType.solana: case WalletType.tron: + case WalletType.dogecoin: return requestElectrumServer(); case WalletType.zano: return requestZanoNode(); diff --git a/cw_core/lib/wallet_type.dart b/cw_core/lib/wallet_type.dart index 40a2b31d5..3f4fa37fa 100644 --- a/cw_core/lib/wallet_type.dart +++ b/cw_core/lib/wallet_type.dart @@ -18,6 +18,7 @@ const walletTypes = [ WalletType.tron, WalletType.zano, WalletType.decred, + WalletType.dogecoin, ]; @HiveType(typeId: WALLET_TYPE_TYPE_ID) @@ -65,7 +66,10 @@ enum WalletType { zano, @HiveField(14) - decred + decred, + + @HiveField(15) + dogecoin } int serializeToInt(WalletType type) { @@ -98,6 +102,8 @@ int serializeToInt(WalletType type) { return 12; case WalletType.decred: return 13; + case WalletType.dogecoin: + return 14; case WalletType.none: return -1; } @@ -133,6 +139,8 @@ WalletType deserializeFromInt(int raw) { return WalletType.zano; case 13: return WalletType.decred; + case 14: + return WalletType.dogecoin; default: throw Exception( 'Unexpected token: $raw for WalletType deserializeFromInt'); @@ -169,6 +177,8 @@ String walletTypeToString(WalletType type) { return 'Zano'; case WalletType.decred: return 'Decred'; + case WalletType.dogecoin: + return 'Dogecoin'; case WalletType.none: return ''; } @@ -204,6 +214,8 @@ String walletTypeToDisplayName(WalletType type) { return 'Zano (ZANO)'; case WalletType.decred: return 'Decred (DCR)'; + case WalletType.dogecoin: + return 'Dogecoin (DOGE)'; case WalletType.none: return ''; } @@ -242,6 +254,8 @@ CryptoCurrency walletTypeToCryptoCurrency(WalletType type, {bool isTestnet = fal return CryptoCurrency.zano; case WalletType.decred: return CryptoCurrency.dcr; + case WalletType.dogecoin: + return CryptoCurrency.doge; case WalletType.none: throw Exception( 'Unexpected wallet type: ${type.toString()} for CryptoCurrency walletTypeToCryptoCurrency'); @@ -278,6 +292,8 @@ WalletType? cryptoCurrencyToWalletType(CryptoCurrency type) { return WalletType.zano; case CryptoCurrency.dcr: return WalletType.decred; + case CryptoCurrency.doge: + return WalletType.dogecoin; default: return null; } diff --git a/cw_dogecoin/.gitignore b/cw_dogecoin/.gitignore new file mode 100644 index 000000000..eb6c05cd3 --- /dev/null +++ b/cw_dogecoin/.gitignore @@ -0,0 +1,31 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +build/ diff --git a/cw_dogecoin/.metadata b/cw_dogecoin/.metadata new file mode 100644 index 000000000..69723fa27 --- /dev/null +++ b/cw_dogecoin/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: "c23637390482d4cf9598c3ce3f2be31aa7332daf" + channel: "stable" + +project_type: package diff --git a/cw_dogecoin/CHANGELOG.md b/cw_dogecoin/CHANGELOG.md new file mode 100644 index 000000000..41cc7d819 --- /dev/null +++ b/cw_dogecoin/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/cw_dogecoin/LICENSE b/cw_dogecoin/LICENSE new file mode 100644 index 000000000..ba75c69f7 --- /dev/null +++ b/cw_dogecoin/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/cw_dogecoin/README.md b/cw_dogecoin/README.md new file mode 100644 index 000000000..4a260d8d2 --- /dev/null +++ b/cw_dogecoin/README.md @@ -0,0 +1,39 @@ + + +TODO: Put a short description of the package here that helps potential users +know whether this package might be useful for them. + +## Features + +TODO: List what your package can do. Maybe include images, gifs, or videos. + +## Getting started + +TODO: List prerequisites and provide or point to information on how to +start using the package. + +## Usage + +TODO: Include short and useful examples for package users. Add longer examples +to `/example` folder. + +```dart +const like = 'sample'; +``` + +## Additional information + +TODO: Tell users more about the package: where to find more information, how to +contribute to the package, how to file issues, what response they can expect +from the package authors, and more. diff --git a/cw_dogecoin/analysis_options.yaml b/cw_dogecoin/analysis_options.yaml new file mode 100644 index 000000000..a5744c1cf --- /dev/null +++ b/cw_dogecoin/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/cw_dogecoin/lib/cw_dogecoin.dart b/cw_dogecoin/lib/cw_dogecoin.dart new file mode 100644 index 000000000..3884dae59 --- /dev/null +++ b/cw_dogecoin/lib/cw_dogecoin.dart @@ -0,0 +1,6 @@ +export 'src/dogecoin_wallet.dart'; +export 'src/dogecoin_wallet_addresses.dart'; +export 'src/dogecoin_wallet_creation_credentials.dart'; +export 'src/dogecoin_wallet_service.dart'; +export 'src/dogecoin_transaction_priority.dart'; + diff --git a/cw_dogecoin/lib/src/dogecoin_transaction_priority.dart b/cw_dogecoin/lib/src/dogecoin_transaction_priority.dart new file mode 100644 index 000000000..b692cc570 --- /dev/null +++ b/cw_dogecoin/lib/src/dogecoin_transaction_priority.dart @@ -0,0 +1,52 @@ +import 'package:cw_bitcoin/bitcoin_transaction_priority.dart'; + +class DogecoinTransactionPriority extends BitcoinTransactionPriority { + const DogecoinTransactionPriority({required String title, required int raw}) + : super(title: title, raw: raw); + + static const List all = [fast, medium, slow]; + static const DogecoinTransactionPriority slow = + DogecoinTransactionPriority(title: 'Slow', raw: 0); + static const DogecoinTransactionPriority medium = + DogecoinTransactionPriority(title: 'Medium', raw: 1); + static const DogecoinTransactionPriority fast = + DogecoinTransactionPriority(title: 'Fast', raw: 2); + + static DogecoinTransactionPriority deserialize({required int raw}) { + switch (raw) { + case 0: + return slow; + case 1: + return medium; + case 2: + return fast; + default: + throw Exception('Unexpected token: $raw for DogecoinTransactionPriority deserialize'); + } + } + + @override + String get units => 'koinu'; + + @override + String toString() { + var label = ''; + + switch (this) { + case DogecoinTransactionPriority.slow: + label = 'Slow'; // S.current.transaction_priority_slow; + break; + case DogecoinTransactionPriority.medium: + label = 'Medium'; // S.current.transaction_priority_medium; + break; + case DogecoinTransactionPriority.fast: + label = 'Fast'; // S.current.transaction_priority_fast; + break; + default: + break; + } + + return label; + } +} + diff --git a/cw_dogecoin/lib/src/dogecoin_wallet.dart b/cw_dogecoin/lib/src/dogecoin_wallet.dart new file mode 100644 index 000000000..bd7fe0699 --- /dev/null +++ b/cw_dogecoin/lib/src/dogecoin_wallet.dart @@ -0,0 +1,164 @@ +import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:cw_bitcoin/bitcoin_address_record.dart'; +import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart'; +import 'package:cw_bitcoin/electrum_balance.dart'; +import 'package:cw_bitcoin/electrum_wallet.dart'; +import 'package:cw_bitcoin/electrum_wallet_snapshot.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/encryption_file_utils.dart'; +import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_keys_file.dart'; +import 'package:flutter/foundation.dart'; +import 'package:hive/hive.dart'; +import 'package:mobx/mobx.dart'; + +import 'dogecoin_wallet_addresses.dart'; + +part 'dogecoin_wallet.g.dart'; + +class DogeCoinWallet = DogeCoinWalletBase with _$DogeCoinWallet; + +abstract class DogeCoinWalletBase extends ElectrumWallet with Store { + DogeCoinWalletBase({ + required String mnemonic, + required String password, + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + required Uint8List seedBytes, + required EncryptionFileUtils encryptionFileUtils, + String? passphrase, + BitcoinAddressType? addressPageType, + List? initialAddresses, + ElectrumBalance? initialBalance, + Map? initialRegularAddressIndex, + Map? initialChangeAddressIndex, + }) : super( + mnemonic: mnemonic, + password: password, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + network: DogecoinNetwork.mainnet, + initialAddresses: initialAddresses, + initialBalance: initialBalance, + seedBytes: seedBytes, + currency: CryptoCurrency.doge, + encryptionFileUtils: encryptionFileUtils, + passphrase: passphrase) { + walletAddresses = DogeCoinWalletAddresses( + walletInfo, + initialAddresses: initialAddresses, + initialRegularAddressIndex: initialRegularAddressIndex, + initialChangeAddressIndex: initialChangeAddressIndex, + mainHd: hd, + sideHd: accountHD.childKey(Bip32KeyIndex(1)), + network: network, + initialAddressPageType: addressPageType, + isHardwareWallet: walletInfo.isHardwareWallet, + ); + autorun((_) { + this.walletAddresses.isEnabledAutoGenerateSubaddress = this.isEnabledAutoGenerateSubaddress; + }); + } + + @override + int get networkDustAmount => 100000000; // 1 DOGE = 1e8 koinu + + static Future create( + {required String mnemonic, + required String password, + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + required EncryptionFileUtils encryptionFileUtils, + String? passphrase, + String? addressPageType, + List? initialAddresses, + ElectrumBalance? initialBalance, + Map? initialRegularAddressIndex, + Map? initialChangeAddressIndex}) async { + return DogeCoinWallet( + mnemonic: mnemonic, + password: password, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + initialAddresses: initialAddresses, + initialBalance: initialBalance, + seedBytes: MnemonicBip39.toSeed(mnemonic, passphrase: passphrase), + encryptionFileUtils: encryptionFileUtils, + initialRegularAddressIndex: initialRegularAddressIndex, + initialChangeAddressIndex: initialChangeAddressIndex, + addressPageType: P2pkhAddressType.p2pkh, + passphrase: passphrase, + ); + } + + static Future open({ + required String name, + required WalletInfo walletInfo, + required Box unspentCoinsInfo, + required String password, + required EncryptionFileUtils encryptionFileUtils, + }) async { + final hasKeysFile = await WalletKeysFile.hasKeysFile(name, walletInfo.type); + + ElectrumWalletSnapshot? snp = null; + + try { + snp = await ElectrumWalletSnapshot.load( + encryptionFileUtils, + name, + walletInfo.type, + password, + DogecoinNetwork.mainnet, + ); + } catch (e) { + if (!hasKeysFile) rethrow; + } + + final WalletKeysData keysData; + // Migrate wallet from the old scheme to then new .keys file scheme + if (!hasKeysFile) { + keysData = + WalletKeysData(mnemonic: snp!.mnemonic, xPub: snp.xpub, passphrase: snp.passphrase); + } else { + keysData = await WalletKeysFile.readKeysFile( + name, + walletInfo.type, + password, + encryptionFileUtils, + ); + } + + return DogeCoinWallet( + mnemonic: keysData.mnemonic!, + password: password, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfo, + initialAddresses: snp?.addresses, + initialBalance: snp?.balance, + seedBytes: await MnemonicBip39.toSeed(keysData.mnemonic!, passphrase: keysData.passphrase), + encryptionFileUtils: encryptionFileUtils, + initialRegularAddressIndex: snp?.regularAddressIndex, + initialChangeAddressIndex: snp?.changeAddressIndex, + addressPageType: P2pkhAddressType.p2pkh, + passphrase: keysData.passphrase, + ); + } + + @override + Future signMessage(String message, {String? address = null}) async { + int? index; + try { + index = address != null + ? walletAddresses.allAddresses.firstWhere((element) => element.address == address).index + : null; + } catch (_) {} + final HD = index == null ? hd : hd.childKey(Bip32KeyIndex(index)); + final priv = ECPrivate.fromWif( + WifEncoder.encode(HD.privateKey.raw, netVer: network.wifNetVer), + netVersion: network.wifNetVer, + ); + return priv.signMessage(StringUtils.encode(message)); + } +} diff --git a/cw_dogecoin/lib/src/dogecoin_wallet_addresses.dart b/cw_dogecoin/lib/src/dogecoin_wallet_addresses.dart new file mode 100644 index 000000000..75d06c148 --- /dev/null +++ b/cw_dogecoin/lib/src/dogecoin_wallet_addresses.dart @@ -0,0 +1,29 @@ +import 'package:bitcoin_base/bitcoin_base.dart'; +import 'package:blockchain_utils/blockchain_utils.dart'; +import 'package:cw_bitcoin/electrum_wallet_addresses.dart'; +import 'package:cw_bitcoin/utils.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:mobx/mobx.dart'; + +part 'dogecoin_wallet_addresses.g.dart'; + +class DogeCoinWalletAddresses = DogeCoinWalletAddressesBase with _$DogeCoinWalletAddresses; + +abstract class DogeCoinWalletAddressesBase extends ElectrumWalletAddresses with Store { + DogeCoinWalletAddressesBase(WalletInfo walletInfo, { + required super.mainHd, + required super.sideHd, + required super.network, + required super.isHardwareWallet, + super.initialAddresses, + super.initialRegularAddressIndex, + super.initialChangeAddressIndex, + super.initialAddressPageType + }) : super(walletInfo); + + @override + String getAddress({required int index, + required Bip32Slip10Secp256k1 hd, + BitcoinAddressType? addressType}) => + generateP2PKHAddress(hd: hd, index: index, network: network); +} diff --git a/cw_dogecoin/lib/src/dogecoin_wallet_creation_credentials.dart b/cw_dogecoin/lib/src/dogecoin_wallet_creation_credentials.dart new file mode 100644 index 000000000..3dfe242e2 --- /dev/null +++ b/cw_dogecoin/lib/src/dogecoin_wallet_creation_credentials.dart @@ -0,0 +1,38 @@ +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; + +class DogeCoinNewWalletCredentials extends WalletCredentials { + DogeCoinNewWalletCredentials({ + required String name, + WalletInfo? walletInfo, + String? password, + String? passphrase, + this.mnemonic, + }) : super( + name: name, + walletInfo: walletInfo, + password: password, + passphrase: passphrase, + ); + final String? mnemonic; +} + +class DogeCoinRestoreWalletFromSeedCredentials extends WalletCredentials { + DogeCoinRestoreWalletFromSeedCredentials({ + required String name, + required String password, + required this.mnemonic, + WalletInfo? walletInfo, + String? passphrase, + }) : super(name: name, password: password, walletInfo: walletInfo, passphrase: passphrase); + + final String mnemonic; +} + +class DogeCoinRestoreWalletFromWIFCredentials extends WalletCredentials { + DogeCoinRestoreWalletFromWIFCredentials( + {required String name, required String password, required this.wif, WalletInfo? walletInfo}) + : super(name: name, password: password, walletInfo: walletInfo); + + final String wif; +} diff --git a/cw_dogecoin/lib/src/dogecoin_wallet_service.dart b/cw_dogecoin/lib/src/dogecoin_wallet_service.dart new file mode 100644 index 000000000..d0e3b2969 --- /dev/null +++ b/cw_dogecoin/lib/src/dogecoin_wallet_service.dart @@ -0,0 +1,151 @@ +import 'dart:io'; + +import 'package:bip39/bip39.dart'; +import 'package:collection/collection.dart'; +import 'package:cw_bitcoin/bitcoin_mnemonics_bip39.dart'; +import 'package:cw_core/encryption_file_utils.dart'; +import 'package:cw_core/pathForWallet.dart'; +import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/wallet_base.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:cw_dogecoin/cw_dogecoin.dart'; +import 'package:hive/hive.dart'; + +class DogeCoinWalletService extends WalletService< + DogeCoinNewWalletCredentials, + DogeCoinRestoreWalletFromSeedCredentials, + DogeCoinRestoreWalletFromWIFCredentials, + DogeCoinNewWalletCredentials> { + DogeCoinWalletService(this.walletInfoSource, this.unspentCoinsInfoSource, this.isDirect); + + final Box walletInfoSource; + final Box unspentCoinsInfoSource; + final bool isDirect; + + @override + WalletType getType() => WalletType.dogecoin; + + @override + Future isWalletExit(String name) async => + File(await pathForWallet(name: name, type: getType())).existsSync(); + + @override + Future create(credentials, {bool? isTestnet}) async { + final strength = credentials.seedPhraseLength == 24 ? 256 : 128; + + final wallet = await DogeCoinWalletBase.create( + mnemonic: credentials.mnemonic ?? MnemonicBip39.generate(strength: strength), + password: credentials.password!, + walletInfo: credentials.walletInfo!, + unspentCoinsInfo: unspentCoinsInfoSource, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), + passphrase: credentials.passphrase, + ); + await wallet.save(); + await wallet.init(); + + return wallet; + } + + @override + Future openWallet(String name, String password) async { + final walletInfo = walletInfoSource.values + .firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!; + + try { + final wallet = await DogeCoinWalletBase.open( + password: password, + name: name, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfoSource, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), + ); + await wallet.init(); + saveBackup(name); + return wallet; + } catch (_) { + await restoreWalletFilesFromBackup(name); + final wallet = await DogeCoinWalletBase.open( + password: password, + name: name, + walletInfo: walletInfo, + unspentCoinsInfo: unspentCoinsInfoSource, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), + ); + await wallet.init(); + return wallet; + } + } + + @override + Future remove(String wallet) async { + File(await pathForWalletDir(name: wallet, type: getType())).delete(recursive: true); + final walletInfo = walletInfoSource.values + .firstWhereOrNull((info) => info.id == WalletBase.idFor(wallet, getType()))!; + await walletInfoSource.delete(walletInfo.key); + + final unspentCoinsToDelete = unspentCoinsInfoSource.values + .where((unspentCoin) => unspentCoin.walletId == walletInfo.id) + .toList(); + + final keysToDelete = unspentCoinsToDelete.map((unspentCoin) => unspentCoin.key).toList(); + + if (keysToDelete.isNotEmpty) { + await unspentCoinsInfoSource.deleteAll(keysToDelete); + } + } + + @override + Future rename(String currentName, String password, String newName) async { + final currentWalletInfo = walletInfoSource.values + .firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!; + final currentWallet = await DogeCoinWalletBase.open( + password: password, + name: currentName, + walletInfo: currentWalletInfo, + unspentCoinsInfo: unspentCoinsInfoSource, + encryptionFileUtils: encryptionFileUtilsFor(isDirect)); + + await currentWallet.renameWalletFiles(newName); + await saveBackup(newName); + + final newWalletInfo = currentWalletInfo; + newWalletInfo.id = WalletBase.idFor(newName, getType()); + newWalletInfo.name = newName; + + await walletInfoSource.put(currentWalletInfo.key, newWalletInfo); + } + + @override + Future restoreFromHardwareWallet(DogeCoinNewWalletCredentials credentials) { + throw UnimplementedError( + "Restoring a Bitcoin Cash wallet from a hardware wallet is not yet supported!"); + } + + @override + Future restoreFromKeys(credentials, {bool? isTestnet}) { + // TODO: implement restoreFromKeys + throw UnimplementedError('restoreFromKeys() is not implemented'); + } + + @override + Future restoreFromSeed(DogeCoinRestoreWalletFromSeedCredentials credentials, + {bool? isTestnet}) async { + if (!validateMnemonic(credentials.mnemonic)) { + throw Exception('Invalid mnemonic: ${credentials.mnemonic}'); + } + + final wallet = await DogeCoinWalletBase.create( + password: credentials.password!, + mnemonic: credentials.mnemonic, + walletInfo: credentials.walletInfo!, + unspentCoinsInfo: unspentCoinsInfoSource, + encryptionFileUtils: encryptionFileUtilsFor(isDirect), + passphrase: credentials.passphrase); + await wallet.save(); + await wallet.init(); + return wallet; + } +} diff --git a/cw_dogecoin/pubspec.yaml b/cw_dogecoin/pubspec.yaml new file mode 100644 index 000000000..599a76429 --- /dev/null +++ b/cw_dogecoin/pubspec.yaml @@ -0,0 +1,81 @@ +name: cw_dogecoin +description: "A new Flutter package project." +version: 0.0.1 +publish_to: none +author: Cake Wallet +homepage: https://cakewallet.com + +environment: + sdk: '>=2.19.0 <3.0.0' + flutter: ">=1.20.0" + +dependencies: + flutter: + sdk: flutter + bip39: ^1.0.6 + bip32: ^2.0.0 + path_provider: ^2.0.11 + mobx: ^2.0.7+4 + flutter_mobx: ^2.0.6+1 + cw_core: + path: ../cw_core + cw_bitcoin: + path: ../cw_bitcoin + + blockchain_utils: + git: + url: https://github.com/cake-tech/blockchain_utils + ref: cake-update-v2 + +dev_dependencies: + flutter_test: + sdk: flutter + build_runner: ^2.4.15 + mobx_codegen: ^2.0.7 + hive_generator: ^2.0.1 + +dependency_overrides: + watcher: ^1.1.0 + bitcoin_base: + git: + url: https://github.com/cake-tech/bitcoin_base + ref: cake-update-v10 + + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + uses-material-design: true + + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/to/asset-from-package + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/to/resolution-aware-images + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/to/font-from-package diff --git a/cw_dogecoin/test/cw_dogecoin_test.dart b/cw_dogecoin/test/cw_dogecoin_test.dart new file mode 100644 index 000000000..1c5f77ec9 --- /dev/null +++ b/cw_dogecoin/test/cw_dogecoin_test.dart @@ -0,0 +1,8 @@ +import 'package:flutter_test/flutter_test.dart'; + +import 'package:cw_dogecoin/cw_dogecoin.dart'; + +void main() { + test('adds one to input values', () { + }); +} diff --git a/ios/Runner/InfoBase.plist b/ios/Runner/InfoBase.plist index 40868aa9f..5eefa786c 100644 --- a/ios/Runner/InfoBase.plist +++ b/ios/Runner/InfoBase.plist @@ -282,6 +282,25 @@ decred-wallet + + CFBundleTypeRole + Viewer + CFBundleURLName + dogecoin + CFBundleURLSchemes + + dash + + + + CFBundleTypeRole + Viewer + CFBundleURLName + dogecoin-wallet + CFBundleURLSchemes + + dogecoin-wallet + CFBundleVersion $(CURRENT_PROJECT_VERSION) diff --git a/lib/bitcoin/cw_bitcoin.dart b/lib/bitcoin/cw_bitcoin.dart index bd6e03002..c0abaa2be 100644 --- a/lib/bitcoin/cw_bitcoin.dart +++ b/lib/bitcoin/cw_bitcoin.dart @@ -184,6 +184,18 @@ class CWBitcoin extends Bitcoin { return estimatedTx.amount; } + + if (wallet.type == WalletType.dogecoin) { + final dogeAddr = + sk.getPublic().toP2pkhAddress(); + final estimatedTx = await electrumWallet.estimateSendAllTx( + [BitcoinOutput(address: dogeAddr, value: BigInt.zero)], + getFeeRate(wallet, priority as BitcoinTransactionPriority), + coinTypeToSpendFrom: coinTypeToSpendFrom, + ); + return estimatedTx.amount; + } + final p2shAddr = sk.getPublic().toP2pkhInP2sh(); final estimatedTx = await electrumWallet.estimateSendAllTx( [BitcoinOutput(address: p2shAddr, value: BigInt.zero)], diff --git a/lib/core/seed_validator.dart b/lib/core/seed_validator.dart index 344b5391d..2e35e7f5a 100644 --- a/lib/core/seed_validator.dart +++ b/lib/core/seed_validator.dart @@ -29,6 +29,8 @@ class SeedValidator extends Validator { return getBitcoinWordList(language); case WalletType.litecoin: return getBitcoinWordList(language); + case WalletType.dogecoin: + return getBitcoinWordList(language); case WalletType.monero: return monero!.getMoneroWordList(language); case WalletType.ethereum: diff --git a/lib/core/wallet_creation_service.dart b/lib/core/wallet_creation_service.dart index b44e56a98..2cf7a7c63 100644 --- a/lib/core/wallet_creation_service.dart +++ b/lib/core/wallet_creation_service.dart @@ -82,6 +82,7 @@ class WalletCreationService { case WalletType.polygon: case WalletType.solana: case WalletType.tron: + case WalletType.dogecoin: return true; case WalletType.monero: case WalletType.wownero: diff --git a/lib/di.dart b/lib/di.dart index cec6331d6..f913478c5 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -274,6 +274,7 @@ import 'package:mobx/mobx.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'buy/kryptonim/kryptonim.dart'; import 'buy/meld/meld_buy_provider.dart'; +import 'dogecoin/dogecoin.dart'; import 'src/screens/buy/buy_sell_page.dart'; import 'package:cake_wallet/view_model/dev/background_sync_logs_view_model.dart'; import 'package:cake_wallet/src/screens/dev/background_sync_logs_page.dart'; @@ -1147,6 +1148,9 @@ Future setup({ case WalletType.bitcoinCash: return bitcoinCash!.createBitcoinCashWalletService(_walletInfoSource, _unspentCoinsInfoSource, SettingsStoreBase.walletPasswordDirectInput); + case WalletType.dogecoin: + return dogecoin!.createDogeCoinWalletService(_walletInfoSource, + _unspentCoinsInfoSource, SettingsStoreBase.walletPasswordDirectInput); case WalletType.nano: case WalletType.banano: return nano!.createNanoWalletService(_walletInfoSource, SettingsStoreBase.walletPasswordDirectInput); diff --git a/lib/dogecoin/cw_dogecoin.dart b/lib/dogecoin/cw_dogecoin.dart new file mode 100644 index 000000000..e78119306 --- /dev/null +++ b/lib/dogecoin/cw_dogecoin.dart @@ -0,0 +1,48 @@ +part of 'dogecoin.dart'; + + +class CWDogeCoin extends DogeCoin { + + @override + WalletService createDogeCoinWalletService( + Box walletInfoSource, Box unspentCoinSource, bool isDirect) { + return DogeCoinWalletService(walletInfoSource, unspentCoinSource, isDirect); + } + + @override + WalletCredentials createDogeCoinNewWalletCredentials({ + required String name, + WalletInfo? walletInfo, + String? password, + String? passphrase, + String? mnemonic, + }) => + DogeCoinNewWalletCredentials( + name: name, + walletInfo: walletInfo, + password: password, + passphrase: passphrase, + mnemonic: mnemonic, + ); + + @override + WalletCredentials createDogeCoinRestoreWalletFromSeedCredentials({ + required String name, + required String mnemonic, + required String password, + String? passphrase, + }) => + DogeCoinRestoreWalletFromSeedCredentials( + name: name, mnemonic: mnemonic, password: password, passphrase: passphrase); + + @override + TransactionPriority deserializeDogeCoinTransactionPriority(int raw) => + DogecoinTransactionPriority.deserialize(raw: raw); + + @override + TransactionPriority getDefaultTransactionPriority() => DogecoinTransactionPriority.medium; + @override + List getTransactionPriorities() => DogecoinTransactionPriority.all; + @override + TransactionPriority getDogeCoinTransactionPrioritySlow() => DogecoinTransactionPriority.slow; +} diff --git a/lib/dogecoin/dogecoin.dart b/lib/dogecoin/dogecoin.dart new file mode 100644 index 000000000..252333628 --- /dev/null +++ b/lib/dogecoin/dogecoin.dart @@ -0,0 +1,32 @@ +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:hive/hive.dart'; + +import 'package:cw_dogecoin/cw_dogecoin.dart'; + +part 'cw_dogecoin.dart'; + +DogeCoin? dogecoin = CWDogeCoin(); + +abstract class DogeCoin { + + WalletService createDogeCoinWalletService( + Box walletInfoSource, Box unspentCoinSource, bool isDirect); + + WalletCredentials createDogeCoinNewWalletCredentials( + {required String name, WalletInfo? walletInfo, String? password, String? passphrase, String? mnemonic}); + + WalletCredentials createDogeCoinRestoreWalletFromSeedCredentials( + {required String name, required String mnemonic, required String password, String? passphrase}); + + TransactionPriority deserializeDogeCoinTransactionPriority(int raw); + + TransactionPriority getDefaultTransactionPriority(); + + List getTransactionPriorities(); + + TransactionPriority getDogeCoinTransactionPrioritySlow(); +} diff --git a/lib/entities/default_settings_migration.dart b/lib/entities/default_settings_migration.dart index 35634c391..0d573195f 100644 --- a/lib/entities/default_settings_migration.dart +++ b/lib/entities/default_settings_migration.dart @@ -46,6 +46,7 @@ const wowneroDefaultNodeUri = 'node3.monerodevs.org:34568'; const zanoDefaultNodeUri = 'zano.cakewallet.com:11211'; const moneroWorldNodeUri = '.moneroworld.com'; const decredDefaultUri = "default-spv-nodes"; +const dogecoinDefaultNodeUri = 'dogecoin.stackwallet.com:50022'; Future defaultSettingsMigration( {required int version, @@ -514,6 +515,15 @@ Future defaultSettingsMigration( case 50: migrateExistingNodesToUseAutoSwitching(nodes: nodes, powNodes: powNodes); break; + case 51: + await addWalletNodeList(nodes: nodes, type: WalletType.dogecoin); + await _changeDefaultNode( + nodes: nodes, + sharedPreferences: sharedPreferences, + type: WalletType.dogecoin, + currentNodePreferenceKey: PreferencesKey.currentDogecoinNodeIdKey, + ); + break; default: break; } @@ -620,6 +630,8 @@ String _getDefaultNodeUri(WalletType type) { return zanoDefaultNodeUri; case WalletType.decred: return decredDefaultUri; + case WalletType.dogecoin: + return dogecoinDefaultNodeUri; case WalletType.banano: case WalletType.none: return ''; @@ -1050,6 +1062,8 @@ Future checkCurrentNodes( final currentDecredNodeId = sharedPreferences.getInt(PreferencesKey.currentDecredNodeIdKey); final currentBitcoinCashNodeId = sharedPreferences.getInt(PreferencesKey.currentBitcoinCashNodeIdKey); + final currentDogecoinNodeId = + sharedPreferences.getInt(PreferencesKey.currentDogecoinNodeIdKey); final currentSolanaNodeId = sharedPreferences.getInt(PreferencesKey.currentSolanaNodeIdKey); final currentTronNodeId = sharedPreferences.getInt(PreferencesKey.currentTronNodeIdKey); final currentWowneroNodeId = sharedPreferences.getInt(PreferencesKey.currentWowneroNodeIdKey); @@ -1074,6 +1088,8 @@ Future checkCurrentNodes( powNodeSource.values.firstWhereOrNull((node) => node.key == currentNanoPowNodeId); final currentBitcoinCashNodeServer = nodeSource.values.firstWhereOrNull((node) => node.key == currentBitcoinCashNodeId); + final currentDogecoinNodeServer = + nodeSource.values.firstWhereOrNull((node) => node.key == currentDogecoinNodeId); final currentSolanaNodeServer = nodeSource.values.firstWhereOrNull((node) => node.key == currentSolanaNodeId); final currentTronNodeServer = @@ -1143,6 +1159,12 @@ Future checkCurrentNodes( await sharedPreferences.setInt(PreferencesKey.currentBitcoinCashNodeIdKey, node.key as int); } + if (currentDogecoinNodeServer == null) { + final node = Node(uri: dogecoinDefaultNodeUri, type: WalletType.dogecoin, useSSL: true); + await nodeSource.add(node); + await sharedPreferences.setInt(PreferencesKey.currentDogecoinNodeIdKey, node.key as int); + } + if (currentPolygonNodeServer == null) { final node = Node(uri: polygonDefaultNodeUri, type: WalletType.polygon); await nodeSource.add(node); diff --git a/lib/entities/node_list.dart b/lib/entities/node_list.dart index bb489e715..40843becf 100644 --- a/lib/entities/node_list.dart +++ b/lib/entities/node_list.dart @@ -46,6 +46,9 @@ Future> loadDefaultNodes(WalletType type) async { case WalletType.decred: path = 'assets/decred_node_list.yml'; break; + case WalletType.dogecoin: + path = 'assets/dogecoin_electrum_server_list.yml'; + break; case WalletType.banano: case WalletType.none: path = ''; @@ -96,6 +99,7 @@ Future resetToDefault(Box nodeSource) async { final tronNodes = await loadDefaultNodes(WalletType.tron); final decredNodes = await loadDefaultNodes(WalletType.decred); final zanoNodes = await loadDefaultNodes(WalletType.zano); + final dogecoinElectrumServerList = await loadDefaultNodes(WalletType.dogecoin); final nodes = moneroNodes + bitcoinElectrumServerList + @@ -108,7 +112,8 @@ Future resetToDefault(Box nodeSource) async { solanaNodes + tronNodes + zanoNodes + - decredNodes; + decredNodes + + dogecoinElectrumServerList; await nodeSource.clear(); await nodeSource.addAll(nodes); diff --git a/lib/entities/preferences_key.dart b/lib/entities/preferences_key.dart index d0e6a56fc..65e4a67d7 100644 --- a/lib/entities/preferences_key.dart +++ b/lib/entities/preferences_key.dart @@ -11,6 +11,7 @@ class PreferencesKey { static const currentNanoNodeIdKey = 'current_node_id_nano'; static const currentNanoPowNodeIdKey = 'current_node_id_nano_pow'; static const currentDecredNodeIdKey = 'current_node_id_decred'; + static const currentDogecoinNodeIdKey = 'current_node_id_doge'; static const currentBananoNodeIdKey = 'current_node_id_banano'; static const currentBananoPowNodeIdKey = 'current_node_id_banano_pow'; static const currentFiatCurrencyKey = 'current_fiat_currency'; diff --git a/lib/entities/priority_for_wallet_type.dart b/lib/entities/priority_for_wallet_type.dart index 5307250d5..c85e98597 100644 --- a/lib/entities/priority_for_wallet_type.dart +++ b/lib/entities/priority_for_wallet_type.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; +import 'package:cake_wallet/dogecoin/dogecoin.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/polygon/polygon.dart'; @@ -23,6 +24,8 @@ List priorityForWalletType(WalletType type) { return ethereum!.getTransactionPriorities(); case WalletType.bitcoinCash: return bitcoinCash!.getTransactionPriorities(); + case WalletType.dogecoin: + return dogecoin!.getTransactionPriorities(); case WalletType.polygon: return polygon!.getTransactionPriorities(); // no such thing for nano/banano/solana/tron: diff --git a/lib/main.dart b/lib/main.dart index 0b9f1b323..f37c69705 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -250,7 +250,7 @@ Future initializeAppConfigs({bool loadWallet = true}) async { payjoinSessionSource: payjoinSessionSource, anonpayInvoiceInfo: anonpayInvoiceInfo, havenSeedStore: havenSeedStore, - initialMigrationVersion: 50, + initialMigrationVersion: 51, ); } diff --git a/lib/reactions/on_current_wallet_change.dart b/lib/reactions/on_current_wallet_change.dart index 7b7199b14..53a12b10c 100644 --- a/lib/reactions/on_current_wallet_change.dart +++ b/lib/reactions/on_current_wallet_change.dart @@ -76,6 +76,7 @@ void startCurrentWalletChangeReaction( wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin || wallet.type == WalletType.bitcoinCash || + wallet.type == WalletType.dogecoin || wallet.type == WalletType.decred) { _setAutoGenerateSubaddressStatus(wallet, settingsStore); } diff --git a/lib/reactions/wallet_utils.dart b/lib/reactions/wallet_utils.dart index d560f01bb..dde43e002 100644 --- a/lib/reactions/wallet_utils.dart +++ b/lib/reactions/wallet_utils.dart @@ -12,6 +12,7 @@ bool isBIP39Wallet(WalletType walletType) { case WalletType.nano: case WalletType.banano: case WalletType.monero: + case WalletType.dogecoin: return true; case WalletType.wownero: case WalletType.haven: diff --git a/lib/src/screens/dashboard/widgets/menu_widget.dart b/lib/src/screens/dashboard/widgets/menu_widget.dart index b394e96ad..b96f0e4cc 100644 --- a/lib/src/screens/dashboard/widgets/menu_widget.dart +++ b/lib/src/screens/dashboard/widgets/menu_widget.dart @@ -37,7 +37,8 @@ class MenuWidgetState extends State { this.tronIcon = Image.asset('assets/images/trx_icon.png'), this.wowneroIcon = Image.asset('assets/images/wownero_icon.png'), this.zanoIcon = Image.asset('assets/images/zano_icon.png'), - this.decredIcon = Image.asset('assets/images/decred_menu.png'); + this.decredIcon = Image.asset('assets/images/decred_menu.png'), + this.dogecoinIcon = Image.asset('assets/images/doge_icon.png'); final largeScreen = 731; @@ -64,6 +65,7 @@ class MenuWidgetState extends State { Image wowneroIcon; Image zanoIcon; Image decredIcon; + Image dogecoinIcon; @override void initState() { @@ -255,6 +257,8 @@ class MenuWidgetState extends State { return zanoIcon; case WalletType.decred: return decredIcon; + case WalletType.dogecoin: + return dogecoinIcon; default: throw Exception('No icon for ${type.toString()}'); } diff --git a/lib/store/settings_store.dart b/lib/store/settings_store.dart index 37e118b4e..6f78addaa 100644 --- a/lib/store/settings_store.dart +++ b/lib/store/settings_store.dart @@ -1070,6 +1070,7 @@ abstract class SettingsStoreBase with Store { final wowneroNodeId = sharedPreferences.getInt(PreferencesKey.currentWowneroNodeIdKey); final zanoNodeId = sharedPreferences.getInt(PreferencesKey.currentZanoNodeIdKey); final decredNodeId = sharedPreferences.getInt(PreferencesKey.currentDecredNodeIdKey); + final dogecoinNodeId = sharedPreferences.getInt(PreferencesKey.currentDogecoinNodeIdKey); /// get the selected node, if null, then use the default final moneroNode = nodeSource.get(nodeId) ?? @@ -1098,6 +1099,8 @@ abstract class SettingsStoreBase with Store { nodeSource.values.firstWhereOrNull((e) => e.uriRaw == wowneroDefaultNodeUri); final zanoNode = nodeSource.get(zanoNodeId) ?? nodeSource.values.firstWhereOrNull((e) => e.uriRaw == zanoDefaultNodeUri); + final dogecoinNode = nodeSource.get(dogecoinNodeId) ?? + nodeSource.values.firstWhereOrNull((e) => e.uriRaw == dogecoinDefaultNodeUri); final packageInfo = await PackageInfo.fromPlatform(); final deviceName = await _getDeviceName() ?? ''; @@ -1185,6 +1188,10 @@ abstract class SettingsStoreBase with Store { nodes[WalletType.decred] = decredNode; } + if (dogecoinNode != null) { + nodes[WalletType.dogecoin] = dogecoinNode; + } + final savedSyncMode = SyncMode.all.firstWhere((element) { return element.type.index == (sharedPreferences.getInt(PreferencesKey.syncModeKey) ?? 2); // default to 2 - daily sync }); @@ -1549,6 +1556,7 @@ abstract class SettingsStoreBase with Store { final wowneroNodeId = sharedPreferences.getInt(PreferencesKey.currentWowneroNodeIdKey); final zanoNodeId = sharedPreferences.getInt(PreferencesKey.currentZanoNodeIdKey); final decredNodeId = sharedPreferences.getInt(PreferencesKey.currentDecredNodeIdKey); + final dogecoinNodeId = sharedPreferences.getInt(PreferencesKey.currentDogecoinNodeIdKey); final moneroNode = nodeSource.get(nodeId); final bitcoinElectrumServer = nodeSource.get(bitcoinElectrumServerId); final litecoinElectrumServer = nodeSource.get(litecoinElectrumServerId); @@ -1562,6 +1570,7 @@ abstract class SettingsStoreBase with Store { final wowneroNode = nodeSource.get(wowneroNodeId); final zanoNode = nodeSource.get(zanoNodeId); final decredNode = nodeSource.get(decredNodeId); + final dogecoinNode = nodeSource.get(dogecoinNodeId); if (moneroNode != null) { nodes[WalletType.monero] = moneroNode; @@ -1616,6 +1625,10 @@ abstract class SettingsStoreBase with Store { nodes[WalletType.decred] = decredNode; } + if (dogecoinNode != null) { + nodes[WalletType.dogecoin] = dogecoinNode; + } + // MIGRATED: useTOTP2FA = await SecureKey.getBool( @@ -1757,6 +1770,10 @@ abstract class SettingsStoreBase with Store { break; case WalletType.zano: await _sharedPreferences.setInt(PreferencesKey.currentZanoNodeIdKey, node.key as int); + break; + case WalletType.dogecoin: + await _sharedPreferences.setInt(PreferencesKey.currentDogecoinNodeIdKey, node.key as int); + break; default: break; } diff --git a/lib/view_model/advanced_privacy_settings_view_model.dart b/lib/view_model/advanced_privacy_settings_view_model.dart index 803744590..620e18681 100644 --- a/lib/view_model/advanced_privacy_settings_view_model.dart +++ b/lib/view_model/advanced_privacy_settings_view_model.dart @@ -37,6 +37,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store { switch (type) { case WalletType.ethereum: case WalletType.bitcoinCash: + case WalletType.dogecoin: case WalletType.polygon: case WalletType.solana: case WalletType.tron: @@ -83,6 +84,7 @@ abstract class AdvancedPrivacySettingsViewModelBase with Store { WalletType.monero, WalletType.wownero, WalletType.zano, + WalletType.dogecoin, ].contains(type); @computed diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index e1ef832e7..3ecfedb2c 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -865,6 +865,7 @@ abstract class DashboardViewModelBase with Store { case WalletType.tron: case WalletType.wownero: case WalletType.decred: + case WalletType.dogecoin: return true; case WalletType.zano: case WalletType.haven: diff --git a/lib/view_model/dashboard/home_settings_view_model.dart b/lib/view_model/dashboard/home_settings_view_model.dart index fdf4d75a5..8d1a5f8fd 100644 --- a/lib/view_model/dashboard/home_settings_view_model.dart +++ b/lib/view_model/dashboard/home_settings_view_model.dart @@ -242,6 +242,7 @@ abstract class HomeSettingsViewModelBase with Store { case WalletType.wownero: case WalletType.bitcoinCash: case WalletType.decred: + case WalletType.dogecoin: return false; } diff --git a/lib/view_model/dashboard/sign_view_model.dart b/lib/view_model/dashboard/sign_view_model.dart index 5b1b4fc00..a175822c8 100644 --- a/lib/view_model/dashboard/sign_view_model.dart +++ b/lib/view_model/dashboard/sign_view_model.dart @@ -24,6 +24,7 @@ abstract class SignViewModelBase with Store { WalletType.bitcoin, WalletType.bitcoinCash, WalletType.litecoin, + WalletType.dogecoin, WalletType.haven, ].contains(wallet.type); diff --git a/lib/view_model/dashboard/transaction_list_item.dart b/lib/view_model/dashboard/transaction_list_item.dart index 808ccabea..b59011c01 100644 --- a/lib/view_model/dashboard/transaction_list_item.dart +++ b/lib/view_model/dashboard/transaction_list_item.dart @@ -167,6 +167,7 @@ class TransactionListItem extends ActionListItem with Keyable { case WalletType.bitcoin: case WalletType.litecoin: case WalletType.bitcoinCash: + case WalletType.dogecoin: amount = calculateFiatAmountRaw( cryptoAmount: bitcoin!.formatterBitcoinAmountToDouble(amount: transaction.amount), price: price); diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 2c7e1bbab..757da5910 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -166,7 +166,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with } bool get isElectrumWallet => - [WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type); + [WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash,WalletType.dogecoin ].contains(wallet.type); bool get hideAddressAfterExchange => [WalletType.monero, WalletType.wownero].contains(wallet.type); @@ -314,6 +314,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash, + WalletType.dogecoin, ].contains(wallet.type) && depositCurrency == wallet.currency; @@ -673,6 +674,7 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with WalletType.litecoin, WalletType.bitcoin, WalletType.bitcoinCash, + WalletType.dogecoin, ].contains(wallet.type)) { final priority = _settingsStore.priority[wallet.type]!; @@ -751,6 +753,10 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with depositCurrency = CryptoCurrency.bch; receiveCurrency = CryptoCurrency.xmr; break; + case WalletType.dogecoin: + depositCurrency = CryptoCurrency.doge; + receiveCurrency = CryptoCurrency.xmr; + break; case WalletType.haven: depositCurrency = CryptoCurrency.xhv; receiveCurrency = CryptoCurrency.btc; diff --git a/lib/view_model/node_list/node_create_or_edit_view_model.dart b/lib/view_model/node_list/node_create_or_edit_view_model.dart index 715f7e05f..85c52b27b 100644 --- a/lib/view_model/node_list/node_create_or_edit_view_model.dart +++ b/lib/view_model/node_list/node_create_or_edit_view_model.dart @@ -93,6 +93,7 @@ abstract class NodeCreateOrEditViewModelBase with Store { case WalletType.litecoin: case WalletType.bitcoinCash: case WalletType.bitcoin: + case WalletType.dogecoin: case WalletType.zano: case WalletType.decred: return false; diff --git a/lib/view_model/restore/wallet_restore_from_qr_code.dart b/lib/view_model/restore/wallet_restore_from_qr_code.dart index e522a20b2..94b1072ab 100644 --- a/lib/view_model/restore/wallet_restore_from_qr_code.dart +++ b/lib/view_model/restore/wallet_restore_from_qr_code.dart @@ -47,6 +47,9 @@ class WalletRestoreFromQRCode { 'decred': WalletType.decred, 'decred-wallet': WalletType.decred, 'decred_wallet': WalletType.decred, + 'dogecoin': WalletType.dogecoin, + 'dogecoin-wallet': WalletType.dogecoin, + 'dogecoin_wallet': WalletType.dogecoin }; static WalletType? _extractWalletType(String code) { diff --git a/lib/view_model/send/fees_view_model.dart b/lib/view_model/send/fees_view_model.dart index f6dd0f201..f56212d4d 100644 --- a/lib/view_model/send/fees_view_model.dart +++ b/lib/view_model/send/fees_view_model.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/decred/decred.dart'; +import 'package:cake_wallet/dogecoin/dogecoin.dart'; import 'package:cake_wallet/entities/priority_for_wallet_type.dart'; import 'package:cake_wallet/core/wallet_change_listener_view_model.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; @@ -91,6 +92,8 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor return transactionPriority == polygon!.getPolygonTransactionPrioritySlow(); case WalletType.decred: return transactionPriority == decred!.getDecredTransactionPrioritySlow(); + case WalletType.dogecoin: + return transactionPriority == dogecoin!.getDogeCoinTransactionPrioritySlow(); case WalletType.none: case WalletType.nano: case WalletType.banano: @@ -119,7 +122,8 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor bool get isElectrumWallet => wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin || - wallet.type == WalletType.bitcoinCash; + wallet.type == WalletType.bitcoinCash || + wallet.type == WalletType.dogecoin; String? get walletCurrencyName => wallet.currency.fullName?.toLowerCase() ?? wallet.currency.name; @@ -188,6 +192,9 @@ abstract class FeesViewModelBase extends WalletChangeListenerViewModel with Stor case WalletType.bitcoinCash: _settingsStore.priority[wallet.type] = bitcoinCash!.getDefaultTransactionPriority(); break; + case WalletType.dogecoin: + _settingsStore.priority[wallet.type] = dogecoin!.getDefaultTransactionPriority(); + break; case WalletType.polygon: _settingsStore.priority[wallet.type] = polygon!.getDefaultTransactionPriority(); break; diff --git a/lib/view_model/send/output.dart b/lib/view_model/send/output.dart index f4925a766..9350038de 100644 --- a/lib/view_model/send/output.dart +++ b/lib/view_model/send/output.dart @@ -99,6 +99,7 @@ abstract class OutputBase with Store { case WalletType.bitcoin: case WalletType.litecoin: case WalletType.bitcoinCash: + case WalletType.dogecoin: _amount = bitcoin!.formatterStringDoubleToBitcoinAmount(_cryptoAmount); break; case WalletType.decred: @@ -166,7 +167,8 @@ abstract class OutputBase with Store { return bitcoin!.formatterBitcoinAmountToDouble(amount: fee); } - if (_wallet.type == WalletType.litecoin || _wallet.type == WalletType.bitcoinCash) { + if (_wallet.type == WalletType.litecoin || _wallet.type == WalletType.bitcoinCash || + _wallet.type == WalletType.dogecoin) { return bitcoin!.formatterBitcoinAmountToDouble(amount: fee); } @@ -316,6 +318,7 @@ abstract class OutputBase with Store { case WalletType.bitcoin: case WalletType.litecoin: case WalletType.bitcoinCash: + case WalletType.dogecoin: maximumFractionDigits = 8; break; case WalletType.wownero: diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index fdb863866..3813c65d6 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -269,6 +269,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor case WalletType.bitcoin: case WalletType.litecoin: case WalletType.bitcoinCash: + case WalletType.dogecoin: case WalletType.monero: case WalletType.wownero: case WalletType.decred: @@ -317,12 +318,13 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor WalletType.monero, WalletType.wownero, WalletType.decred, - WalletType.bitcoinCash + WalletType.bitcoinCash, + WalletType.dogecoin ].contains(wallet.type); @computed bool get isElectrumWallet => - [WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type); + [WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash, WalletType.dogecoin].contains(wallet.type); @observable CryptoCurrency selectedCryptoCurrency; @@ -652,6 +654,7 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor switch (wallet.type) { case WalletType.bitcoin: case WalletType.bitcoinCash: + case WalletType.dogecoin: return bitcoin!.createBitcoinTransactionCredentials( outputs, priority: priority!, diff --git a/lib/view_model/settings/other_settings_view_model.dart b/lib/view_model/settings/other_settings_view_model.dart index 3036e8ae9..64265a09d 100644 --- a/lib/view_model/settings/other_settings_view_model.dart +++ b/lib/view_model/settings/other_settings_view_model.dart @@ -70,7 +70,8 @@ abstract class OtherSettingsViewModelBase with Store { if (_wallet.type == WalletType.bitcoin || _wallet.type == WalletType.litecoin || - _wallet.type == WalletType.bitcoinCash) { + _wallet.type == WalletType.bitcoinCash || + _wallet.type == WalletType.dogecoin) { final rate = bitcoin!.getFeeRate(_wallet, _priority); return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate); } @@ -83,7 +84,8 @@ abstract class OtherSettingsViewModelBase with Store { if (_wallet.type == WalletType.bitcoin || _wallet.type == WalletType.litecoin || - _wallet.type == WalletType.bitcoinCash) { + _wallet.type == WalletType.bitcoinCash || + _wallet.type == WalletType.dogecoin) { final rate = bitcoin!.getFeeRate(_wallet, _priority); return bitcoin!.bitcoinTransactionPriorityWithLabel(_priority, rate, customRate: customValue); diff --git a/lib/view_model/settings/privacy_settings_view_model.dart b/lib/view_model/settings/privacy_settings_view_model.dart index e78f61ec4..e70383c6e 100644 --- a/lib/view_model/settings/privacy_settings_view_model.dart +++ b/lib/view_model/settings/privacy_settings_view_model.dart @@ -43,6 +43,7 @@ abstract class PrivacySettingsViewModelBase with Store { WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash, + WalletType.dogecoin, WalletType.decred ].contains(_wallet.type); diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index bdb29e1c0..eec24a031 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -88,6 +88,9 @@ abstract class TransactionDetailsViewModelBase with Store { case WalletType.decred: _addDecredListItems(tx, dateFormat); break; + case WalletType.dogecoin: + _addDogecoinListItems(tx, dateFormat); + break; case WalletType.none: case WalletType.banano: break; @@ -193,6 +196,8 @@ abstract class TransactionDetailsViewModelBase with Store { return 'https://explorer.zano.org/transaction/${txId}'; case WalletType.decred: return 'https://${wallet.isTestnet ? "testnet" : "dcrdata"}.decred.org/tx/${txId.split(':')[0]}'; + case WalletType.dogecoin: + return 'https://blockchair.com/dogecoin/transaction/${txId}'; case WalletType.none: return ''; } @@ -206,6 +211,7 @@ abstract class TransactionDetailsViewModelBase with Store { return S.current.view_transaction_on + 'mempool.space'; case WalletType.litecoin: case WalletType.bitcoinCash: + case WalletType.dogecoin: return S.current.view_transaction_on + 'Blockchair.com'; case WalletType.haven: return S.current.view_transaction_on + 'explorer.havenprotocol.org'; @@ -750,6 +756,51 @@ abstract class TransactionDetailsViewModelBase with Store { items.addAll(_items); } + void _addDogecoinListItems(TransactionInfo tx, DateFormat dateFormat) { + final _items = [ + StandartListItem( + title: S.current.transaction_details_transaction_id, + value: tx.txHash, + key: ValueKey('standard_list_item_transaction_details_id_key'), + ), + StandartListItem( + title: S.current.transaction_details_date, + value: dateFormat.format(tx.date), + key: ValueKey('standard_list_item_transaction_details_date_key'), + ), + StandartListItem( + title: S.current.transaction_details_height, + value: '${tx.height}', + key: ValueKey('standard_list_item_transaction_details_height_key'), + ), + StandartListItem( + title: S.current.transaction_details_amount, + value: tx.amountFormatted(), + key: ValueKey('standard_list_item_transaction_details_amount_key'), + ), + if (tx.feeFormatted()?.isNotEmpty ?? false) + StandartListItem( + title: S.current.transaction_details_fee, + value: tx.feeFormatted()!, + key: ValueKey('standard_list_item_transaction_details_fee_key'), + ), + if (showRecipientAddress && tx.to != null) + StandartListItem( + title: S.current.transaction_details_recipient_address, + value: tx.to!, + key: ValueKey('standard_list_item_transaction_details_recipient_address_key'), + ), + if (tx.from != null) + StandartListItem( + title: S.current.transaction_details_source_address, + value: tx.from!, + key: ValueKey('standard_list_item_transaction_details_source_address_key'), + ), + ]; + + items.addAll(_items); + } + @action Future _checkForRBF(TransactionInfo tx) async { if (wallet.type == WalletType.bitcoin && diff --git a/lib/view_model/unspent_coins/unspent_coins_details_view_model.dart b/lib/view_model/unspent_coins/unspent_coins_details_view_model.dart index 16f59aeb9..9cf6ed4bf 100644 --- a/lib/view_model/unspent_coins/unspent_coins_details_view_model.dart +++ b/lib/view_model/unspent_coins/unspent_coins_details_view_model.dart @@ -49,7 +49,7 @@ abstract class UnspentCoinsDetailsViewModelBase with Store { }) ]; - if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(_type)) { + if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash, WalletType.dogecoin].contains(_type)) { items.add(BlockExplorerListItem( title: S.current.view_in_block_explorer, value: _explorerDescription(_type), @@ -71,6 +71,8 @@ abstract class UnspentCoinsDetailsViewModelBase with Store { return 'https://litecoin.earlyordies.com/tx/${txId}'; case WalletType.bitcoinCash: return 'https://blockchair.com/bitcoin-cash/transaction/${txId}'; + case WalletType.dogecoin: + return 'https://dogechain.info/tx/${txId}'; default: return ''; } @@ -84,6 +86,8 @@ abstract class UnspentCoinsDetailsViewModelBase with Store { return S.current.view_transaction_on + 'Earlyordies.com'; case WalletType.bitcoinCash: return S.current.view_transaction_on + 'Blockchair.com'; + case WalletType.dogecoin: + return S.current.view_transaction_on + 'Dogechain.info'; default: return ''; } diff --git a/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart b/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart index bae4677cf..5799cb58d 100644 --- a/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart +++ b/lib/view_model/unspent_coins/unspent_coins_list_view_model.dart @@ -91,7 +91,7 @@ abstract class UnspentCoinsListViewModelBase with Store { return monero!.formatterMoneroAmountToString(amount: fullBalance); if (wallet.type == WalletType.wownero) return wownero!.formatterWowneroAmountToString(amount: fullBalance); - if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type)) + if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash, WalletType.dogecoin].contains(wallet.type)) return bitcoin!.formatterBitcoinAmountToString(amount: fullBalance); if (wallet.type == WalletType.decred) return decred!.formatterDecredAmountToString(amount: fullBalance); @@ -105,7 +105,7 @@ abstract class UnspentCoinsListViewModelBase with Store { if (wallet.type == WalletType.wownero) { await wownero!.updateUnspents(wallet); } - if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type)) { + if ([WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash, WalletType.dogecoin].contains(wallet.type)) { await bitcoin!.updateUnspents(wallet); } if (wallet.type == WalletType.decred) { @@ -123,6 +123,7 @@ abstract class UnspentCoinsListViewModelBase with Store { case WalletType.bitcoin: case WalletType.litecoin: case WalletType.bitcoinCash: + case WalletType.dogecoin: return bitcoin!.getUnspents(wallet, coinTypeToSpendFrom: coinTypeToSpendFrom); case WalletType.decred: return decred!.getUnspents(wallet); @@ -140,6 +141,7 @@ abstract class UnspentCoinsListViewModelBase with Store { case WalletType.bitcoin: case WalletType.litecoin: case WalletType.bitcoinCash: + case WalletType.dogecoin: return bitcoin!.getUnspents(wallet, coinTypeToSpendFrom: overrideCoinTypeToSpendFrom); case WalletType.decred: return decred!.getUnspents(wallet); diff --git a/lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart index c417f6f4e..269b93241 100644 --- a/lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart @@ -48,7 +48,8 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store { bool get isElectrum => _wallet.type == WalletType.bitcoin || _wallet.type == WalletType.bitcoinCash || - _wallet.type == WalletType.litecoin; + _wallet.type == WalletType.litecoin || + _wallet.type == WalletType.dogecoin; Future save() async { try { diff --git a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart index 4cae799b1..1e02b0fba 100644 --- a/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart +++ b/lib/view_model/wallet_address_list/wallet_address_list_view_model.dart @@ -242,6 +242,22 @@ class DecredURI extends PaymentURI { } } +class DogeURI extends PaymentURI { + DogeURI({required String amount, required String address}) + : super(amount: amount, address: address); + + @override + String toString() { + var base = 'doge:' + address; + + if (amount.isNotEmpty) { + base += '?amount=${amount.replaceAll(',', '.')}'; + } + + return base; + } +} + abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewModel with Store { WalletAddressListViewModelBase({ required AppStore appStore, @@ -350,6 +366,8 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo return ZanoURI(amount: amount, address: address.address); case WalletType.decred: return DecredURI(amount: amount, address: address.address); + case WalletType.dogecoin: + return DogeURI(amount: amount, address: address.address); case WalletType.none: throw Exception('Unexpected type: ${type.toString()}'); } @@ -586,12 +604,13 @@ abstract class WalletAddressListViewModelBase extends WalletChangeListenerViewMo WalletType.bitcoinCash, WalletType.bitcoin, WalletType.litecoin, - WalletType.decred + WalletType.decred, + WalletType.dogecoin, ].contains(wallet.type); @computed bool get isElectrumWallet => - [WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash].contains(wallet.type); + [WalletType.bitcoin, WalletType.litecoin, WalletType.bitcoinCash, WalletType.dogecoin].contains(wallet.type); @computed bool get isBalanceAvailable => isElectrumWallet; diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index 2e7eb8144..1f7620452 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -164,6 +164,7 @@ abstract class WalletKeysViewModelBase with Store { case WalletType.bitcoin: case WalletType.litecoin: case WalletType.bitcoinCash: + case WalletType.dogecoin: if (_wallet.type == WalletType.bitcoin) { keys = bitcoin!.getSilentPaymentKeys(_appStore.wallet!); } @@ -261,6 +262,8 @@ abstract class WalletKeysViewModelBase with Store { return 'zano-wallet'; case WalletType.decred: return 'decred-wallet'; + case WalletType.dogecoin: + return 'dogecoin-wallet'; default: throw Exception('Unexpected wallet type: ${_wallet.type.toString()}'); } diff --git a/lib/view_model/wallet_new_vm.dart b/lib/view_model/wallet_new_vm.dart index 86d1be65f..6e7a4cb96 100644 --- a/lib/view_model/wallet_new_vm.dart +++ b/lib/view_model/wallet_new_vm.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/core/new_wallet_arguments.dart'; +import 'package:cake_wallet/dogecoin/dogecoin.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/zano/zano.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; @@ -96,6 +97,13 @@ abstract class WalletNewVMBase extends WalletCreationVM with Store { passphrase: passphrase, mnemonic: newWalletArguments!.mnemonic, ); + case WalletType.dogecoin: + return dogecoin!.createDogeCoinNewWalletCredentials( + name: name, + password: walletPassword, + passphrase: passphrase, + mnemonic: newWalletArguments!.mnemonic, + ); case WalletType.nano: case WalletType.banano: return nano!.createNanoNewWalletCredentials( diff --git a/lib/view_model/wallet_restore_view_model.dart b/lib/view_model/wallet_restore_view_model.dart index 3a7485c80..7e73cb83b 100644 --- a/lib/view_model/wallet_restore_view_model.dart +++ b/lib/view_model/wallet_restore_view_model.dart @@ -3,6 +3,7 @@ import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; import 'package:cake_wallet/core/generate_wallet_password.dart'; import 'package:cake_wallet/core/wallet_creation_service.dart'; import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/dogecoin/dogecoin.dart'; import 'package:cake_wallet/ethereum/ethereum.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/nano/nano.dart'; @@ -58,6 +59,7 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { case WalletType.bitcoinCash: case WalletType.zano: case WalletType.none: + case WalletType.dogecoin: availableModes = [WalletRestoreMode.seed]; break; } @@ -148,6 +150,13 @@ abstract class WalletRestoreViewModelBase extends WalletCreationVM with Store { password: password, passphrase: passphrase, ); + case WalletType.dogecoin: + return dogecoin!.createDogeCoinRestoreWalletFromSeedCredentials( + name: name, + mnemonic: seed, + password: password, + passphrase: passphrase, + ); case WalletType.nano: case WalletType.banano: return nano!.createNanoRestoreWalletFromSeedCredentials( diff --git a/model_generator.sh b/model_generator.sh index 7a5e3bfd7..4493e62f9 100755 --- a/model_generator.sh +++ b/model_generator.sh @@ -1,7 +1,7 @@ #!/bin/bash set -x -e -for cwcoin in cw_{core,evm,monero,bitcoin,nano,bitcoin_cash,solana,tron,wownero,zano,decred} +for cwcoin in cw_{core,evm,monero,bitcoin,nano,bitcoin_cash,solana,tron,wownero,zano,decred,dogecoin} do if [[ "x$1" == "xasync" ]]; then diff --git a/pubspec_base.yaml b/pubspec_base.yaml index af1e67df3..ce2cbc3bc 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -177,7 +177,7 @@ dependency_overrides: bitcoin_base: git: url: https://github.com/cake-tech/bitcoin_base - ref: cake-update-v10 + ref: cake-update-v11 ffi: 2.1.0 ledger_flutter_plus: git: @@ -223,6 +223,7 @@ flutter: - assets/wownero_node_list.yml - assets/zano_node_list.yml - assets/decred_node_list.yml + - assets/dogecoin_electrum_server_list.yml - assets/text/ - assets/faq/ - assets/animation/ diff --git a/scripts/android/pubspec_gen.sh b/scripts/android/pubspec_gen.sh index b980f877d..8170b7ff4 100755 --- a/scripts/android/pubspec_gen.sh +++ b/scripts/android/pubspec_gen.sh @@ -10,7 +10,7 @@ case $APP_ANDROID_TYPE in CONFIG_ARGS="--monero" ;; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred" + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin" ;; esac diff --git a/scripts/ios/app_config.sh b/scripts/ios/app_config.sh index b642d67e4..78a8f7d82 100755 --- a/scripts/ios/app_config.sh +++ b/scripts/ios/app_config.sh @@ -31,7 +31,7 @@ case $APP_IOS_TYPE in ;; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred" + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --zano --decred --dogecoin" ;; esac diff --git a/scripts/linux/app_config.sh b/scripts/linux/app_config.sh index 5d9d8597b..77bb6c272 100755 --- a/scripts/linux/app_config.sh +++ b/scripts/linux/app_config.sh @@ -13,7 +13,7 @@ CONFIG_ARGS="" case $APP_LINUX_TYPE in $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --excludeFlutterSecureStorage";; + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin --excludeFlutterSecureStorage";; esac cp -rf pubspec_description.yaml pubspec.yaml diff --git a/scripts/macos/app_config.sh b/scripts/macos/app_config.sh index 1c53ef1f2..dfea3dee2 100755 --- a/scripts/macos/app_config.sh +++ b/scripts/macos/app_config.sh @@ -36,7 +36,7 @@ case $APP_MACOS_TYPE in $MONERO_COM) CONFIG_ARGS="--monero";; $CAKEWALLET) - CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero";; + CONFIG_ARGS="--monero --bitcoin --ethereum --polygon --nano --bitcoinCash --solana --tron --wownero --dogecoin";; esac cp -rf pubspec_description.yaml pubspec.yaml diff --git a/tool/configure.dart b/tool/configure.dart index d91c1154b..ee44fba4f 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -11,6 +11,7 @@ const tronOutputPath = 'lib/tron/tron.dart'; const wowneroOutputPath = 'lib/wownero/wownero.dart'; const zanoOutputPath = 'lib/zano/zano.dart'; const decredOutputPath = 'lib/decred/decred.dart'; +const dogecoinOutputPath = 'lib/dogecoin/dogecoin.dart'; const walletTypesPath = 'lib/wallet_types.g.dart'; const secureStoragePath = 'lib/core/secure_storage.dart'; const pubspecDefaultPath = 'pubspec_default.yaml'; @@ -30,6 +31,7 @@ Future main(List args) async { final hasWownero = args.contains('${prefix}wownero'); final hasZano = args.contains('${prefix}zano'); final hasDecred = args.contains('${prefix}decred'); + final hasDogecoin = args.contains('${prefix}dogecoin'); final excludeFlutterSecureStorage = args.contains('${prefix}excludeFlutterSecureStorage'); await generateBitcoin(hasBitcoin); @@ -44,6 +46,7 @@ Future main(List args) async { await generateZano(hasZano); // await generateBanano(hasEthereum); await generateDecred(hasDecred); + await generateDogecoin(hasDogecoin); await generatePubspec( hasMonero: hasMonero, @@ -59,6 +62,7 @@ Future main(List args) async { hasWownero: hasWownero, hasZano: hasZano, hasDecred: hasDecred, + hasDogecoin: hasDogecoin, ); await generateWalletTypes( hasMonero: hasMonero, @@ -73,6 +77,7 @@ Future main(List args) async { hasWownero: hasWownero, hasZano: hasZano, hasDecred: hasDecred, + hasDogecoin: hasDogecoin, ); await injectSecureStorage(!excludeFlutterSecureStorage); } @@ -1414,6 +1419,61 @@ abstract class Decred { await outputFile.writeAsString(output); } +Future generateDogecoin(bool hasImplementation) async { + final outputFile = File(dogecoinOutputPath); + const dogecoinCommonHeaders = """ +import 'package:cw_core/transaction_priority.dart'; +import 'package:cw_core/unspent_coins_info.dart'; +import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/wallet_info.dart'; +import 'package:cw_core/wallet_service.dart'; +import 'package:hive/hive.dart'; +"""; + const dogecoinCWHeaders = """ +import 'package:cw_dogecoin/cw_dogecoin.dart'; +"""; + const dogecoinCwPart = "part 'cw_dogecoin.dart';"; + const dogecoinContent = """ +abstract class DogeCoin { + + WalletService createDogeCoinWalletService( + Box walletInfoSource, Box unspentCoinSource, bool isDirect); + + WalletCredentials createDogeCoinNewWalletCredentials( + {required String name, WalletInfo? walletInfo, String? password, String? passphrase, String? mnemonic}); + + WalletCredentials createDogeCoinRestoreWalletFromSeedCredentials( + {required String name, required String mnemonic, required String password, String? passphrase}); + + TransactionPriority deserializeDogeCoinTransactionPriority(int raw); + + TransactionPriority getDefaultTransactionPriority(); + + List getTransactionPriorities(); + + TransactionPriority getDogeCoinTransactionPrioritySlow(); +} +"""; + + const dogecoinEmptyDefinition = 'DogeCoin? dogecoin;\n'; + const dogecoinCWDefinition = 'DogeCoin? dogecoin = CWDogeCoin();\n'; + + final output = '$dogecoinCommonHeaders\n' + + (hasImplementation ? '$dogecoinCWHeaders\n' : '\n') + + (hasImplementation ? '$dogecoinCwPart\n\n' : '\n') + + (hasImplementation + ? dogecoinCWDefinition + : dogecoinEmptyDefinition) + + '\n' + + dogecoinContent; + + if (outputFile.existsSync()) { + await outputFile.delete(); + } + + await outputFile.writeAsString(output); +} + Future generatePubspec({ required bool hasMonero, required bool hasBitcoin, @@ -1428,6 +1488,7 @@ Future generatePubspec({ required bool hasWownero, required bool hasZano, required bool hasDecred, + required bool hasDogecoin, }) async { const cwCore = """ cw_core: @@ -1492,6 +1553,10 @@ Future generatePubspec({ cw_decred: path: ./cw_decred """; + const cwDogecoin = """ + cw_dogecoin: + path: ./cw_dogecoin + """; final inputFile = File(pubspecOutputPath); final inputText = await inputFile.readAsString(); final inputLines = inputText.split('\n'); @@ -1557,6 +1622,10 @@ Future generatePubspec({ output += '\n$cwZano'; } + if (hasDogecoin) { + output += '\n$cwDogecoin'; + } + final outputLines = output.split('\n'); inputLines.insertAll(dependenciesIndex + 1, outputLines); final outputContent = inputLines.join('\n'); @@ -1582,6 +1651,7 @@ Future generateWalletTypes({ required bool hasWownero, required bool hasZano, required bool hasDecred, + required bool hasDogecoin, }) async { final walletTypesFile = File(walletTypesPath); @@ -1609,6 +1679,10 @@ Future generateWalletTypes({ outputContent += '\tWalletType.litecoin,\n'; } + if (hasDogecoin) { + outputContent += '\tWalletType.dogecoin,\n'; + } + if (hasBitcoinCash) { outputContent += '\tWalletType.bitcoinCash,\n'; }