diff --git a/assets/decred_node_list.yml b/assets/decred_node_list.yml index 75a9d69ddc..70c3193886 100644 --- a/assets/decred_node_list.yml +++ b/assets/decred_node_list.yml @@ -1,6 +1,3 @@ - uri: is_default: true -- - uri: dcrd.sethforprivacy.com:9108 - useSSL: true diff --git a/assets/images/2.0x/decred.png b/assets/images/2.0x/decred.png index 2142dc1025..2f4919cecd 100644 Binary files a/assets/images/2.0x/decred.png and b/assets/images/2.0x/decred.png differ diff --git a/assets/images/3.0x/decred.png b/assets/images/3.0x/decred.png index eef85bde56..b2c9ac8180 100644 Binary files a/assets/images/3.0x/decred.png and b/assets/images/3.0x/decred.png differ diff --git a/assets/images/dcr_icon.png b/assets/images/dcr_icon.png index 609873611f..757cd03882 100644 Binary files a/assets/images/dcr_icon.png and b/assets/images/dcr_icon.png differ diff --git a/assets/images/decred.png b/assets/images/decred.png index 271413394c..0b12f2ef01 100644 Binary files a/assets/images/decred.png and b/assets/images/decred.png differ diff --git a/assets/images/decred_icon.png b/assets/images/decred_icon.png index 6f181da18c..9391abc3d8 100644 Binary files a/assets/images/decred_icon.png and b/assets/images/decred_icon.png differ diff --git a/cw_core/lib/receive_page_option.dart b/cw_core/lib/receive_page_option.dart index 786d07bc56..f7d69bf0a2 100644 --- a/cw_core/lib/receive_page_option.dart +++ b/cw_core/lib/receive_page_option.dart @@ -2,6 +2,7 @@ import 'package:cw_core/enumerate.dart'; class ReceivePageOption implements Enumerate { static const mainnet = ReceivePageOption._('mainnet'); + static const testnet = ReceivePageOption._('testnet'); static const anonPayInvoice = ReceivePageOption._('anonPayInvoice'); static const anonPayDonationLink = ReceivePageOption._('anonPayDonationLink'); diff --git a/cw_decred/lib/amount_format.dart b/cw_decred/lib/amount_format.dart index 421cec1b86..c09f76b3b3 100644 --- a/cw_decred/lib/amount_format.dart +++ b/cw_decred/lib/amount_format.dart @@ -7,8 +7,8 @@ final decredAmountFormat = NumberFormat() ..maximumFractionDigits = decredAmountLength ..minimumFractionDigits = 1; -String decredAmountToString({required int amount}) => decredAmountFormat - .format(cryptoAmountToDouble(amount: amount, divider: decredAmountDivider)); +String decredAmountToString({required int amount}) => + decredAmountFormat.format(cryptoAmountToDouble(amount: amount, divider: decredAmountDivider)); double decredAmountToDouble({required int amount}) => cryptoAmountToDouble(amount: amount, divider: decredAmountDivider); diff --git a/cw_decred/lib/api/libdcrwallet.dart b/cw_decred/lib/api/libdcrwallet.dart index 0657565e7b..ebc450ce88 100644 --- a/cw_decred/lib/api/libdcrwallet.dart +++ b/cw_decred/lib/api/libdcrwallet.dart @@ -7,10 +7,9 @@ import 'package:cw_decred/api/util.dart'; final int ErrCodeNotSynced = 1; -final String libraryName = - Platform.isAndroid || Platform.isLinux // TODO: Linux. - ? 'libdcrwallet.so' - : 'cw_decred.framework/cw_decred'; +final String libraryName = Platform.isAndroid || Platform.isLinux // TODO: Linux. + ? 'libdcrwallet.so' + : 'cw_decred.framework/cw_decred'; final dcrwalletApi = libdcrwallet(DynamicLibrary.open(libraryName)); @@ -53,14 +52,12 @@ void createWalletSync(Map args) { final network = args["network"]!.toCString(); executePayloadFn( - fn: () => - dcrwalletApi.createWallet(name, dataDir, network, password, mnemonic), + fn: () => dcrwalletApi.createWallet(name, dataDir, network, password, mnemonic), ptrsToFree: [name, dataDir, network, password, mnemonic], ); } -void createWatchOnlyWallet( - String walletName, String datadir, String pubkey, String network) { +void createWatchOnlyWallet(String walletName, String datadir, String pubkey, String network) { final cName = walletName.toCString(); final cDataDir = datadir.toCString(); final cPub = pubkey.toCString(); @@ -72,8 +69,7 @@ void createWatchOnlyWallet( } /// loadWalletAsync calls the libdcrwallet's loadWallet function asynchronously. -Future loadWalletAsync( - {required String name, required String dataDir, required String net}) { +Future loadWalletAsync({required String name, required String dataDir, required String net}) { final args = { "name": name, "dataDir": dataDir, @@ -118,8 +114,7 @@ void closeWallet(String walletName) { ); } -String changeWalletPassword( - String walletName, String currentPassword, String newPassword) { +String changeWalletPassword(String walletName, String currentPassword, String newPassword) { final cName = walletName.toCString(); final cCurrentPass = currentPassword.toCString(); final cNewPass = newPassword.toCString(); @@ -140,22 +135,23 @@ String? walletSeed(String walletName, String walletPassword) { return res.payload; } -String? currentReceiveAddress(String walletName) { - final cName = walletName.toCString(); - final res = executePayloadFn( - fn: () => dcrwalletApi.currentReceiveAddress(cName), - ptrsToFree: [cName], - skipErrorCheck: true, // errCode is checked below, before checking err - ); - - if (res.errCode == ErrCodeNotSynced) { - // Wallet is not synced. We do not want to give out a used address so give - // nothing. - return null; - } - checkErr(res.err); - return res.payload; -} +// NOTE: Currently unused. +// String? currentReceiveAddress(String walletName) { +// final cName = walletName.toCString(); +// final res = executePayloadFn( +// fn: () => dcrwalletApi.currentReceiveAddress(cName), +// ptrsToFree: [cName], +// skipErrorCheck: true, // errCode is checked below, before checking err +// ); +// +// if (res.errCode == ErrCodeNotSynced) { +// // Wallet is not synced. We do not want to give out a used address so give +// // nothing. +// return null; +// } +// checkErr(res.err); +// return res.payload; +// } String syncStatus(String walletName) { final cName = walletName.toCString(); @@ -185,13 +181,11 @@ String estimateFee(String walletName, int numBlocks) { return res.payload; } -String createSignedTransaction( - String walletName, String createSignedTransactionReq) { +String createSignedTransaction(String walletName, String createSignedTransactionReq) { final cName = walletName.toCString(); final cCreateSignedTransactionReq = createSignedTransactionReq.toCString(); final res = executePayloadFn( - fn: () => dcrwalletApi.createSignedTransaction( - cName, cCreateSignedTransactionReq), + fn: () => dcrwalletApi.createSignedTransaction(cName, cCreateSignedTransactionReq), ptrsToFree: [cName, cCreateSignedTransactionReq], ); return res.payload; @@ -246,8 +240,7 @@ String rescanFromHeight(String walletName, String height) { return res.payload; } -Future signMessageAsync( - String name, String message, String address, String walletPass) { +Future signMessageAsync(String name, String message, String address, String walletPass) { final args = { "walletname": name, "message": message, @@ -306,10 +299,12 @@ String defaultPubkey(String walletName) { return res.payload; } -String addresses(String walletName) { +String addresses(String walletName, String nUsed, String nUnused) { final cName = walletName.toCString(); + final cNUsed = nUsed.toCString(); + final cNUnused = nUnused.toCString(); final res = executePayloadFn( - fn: () => dcrwalletApi.addresses(cName), + fn: () => dcrwalletApi.addresses(cName, cNUsed, cNUnused), ptrsToFree: [cName], ); return res.payload; diff --git a/cw_decred/lib/balance.dart b/cw_decred/lib/balance.dart index add51b6a46..a9575c70e2 100644 --- a/cw_decred/lib/balance.dart +++ b/cw_decred/lib/balance.dart @@ -11,10 +11,8 @@ class DecredBalance extends Balance { final int unconfirmed; @override - String get formattedAvailableBalance => - decredAmountToString(amount: confirmed); + String get formattedAvailableBalance => decredAmountToString(amount: confirmed); @override - String get formattedAdditionalBalance => - decredAmountToString(amount: unconfirmed); + String get formattedAdditionalBalance => decredAmountToString(amount: unconfirmed); } diff --git a/cw_decred/lib/transaction_credentials.dart b/cw_decred/lib/transaction_credentials.dart index fa59e6633a..5ace384f47 100644 --- a/cw_decred/lib/transaction_credentials.dart +++ b/cw_decred/lib/transaction_credentials.dart @@ -2,8 +2,7 @@ import 'package:cw_decred/transaction_priority.dart'; import 'package:cw_core/output_info.dart'; class DecredTransactionCredentials { - DecredTransactionCredentials(this.outputs, - {required this.priority, this.feeRate}); + DecredTransactionCredentials(this.outputs, {required this.priority, this.feeRate}); final List outputs; final DecredTransactionPriority? priority; diff --git a/cw_decred/lib/transaction_history.dart b/cw_decred/lib/transaction_history.dart index ab65a01608..02227aa9cc 100644 --- a/cw_decred/lib/transaction_history.dart +++ b/cw_decred/lib/transaction_history.dart @@ -8,12 +8,10 @@ class DecredTransactionHistory extends TransactionHistoryBase { } @override - void addOne(TransactionInfo transaction) => - transactions[transaction.id] = transaction; + void addOne(TransactionInfo transaction) => transactions[transaction.id] = transaction; @override - void addMany(Map transactions) => - this.transactions.addAll(transactions); + void addMany(Map transactions) => this.transactions.addAll(transactions); @override Future save() async {} @@ -22,8 +20,7 @@ class DecredTransactionHistory extends TransactionHistoryBase { bool update(Map txs) { var foundOldTx = false; txs.forEach((_, tx) { - if (!this.transactions.containsKey(tx.id) || - this.transactions[tx.id]!.isPending) { + if (!this.transactions.containsKey(tx.id) || this.transactions[tx.id]!.isPending) { this.transactions[tx.id] = tx; } else { foundOldTx = true; diff --git a/cw_decred/lib/transaction_priority.dart b/cw_decred/lib/transaction_priority.dart index 1e1482f2f5..80a9c7e3aa 100644 --- a/cw_decred/lib/transaction_priority.dart +++ b/cw_decred/lib/transaction_priority.dart @@ -5,12 +5,10 @@ class DecredTransactionPriority extends TransactionPriority { : super(title: title, raw: raw); static const List all = [fast, medium, slow]; - static const DecredTransactionPriority slow = - DecredTransactionPriority(title: 'Slow', raw: 0); + static const DecredTransactionPriority slow = DecredTransactionPriority(title: 'Slow', raw: 0); static const DecredTransactionPriority medium = DecredTransactionPriority(title: 'Medium', raw: 1); - static const DecredTransactionPriority fast = - DecredTransactionPriority(title: 'Fast', raw: 2); + static const DecredTransactionPriority fast = DecredTransactionPriority(title: 'Fast', raw: 2); static DecredTransactionPriority deserialize({required int raw}) { switch (raw) { @@ -21,8 +19,7 @@ class DecredTransactionPriority extends TransactionPriority { case 2: return fast; default: - throw Exception( - 'Unexpected token: $raw for DecredTransactionPriority deserialize'); + throw Exception('Unexpected token: $raw for DecredTransactionPriority deserialize'); } } @@ -34,8 +31,7 @@ class DecredTransactionPriority extends TransactionPriority { switch (this) { case DecredTransactionPriority.slow: - label = - 'Slow ~24hrs'; // '${S.current.transaction_priority_slow} ~24hrs'; + label = 'Slow ~24hrs'; // '${S.current.transaction_priority_slow} ~24hrs'; break; case DecredTransactionPriority.medium: label = 'Medium'; // S.current.transaction_priority_medium; diff --git a/cw_decred/lib/wallet.dart b/cw_decred/lib/wallet.dart index ebf5be15be..2e71bc50b8 100644 --- a/cw_decred/lib/wallet.dart +++ b/cw_decred/lib/wallet.dart @@ -40,6 +40,8 @@ abstract class DecredWalletBase this.watchingOnly = walletInfo.derivationPath == DecredWalletService.pubkeyRestorePath || walletInfo.derivationPath == DecredWalletService.pubkeyRestorePathTestnet, this.balance = ObservableMap.of({CryptoCurrency.dcr: DecredBalance.zero()}), + this.isTestnet = walletInfo.derivationPath == DecredWalletService.seedRestorePathTestnet || + walletInfo.derivationPath == DecredWalletService.pubkeyRestorePathTestnet, super(walletInfo) { walletAddresses = DecredWalletAddresses(walletInfo); transactionHistory = DecredTransactionHistory(); @@ -48,13 +50,19 @@ abstract class DecredWalletBase // NOTE: Hitting this max fee would be unexpected with current on chain use // but this may need to be updated in the future. final maxFeeRate = 100000; - final syncInterval = 5; // seconds + // syncIntervalSyncing is used up until synced, then transactions are checked + // every syncIntervalSynced. + final syncIntervalSyncing = 5; // seconds + final syncIntervalSynced = 30; // seconds static final defaultFeeRate = 10000; final String _password; final idPrefix = "decred_"; + // synced is used to set the syncTimer interval. + bool synced = false; bool watchingOnly; bool connecting = false; String persistantPeer = ""; + bool _isEnabledAutoGenerateSubaddress = true; FeeCache feeRateFast = FeeCache(defaultFeeRate); FeeCache feeRateMedium = FeeCache(defaultFeeRate); FeeCache feeRateSlow = FeeCache(defaultFeeRate); @@ -83,13 +91,16 @@ abstract class DecredWalletBase @override Object get keys => {}; + @override + bool isTestnet; + String get pubkey { return libdcrwallet.defaultPubkey(walletInfo.name); } Future init() async { - updateBalance(); - + await updateBalance(); + await updateTransactionHistory(); await walletAddresses.init(); fetchTransactions(); @@ -97,12 +108,33 @@ abstract class DecredWalletBase void performBackgroundTasks() async { if (!checkSync()) { + if (synced == true) { + synced = false; + if (syncTimer != null) { + syncTimer!.cancel(); + } + syncTimer = Timer.periodic( + Duration(seconds: syncIntervalSyncing), (Timer t) => performBackgroundTasks()); + } return; } - // final res = libdcrwallet.bestBlock(walletInfo.name); - // final decoded = json.decode(res); - // final hash = decoded["hash"] ?? ""; - updateBalance(); + await updateBalance(); + await walletAddresses.updateAddressesInBox(); + // Set sync check interval lower since we are synced. + if (synced == false) { + synced = true; + if (syncTimer != null) { + syncTimer!.cancel(); + } + syncTimer = Timer.periodic( + Duration(seconds: syncIntervalSynced), (Timer t) => performBackgroundTasks()); + } + await updateTransactionHistory(); + } + + Future updateTransactionHistory() async { + // from is the number of transactions skipped from most recent, not block + // height. var from = 0; while (true) { // Transactions are returned from newest to oldest. Loop fetching 5 txn @@ -146,11 +178,6 @@ abstract class DecredWalletBase if (syncStatusCode > 4) { syncStatus = SyncedSyncStatus(); - // Initiate a receive address in case we lose peers later. - if (walletAddresses.currentAddr == '') { - walletAddresses.address; - walletAddresses.updateAddressesInBox(); - } return true; } @@ -209,10 +236,7 @@ abstract class DecredWalletBase } persistantPeer = addr; libdcrwallet.closeWallet(walletInfo.name); - final network = walletInfo.derivationPath == DecredWalletService.seedRestorePathTestnet || - walletInfo.derivationPath == DecredWalletService.pubkeyRestorePathTestnet - ? "testnet" - : "mainnet"; + final network = isTestnet ? "testnet" : "mainnet"; libdcrwallet.loadWalletSync({ "name": walletInfo.name, "dataDir": walletInfo.dirPath, @@ -244,8 +268,8 @@ abstract class DecredWalletBase name: walletInfo.name, peers: persistantPeer, ); - syncTimer = - Timer.periodic(Duration(seconds: syncInterval), (Timer t) => performBackgroundTasks()); + syncTimer = Timer.periodic( + Duration(seconds: syncIntervalSyncing), (Timer t) => performBackgroundTasks()); } catch (e) { printV(e.toString()); syncStatus = FailedSyncStatus(); @@ -264,62 +288,63 @@ abstract class DecredWalletBase throw "unable to send with watching only wallet"; }); } + var totalIn = 0; final ignoreInputs = []; this.unspentCoinsInfo.values.forEach((unspent) { if (unspent.isFrozen || !unspent.isSending) { final input = {"txid": unspent.hash, "vout": unspent.vout}; ignoreInputs.add(input); + return; } + totalIn += unspent.value; }); final creds = credentials as DecredTransactionCredentials; var totalAmt = 0; + var sendAll = false; final outputs = []; for (final out in creds.outputs) { var amt = 0; if (out.sendAll) { - // get all spendable inputs amount - totalAmt = unspentCoinsInfo.values.fold(0, (sum, unspent) { - if (unspent.isFrozen || !unspent.isSending) { - return sum; - } - - return sum + unspent.value; - }); - break; - } - if (out.cryptoAmount != null) { + if (creds.outputs.length != 1) { + throw "can only send all to one output"; + } + sendAll = true; + totalAmt = totalIn; + } else if (out.cryptoAmount != null) { final coins = double.parse(out.cryptoAmount!); amt = (coins * 1e8).toInt(); } totalAmt += amt; final o = { "address": out.isParsedAddress ? out.extractedAddress! : out.address, - "amount": amt, + "amount": amt }; outputs.add(o); } // The inputs are always used. Currently we don't have use for this - // argument. + // argument. sendall ingores output value and sends everything. final signReq = { // "inputs": inputs, "ignoreInputs": ignoreInputs, "outputs": outputs, "feerate": creds.feeRate ?? defaultFeeRate, "password": _password, + "sendall": sendAll, }; final res = libdcrwallet.createSignedTransaction(walletInfo.name, jsonEncode(signReq)); final decoded = json.decode(res); final signedHex = decoded["signedhex"]; final send = () async { libdcrwallet.sendRawTransaction(walletInfo.name, signedHex); + await updateBalance(); }; + final fee = decoded["fee"] ?? 0; + if (sendAll) { + totalAmt = (totalAmt - fee).toInt(); + } return DecredPendingTransaction( - txid: decoded["txid"] ?? "", - amount: totalAmt, - fee: decoded["fee"] ?? 0, - rawHex: signedHex, - send: send); + txid: decoded["txid"] ?? "", amount: totalAmt, fee: fee, rawHex: signedHex, send: send); } int feeRate(TransactionPriority priority) { @@ -503,9 +528,9 @@ abstract class DecredWalletBase } var addr = address; if (addr == null) { - addr = libdcrwallet.currentReceiveAddress(walletInfo.name); + addr = walletAddresses.address; } - if (addr == null) { + if (addr == "") { throw "unable to get an address from unsynced wallet"; } return libdcrwallet.signMessageAsync(walletInfo.name, message, addr, _password); @@ -619,4 +644,15 @@ abstract class DecredWalletBase @override String get password => _password; + + @override + set isEnabledAutoGenerateSubaddress(bool value) { + this._isEnabledAutoGenerateSubaddress = value; + this.walletAddresses.isEnabledAutoGenerateSubaddress = value; + } + + @override + bool get isEnabledAutoGenerateSubaddress { + return this._isEnabledAutoGenerateSubaddress; + } } diff --git a/cw_decred/lib/wallet_addresses.dart b/cw_decred/lib/wallet_addresses.dart index 3f3763b398..95731351c1 100644 --- a/cw_decred/lib/wallet_addresses.dart +++ b/cw_decred/lib/wallet_addresses.dart @@ -1,55 +1,132 @@ import 'dart:convert'; -import 'dart:developer'; +import 'package:mobx/mobx.dart'; +import 'package:cw_core/address_info.dart'; import 'package:cw_core/wallet_addresses.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_decred/api/libdcrwallet.dart' as libdcrwallet; -class DecredWalletAddresses extends WalletAddresses { - DecredWalletAddresses(WalletInfo walletInfo) : super(walletInfo); +part 'wallet_addresses.g.dart'; +class DecredWalletAddresses = DecredWalletAddressesBase with _$DecredWalletAddresses; + +abstract class DecredWalletAddressesBase extends WalletAddresses with Store { + DecredWalletAddressesBase(WalletInfo walletInfo) : super(walletInfo); String currentAddr = ''; + bool isEnabledAutoGenerateSubaddress = true; + + @observable + String selectedAddr = ''; @override + @computed String get address { - final cAddr = libdcrwallet.currentReceiveAddress(walletInfo.name) ?? ''; - if (cAddr != '') { - currentAddr = cAddr; - } - return currentAddr; - } - - String generateNewAddress() { - final nAddr = libdcrwallet.newExternalAddress(walletInfo.name) ?? ''; - if (nAddr != '') { - currentAddr = nAddr; - } - return nAddr; - } - - List addresses() { - final res = libdcrwallet.addresses(walletInfo.name); - final addrs = (json.decode(res) as List).cast(); - return addrs; + return selectedAddr; } @override - set address(String addr) {} + set address(value) { + selectedAddr = value; + } @override Future init() async { - address = walletInfo.address; + if (walletInfo.addresses != null) { + addressesMap = walletInfo.addresses!; + } + if (walletInfo.addressInfos != null) { + addressInfos = walletInfo.addressInfos!; + } + if (walletInfo.usedAddresses != null) { + usedAddresses = {...walletInfo.usedAddresses!}; + } await updateAddressesInBox(); } @override Future updateAddressesInBox() async { - try { - addressesMap.clear(); - addressesMap[address] = ''; - await saveAddressesInBox(); - } catch (e) { - log(e.toString()); + final addrs = libAddresses(); + final allAddrs = new List.from(addrs.usedAddrs)..addAll(addrs.unusedAddrs); + + // Add all addresses. + allAddrs.forEach((addr) { + if (addressesMap.containsKey(addr)) { + return; + } + addressesMap[addr] = ""; + addressInfos[0] ??= []; + addressInfos[0]?.add(AddressInfo(address: addr, label: "", accountIndex: 0)); + }); + + // Add used addresses. + addrs.usedAddrs.forEach((addr) { + if (!usedAddresses.contains(addr)) { + usedAddresses.add(addr); + } + }); + + if (addrs.unusedAddrs.length > 0 && addrs.unusedAddrs[0] != currentAddr) { + currentAddr = addrs.unusedAddrs[0]; + selectedAddr = currentAddr; + } + + await saveAddressesInBox(); + } + + List getAddressInfos() { + if (addressInfos.containsKey(0)) { + return addressInfos[0]!; + } + return []; + } + + Future updateAddress(String address, String label) async { + if (!addressInfos.containsKey(0)) { + return; + } + addressInfos[0]!.forEach((info) { + if (info.address == address) { + info.label = label; + } + }); + await saveAddressesInBox(); + } + + LibAddresses libAddresses() { + final nUsed = "10"; + var nUnused = "0"; + if (this.isEnabledAutoGenerateSubaddress) { + nUnused = "3"; + } + final res = libdcrwallet.addresses(walletInfo.name, nUsed, nUnused); + final decoded = json.decode(res); + final usedAddrs = List.from(decoded["used"]); + final unusedAddrs = List.from(decoded["unused"]); + // index is the index of the first unused address. + final index = decoded["index"] ?? 0; + return new LibAddresses(usedAddrs, unusedAddrs, index); + } + + Future generateNewAddress(String label) async { + // NOTE: This will ignore the gap limit and may cause problems when restoring from seed if too + // many addresses are taken and not used. + final addr = libdcrwallet.newExternalAddress(walletInfo.name) ?? ''; + if (addr == "") { + return; } + if (!addressesMap.containsKey(addr)) { + addressesMap[addr] = ""; + addressInfos[0] ??= []; + addressInfos[0]?.add(AddressInfo(address: addr, label: label, accountIndex: 0)); + } + selectedAddr = addr; + await saveAddressesInBox(); } } + +class LibAddresses { + final List usedAddrs, unusedAddrs; + final int firstUnusedAddrIndex; + + LibAddresses(this.usedAddrs, this.unusedAddrs, this.firstUnusedAddrIndex); +} diff --git a/cw_decred/lib/wallet_creation_credentials.dart b/cw_decred/lib/wallet_creation_credentials.dart index b0c68bb566..ca04514479 100644 --- a/cw_decred/lib/wallet_creation_credentials.dart +++ b/cw_decred/lib/wallet_creation_credentials.dart @@ -31,9 +31,7 @@ class DecredRestoreWalletFromPubkeyCredentials extends WalletCredentials { class DecredRestoreWalletFromHardwareCredentials extends WalletCredentials { DecredRestoreWalletFromHardwareCredentials( - {required String name, - required this.hwAccountData, - WalletInfo? walletInfo}) + {required String name, required this.hwAccountData, WalletInfo? walletInfo}) : t = throw UnimplementedError(), super(name: name, walletInfo: walletInfo); diff --git a/cw_decred/lib/wallet_service.dart b/cw_decred/lib/wallet_service.dart index 21303f754f..ee4fa684e2 100644 --- a/cw_decred/lib/wallet_service.dart +++ b/cw_decred/lib/wallet_service.dart @@ -42,8 +42,7 @@ class DecredWalletService extends WalletService< File(await pathForWallet(name: name, type: getType())).existsSync(); @override - Future create(DecredNewWalletCredentials credentials, - {bool? isTestnet}) async { + Future create(DecredNewWalletCredentials credentials, {bool? isTestnet}) async { await createWalletAsync( name: credentials.walletInfo!.name, dataDir: credentials.walletInfo!.dirPath, @@ -52,16 +51,16 @@ class DecredWalletService extends WalletService< ); credentials.walletInfo!.derivationPath = isTestnet == true ? seedRestorePathTestnet : seedRestorePath; - final wallet = DecredWallet(credentials.walletInfo!, credentials.password!, - this.unspentCoinsInfoSource); + final wallet = + DecredWallet(credentials.walletInfo!, credentials.password!, this.unspentCoinsInfoSource); 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()))!; + final walletInfo = walletInfoSource.values + .firstWhereOrNull((info) => info.id == WalletBase.idFor(name, getType()))!; final network = walletInfo.derivationPath == seedRestorePathTestnet || walletInfo.derivationPath == pubkeyRestorePathTestnet ? testnet @@ -77,33 +76,28 @@ class DecredWalletService extends WalletService< dataDir: walletInfo.dirPath, net: network, ); - final wallet = - DecredWallet(walletInfo, password, this.unspentCoinsInfoSource); + final wallet = DecredWallet(walletInfo, password, this.unspentCoinsInfoSource); 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()))!; + 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); } @override - Future rename( - String currentName, String password, String newName) async { - final currentWalletInfo = walletInfoSource.values.firstWhereOrNull( - (info) => info.id == WalletBase.idFor(currentName, getType()))!; - final network = - currentWalletInfo.derivationPath == seedRestorePathTestnet || - currentWalletInfo.derivationPath == pubkeyRestorePathTestnet - ? testnet - : mainnet; - final currentWallet = - DecredWallet(currentWalletInfo, password, this.unspentCoinsInfoSource); + Future rename(String currentName, String password, String newName) async { + final currentWalletInfo = walletInfoSource.values + .firstWhereOrNull((info) => info.id == WalletBase.idFor(currentName, getType()))!; + final network = currentWalletInfo.derivationPath == seedRestorePathTestnet || + currentWalletInfo.derivationPath == pubkeyRestorePathTestnet + ? testnet + : mainnet; + final currentWallet = DecredWallet(currentWalletInfo, password, this.unspentCoinsInfoSource); await currentWallet.renameWalletFiles(newName); @@ -118,8 +112,7 @@ class DecredWalletService extends WalletService< } @override - Future restoreFromSeed( - DecredRestoreWalletFromSeedCredentials credentials, + Future restoreFromSeed(DecredRestoreWalletFromSeedCredentials credentials, {bool? isTestnet}) async { await createWalletAsync( name: credentials.walletInfo!.name, @@ -129,8 +122,8 @@ class DecredWalletService extends WalletService< network: isTestnet == true ? testnet : mainnet); credentials.walletInfo!.derivationPath = isTestnet == true ? seedRestorePathTestnet : seedRestorePath; - final wallet = DecredWallet(credentials.walletInfo!, credentials.password!, - this.unspentCoinsInfoSource); + final wallet = + DecredWallet(credentials.walletInfo!, credentials.password!, this.unspentCoinsInfoSource); await wallet.init(); return wallet; } @@ -138,8 +131,7 @@ class DecredWalletService extends WalletService< // restoreFromKeys only supports restoring a watch only wallet from an account // pubkey. @override - Future restoreFromKeys( - DecredRestoreWalletFromPubkeyCredentials credentials, + Future restoreFromKeys(DecredRestoreWalletFromPubkeyCredentials credentials, {bool? isTestnet}) async { createWatchOnlyWallet( credentials.walletInfo!.name, @@ -149,8 +141,8 @@ class DecredWalletService extends WalletService< ); credentials.walletInfo!.derivationPath = isTestnet == true ? pubkeyRestorePathTestnet : pubkeyRestorePath; - final wallet = DecredWallet(credentials.walletInfo!, credentials.password!, - this.unspentCoinsInfoSource); + final wallet = + DecredWallet(credentials.walletInfo!, credentials.password!, this.unspentCoinsInfoSource); await wallet.init(); return wallet; } diff --git a/cw_decred/pubspec.lock b/cw_decred/pubspec.lock index 47301cea13..8d932bebab 100644 --- a/cw_decred/pubspec.lock +++ b/cw_decred/pubspec.lock @@ -5,18 +5,23 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" + sha256: f256b0c0ba6c7577c15e2e4e114755640a875e885099367bf6e012b19314c834 url: "https://pub.dev" source: hosted - version: "67.0.0" + version: "72.0.0" + _macros: + dependency: transitive + description: dart + source: sdk + version: "0.3.2" analyzer: dependency: transitive description: name: analyzer - sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" + sha256: b652861553cd3990d8ed361f7979dc6d7053a9ac8843fa73820ab68ce5410139 url: "https://pub.dev" source: hosted - version: "6.4.1" + version: "6.7.0" args: dependency: transitive description: @@ -69,10 +74,10 @@ packages: dependency: transitive description: name: build_daemon - sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1" + sha256: "79b2aef6ac2ed00046867ed354c88778c9c0f029df8a20fe10b5436826721ef9" url: "https://pub.dev" source: hosted - version: "4.0.1" + version: "4.0.2" build_resolvers: dependency: "direct dev" description: @@ -85,18 +90,18 @@ packages: dependency: "direct dev" description: name: build_runner - sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22" + sha256: "028819cfb90051c6b5440c7e574d1896f8037e3c96cf17aaeb054c9311cfbf4d" url: "https://pub.dev" source: hosted - version: "2.4.9" + version: "2.4.13" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" + sha256: f8126682b87a7282a339b871298cc12009cb67109cfa1614d6436fb0289193e0 url: "https://pub.dev" source: hosted - version: "7.3.0" + version: "7.3.2" built_collection: dependency: transitive description: @@ -142,10 +147,10 @@ packages: dependency: transitive description: name: cli_util - sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19 + sha256: ff6785f7e9e3c38ac98b2fb035701789de90154024a75b6cb926445e83197d1c url: "https://pub.dev" source: hosted - version: "0.4.1" + version: "0.4.2" clock: dependency: transitive description: @@ -158,10 +163,10 @@ packages: dependency: transitive description: name: code_builder - sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37 + sha256: "0ec10bf4a89e4c613960bf1e8b42c64127021740fb21640c29c909826a5eea3e" url: "https://pub.dev" source: hosted - version: "4.10.0" + version: "4.10.1" collection: dependency: transitive description: @@ -213,10 +218,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" + sha256: "7856d364b589d1f08986e140938578ed36ed948581fbc3bc9aef1805039ac5ab" url: "https://pub.dev" source: hosted - version: "2.3.6" + version: "2.3.7" decimal: dependency: transitive description: @@ -253,10 +258,10 @@ packages: dependency: "direct dev" description: name: ffigen - sha256: "585a852fefda1b796322b45b433e602daae58f92dd8d918b2497db02f1cabf7c" + sha256: e0bdaa4ff30106aab68e7fa19311df4ced2035dc07be30f2e112855e8dcd3259 url: "https://pub.dev" source: hosted - version: "14.0.1" + version: "16.0.0" file: dependency: transitive description: @@ -419,6 +424,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + macros: + dependency: transitive + description: + name: macros + sha256: "0acaed5d6b7eab89f63350bccd82119e6c602df0f391260d0e32b5e23db79536" + url: "https://pub.dev" + source: hosted + version: "0.1.2-main.4" matcher: dependency: transitive description: @@ -447,10 +460,10 @@ packages: dependency: transitive description: name: mime - sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a" + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "2.0.0" mobx: dependency: transitive description: @@ -495,26 +508,26 @@ packages: dependency: transitive description: name: path_provider - sha256: fec0d61223fba3154d87759e3cc27fe2c8dc498f6386c6d6fc80d1afdd1bf378 + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d + sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.2.15" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" path_provider_linux: dependency: transitive description: @@ -623,10 +636,10 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" + sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "2.0.1" sky_engine: dependency: transitive description: flutter @@ -756,10 +769,10 @@ packages: dependency: transitive description: name: vm_service - sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.4" + version: "14.2.5" watcher: dependency: transitive description: @@ -772,18 +785,26 @@ packages: dependency: transitive description: name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "1.1.0" + web_socket: + dependency: transitive + description: + name: web_socket + sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + url: "https://pub.dev" + source: hosted + version: "0.1.6" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42" + sha256: "9f187088ed104edd8662ca07af4b124465893caf063ba29758f97af57e61da8f" url: "https://pub.dev" source: hosted - version: "2.4.5" + version: "3.0.1" xdg_directories: dependency: transitive description: @@ -810,4 +831,4 @@ packages: version: "2.2.1" sdks: dart: ">=3.5.0 <4.0.0" - flutter: ">=3.19.0" + flutter: ">=3.24.0" diff --git a/cw_decred/pubspec.yaml b/cw_decred/pubspec.yaml index 9b93344577..8addf8fbc9 100644 --- a/cw_decred/pubspec.yaml +++ b/cw_decred/pubspec.yaml @@ -6,9 +6,10 @@ author: Cake Wallet homepage: https://cakewallet.com environment: - sdk: ">=3.2.0-0 <4.0.0" + sdk: '>=3.2.0-0 <4.0.0' flutter: ">=3.19.0" + dependencies: flutter: sdk: flutter @@ -22,7 +23,7 @@ dev_dependencies: build_resolvers: ^2.0.9 mobx_codegen: ^2.0.7 hive_generator: ^2.0.1 - ffigen: ^14.0.1 + ffigen: ^16.0.0 ffigen: name: libdcrwallet diff --git a/lib/decred/cw_decred.dart b/lib/decred/cw_decred.dart index ef4ff0f15a..74069cf857 100644 --- a/lib/decred/cw_decred.dart +++ b/lib/decred/cw_decred.dart @@ -13,55 +13,35 @@ class CWDecred extends Decred { @override WalletCredentials createDecredRestoreWalletFromSeedCredentials( - {required String name, - required String mnemonic, - required String password}) => - DecredRestoreWalletFromSeedCredentials( - name: name, mnemonic: mnemonic, password: password); + {required String name, required String mnemonic, required String password}) => + DecredRestoreWalletFromSeedCredentials(name: name, mnemonic: mnemonic, password: password); @override WalletCredentials createDecredRestoreWalletFromPubkeyCredentials( - {required String name, - required String pubkey, - required String password}) => - DecredRestoreWalletFromPubkeyCredentials( - name: name, pubkey: pubkey, password: password); + {required String name, required String pubkey, required String password}) => + DecredRestoreWalletFromPubkeyCredentials(name: name, pubkey: pubkey, password: password); @override - WalletService createDecredWalletService(Box walletInfoSource, - Box unspentCoinSource) { + WalletService createDecredWalletService( + Box walletInfoSource, Box unspentCoinSource) { return DecredWalletService(walletInfoSource, unspentCoinSource); } @override - List getTransactionPriorities() => - DecredTransactionPriority.all; + List getTransactionPriorities() => DecredTransactionPriority.all; @override - TransactionPriority getMediumTransactionPriority() => - DecredTransactionPriority.medium; + TransactionPriority getDecredTransactionPriorityMedium() => DecredTransactionPriority.medium; @override - TransactionPriority getDecredTransactionPriorityMedium() => - DecredTransactionPriority.medium; - - @override - TransactionPriority getDecredTransactionPrioritySlow() => - DecredTransactionPriority.slow; + TransactionPriority getDecredTransactionPrioritySlow() => DecredTransactionPriority.slow; @override TransactionPriority deserializeDecredTransactionPriority(int raw) => DecredTransactionPriority.deserialize(raw: raw); @override - int getFeeRate(Object wallet, TransactionPriority priority) { - final decredWallet = wallet as DecredWallet; - return decredWallet.feeRate(priority); - } - - @override - Object createDecredTransactionCredentials( - List outputs, TransactionPriority priority) => + Object createDecredTransactionCredentials(List outputs, TransactionPriority priority) => DecredTransactionCredentials( outputs .map((out) => OutputInfo( @@ -76,22 +56,21 @@ class CWDecred extends Decred { .toList(), priority: priority as DecredTransactionPriority); - @override - List getAddresses(Object wallet) { + List getAddressInfos(Object wallet) { final decredWallet = wallet as DecredWallet; - return decredWallet.walletAddresses.addresses(); + return decredWallet.walletAddresses.getAddressInfos(); } @override - String getAddress(Object wallet) { + Future updateAddress(Object wallet, String address, String label) async { final decredWallet = wallet as DecredWallet; - return decredWallet.walletAddresses.address; + await decredWallet.walletAddresses.updateAddress(address, label); } @override - Future generateNewAddress(Object wallet) async { + Future generateNewAddress(Object wallet, String label) async { final decredWallet = wallet as DecredWallet; - await decredWallet.walletAddresses.generateNewAddress(); + await decredWallet.walletAddresses.generateNewAddress(label); } @override @@ -103,8 +82,7 @@ class CWDecred extends Decred { decredAmountToDouble(amount: amount); @override - int formatterStringDoubleToDecredAmount(String amount) => - stringDoubleToDecredAmount(amount); + int formatterStringDoubleToDecredAmount(String amount) => stringDoubleToDecredAmount(amount); @override List getUnspents(Object wallet) { @@ -120,13 +98,12 @@ class CWDecred extends Decred { @override int heightByDate(DateTime date) { - final genesisBlocktime = - DateTime.fromMillisecondsSinceEpoch(1454954400 * 1000); + final genesisBlocktime = DateTime.fromMillisecondsSinceEpoch(1454954400 * 1000); final minutesDiff = date.difference(genesisBlocktime).inMinutes; // Decred has five minute blocks on mainnet. // NOTE: This is off by about a day but is currently unused by decred as we // rescan from the wallet birthday. - return (minutesDiff / 5).toInt(); + return minutesDiff ~/ 5; } @override diff --git a/lib/reactions/on_current_wallet_change.dart b/lib/reactions/on_current_wallet_change.dart index 3840b042e2..9cac74b981 100644 --- a/lib/reactions/on_current_wallet_change.dart +++ b/lib/reactions/on_current_wallet_change.dart @@ -70,7 +70,8 @@ void startCurrentWalletChangeReaction( wallet.type == WalletType.wownero || wallet.type == WalletType.bitcoin || wallet.type == WalletType.litecoin || - wallet.type == WalletType.bitcoinCash) { + wallet.type == WalletType.bitcoinCash || + wallet.type == WalletType.decred) { _setAutoGenerateSubaddressStatus(wallet, settingsStore); } diff --git a/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart b/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart index a942be00dc..4b682c001f 100644 --- a/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart +++ b/lib/src/screens/new_wallet/advanced_privacy_settings_page.dart @@ -274,7 +274,8 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo ], ); }), - if (widget.privacySettingsViewModel.type == WalletType.bitcoin) + if (widget.privacySettingsViewModel.type == WalletType.bitcoin || + widget.privacySettingsViewModel.type == WalletType.decred) Builder(builder: (_) { final val = testnetValue ?? false; return SettingsSwitcherCell( @@ -301,7 +302,9 @@ class _AdvancedPrivacySettingsBodyState extends State<_AdvancedPrivacySettingsBo widget.nodeViewModel.save(); } - if (testnetValue == true) { + if (testnetValue == true && + widget.privacySettingsViewModel.type == + WalletType.bitcoin) { // TODO: add type (mainnet/testnet) to Node class so when switching wallets the node can be switched to a matching type // Currently this is so you can create a working testnet wallet but you need to keep switching back the node if you use multiple wallets at once widget.nodeViewModel.address = publicBitcoinTestnetElectrumAddress; diff --git a/lib/view_model/dashboard/receive_option_view_model.dart b/lib/view_model/dashboard/receive_option_view_model.dart index 744e4c58d9..f15d7dad6f 100644 --- a/lib/view_model/dashboard/receive_option_view_model.dart +++ b/lib/view_model/dashboard/receive_option_view_model.dart @@ -14,7 +14,9 @@ abstract class ReceiveOptionViewModelBase with Store { (_wallet.type == WalletType.bitcoin || _wallet.type == WalletType.litecoin ? bitcoin!.getSelectedAddressType(_wallet) - : ReceivePageOption.mainnet), + : (_wallet.type == WalletType.decred && _wallet.isTestnet) + ? ReceivePageOption.testnet + : ReceivePageOption.mainnet), _options = [] { final walletType = _wallet.type; switch (walletType) { @@ -33,6 +35,17 @@ abstract class ReceiveOptionViewModelBase with Store { case WalletType.haven: _options = [ReceivePageOption.mainnet]; break; + case WalletType.decred: + if (_wallet.isTestnet) { + _options = [ + ReceivePageOption.testnet, + ...ReceivePageOptions.where( + (element) => element != ReceivePageOption.mainnet) + ]; + } else { + _options = ReceivePageOptions; + } + break; default: _options = ReceivePageOptions; } diff --git a/lib/view_model/exchange/exchange_view_model.dart b/lib/view_model/exchange/exchange_view_model.dart index 947ecf8671..6c3d2af3ca 100644 --- a/lib/view_model/exchange/exchange_view_model.dart +++ b/lib/view_model/exchange/exchange_view_model.dart @@ -22,6 +22,7 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin_cash/bitcoin_cash.dart'; +import 'package:cake_wallet/decred/decred.dart'; import 'package:cake_wallet/core/wallet_change_listener_view_model.dart'; import 'package:cake_wallet/entities/exchange_api_mode.dart'; import 'package:cake_wallet/entities/preferences_key.dart'; @@ -323,6 +324,8 @@ abstract class ExchangeViewModelBase extends WalletChangeListenerViewModel with return transactionPriority == bitcoinCash!.getBitcoinCashTransactionPrioritySlow(); case WalletType.polygon: return transactionPriority == polygon!.getPolygonTransactionPrioritySlow(); + case WalletType.decred: + return transactionPriority == decred!.getDecredTransactionPrioritySlow(); default: return false; } 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 8e30c8928a..58984e881e 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 @@ -69,8 +69,6 @@ abstract class NodeCreateOrEditViewModelBase with Store { bool get hasAuthCredentials => _walletType == WalletType.monero || _walletType == WalletType.wownero || _walletType == WalletType.haven; - bool get hasTestnetSupport => _walletType == WalletType.bitcoin; - bool get hasPathSupport { switch (_walletType) { case WalletType.ethereum: diff --git a/lib/view_model/send/output.dart b/lib/view_model/send/output.dart index 121ffa693f..2f102890dd 100644 --- a/lib/view_model/send/output.dart +++ b/lib/view_model/send/output.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/decred/decred.dart'; import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart'; import 'package:cake_wallet/entities/parse_address_from_domain.dart'; @@ -101,6 +102,9 @@ abstract class OutputBase with Store { case WalletType.bitcoinCash: _amount = bitcoin!.formatterStringDoubleToBitcoinAmount(_cryptoAmount); break; + case WalletType.decred: + _amount = decred!.formatterStringDoubleToDecredAmount(_cryptoAmount); + break; case WalletType.haven: _amount = haven!.formatterMoneroParseAmount(amount: _cryptoAmount); break; diff --git a/lib/view_model/settings/privacy_settings_view_model.dart b/lib/view_model/settings/privacy_settings_view_model.dart index e2c9775905..62b656dcad 100644 --- a/lib/view_model/settings/privacy_settings_view_model.dart +++ b/lib/view_model/settings/privacy_settings_view_model.dart @@ -44,7 +44,8 @@ abstract class PrivacySettingsViewModelBase with Store { _wallet.type == WalletType.wownero || _wallet.type == WalletType.bitcoin || _wallet.type == WalletType.litecoin || - _wallet.type == WalletType.bitcoinCash; + _wallet.type == WalletType.bitcoinCash || + _wallet.type == WalletType.decred; bool get isMoneroWallet => _wallet.type == WalletType.monero; diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index 8b80105334..1419c6675e 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -191,7 +191,7 @@ abstract class TransactionDetailsViewModelBase with Store { case WalletType.zano: return 'https://explorer.zano.org/transaction/${txId}'; case WalletType.decred: - return 'https://dcrdata.decred.org/tx/${txId.split(':')[0]}'; + return 'https://${wallet.isTestnet ? "testnet" : "dcrdata"}.decred.org/tx/${txId.split(':')[0]}'; case WalletType.none: return ''; } 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 d365c8e006..36f8858781 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 @@ -5,6 +5,7 @@ import 'package:cw_core/wallet_base.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/haven/haven.dart'; +import 'package:cake_wallet/decred/decred.dart'; import 'package:cw_core/wallet_type.dart'; part 'wallet_address_edit_or_create_view_model.g.dart'; @@ -71,6 +72,10 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store { if (isElectrum) await bitcoin!.generateNewAddress(wallet, label); + if (wallet.type == WalletType.decred) { + await decred!.generateNewAddress(wallet, label); + } + if (wallet.type == WalletType.monero) { await monero !.getSubaddressList(wallet) @@ -111,6 +116,12 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store { if (isElectrum) await bitcoin!.updateAddress(wallet, _item!.address, label); + if (wallet.type == WalletType.decred) { + await decred!.updateAddress(wallet, _item!.address, label); + await wallet.save(); + return; + } + final index = _item?.id; if (index != null) { if (wallet.type == WalletType.monero) { 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 40e177504a..ebb2394dbb 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 @@ -12,6 +12,7 @@ import 'package:cake_wallet/haven/haven.dart'; import 'package:cake_wallet/monero/monero.dart'; import 'package:cake_wallet/polygon/polygon.dart'; import 'package:cake_wallet/solana/solana.dart'; +import 'package:cake_wallet/decred/decred.dart'; import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; import 'package:cake_wallet/store/settings_store.dart'; @@ -502,6 +503,14 @@ abstract class WalletAddressListViewModelBase isPrimary: true, name: null, address: primaryAddress)); } + if (wallet.type == WalletType.decred) { + final addrInfos = decred!.getAddressInfos(wallet); + addrInfos.forEach((info) { + addressList.add(new WalletAddressListItem(isPrimary: false, address: info.address, + name: info.label)); + }); + } + for (var i = 0; i < addressList.length; i++) { if (!(addressList[i] is WalletAddressListItem)) continue; (addressList[i] as WalletAddressListItem).isHidden = wallet @@ -582,7 +591,8 @@ abstract class WalletAddressListViewModelBase WalletType.haven, WalletType.bitcoinCash, WalletType.bitcoin, - WalletType.litecoin + WalletType.litecoin, + WalletType.decred ].contains(wallet.type); @computed diff --git a/scripts/android/build_decred.sh b/scripts/android/build_decred.sh index d73841e172..46507e468e 100755 --- a/scripts/android/build_decred.sh +++ b/scripts/android/build_decred.sh @@ -7,7 +7,7 @@ cd "$(dirname "$0")" CW_DECRED_DIR=$(realpath ../..)/cw_decred LIBWALLET_PATH="${PWD}/decred/libwallet" LIBWALLET_URL="https://github.com/decred/libwallet.git" -LIBWALLET_VERSION="c890cb24bee480e7f6eadac3f22f10b9697b7e8a" +LIBWALLET_VERSION="82ed8a80ae9fa3b15a2d5609bc748fc663be7e37" # v2.2.1 if [ -e $LIBWALLET_PATH ]; then rm -fr $LIBWALLET_PATH/{*,.*} || true @@ -38,6 +38,7 @@ fi export NDK_BIN_PATH="${ANDROID_HOME}/ndk/${ANDROID_NDK_VERSION}/toolchains/llvm/prebuilt/$(uname | tr '[:upper:]' '[:lower:]')-x86_64/bin" export ANDROID_API_VERSION=21 +# export CPATH="$(clang -v 2>&1 | grep "Selected GCC installation" | rev | cut -d' ' -f1 | rev)/include" for arch in "aarch" "aarch64" "x86_64" do @@ -59,7 +60,7 @@ do TARGET="amd64" ARCH_ABI="x86_64";; *) - echo "Unkonwn arch: $arch" + echo "Unknown arch: $arch" exit 1;; esac @@ -80,4 +81,4 @@ done HEADER_DIR=$CW_DECRED_DIR/lib/api cp ${LIBWALLET_PATH}/build/${TRIPLET}-libdcrwallet.h $HEADER_DIR/libdcrwallet.h cd $CW_DECRED_DIR -dart run ffigen \ No newline at end of file +dart run ffigen diff --git a/scripts/ios/build_decred.sh b/scripts/ios/build_decred.sh index 482cc9499b..0387dca0c8 100755 --- a/scripts/ios/build_decred.sh +++ b/scripts/ios/build_decred.sh @@ -3,7 +3,7 @@ set -e . ./config.sh LIBWALLET_PATH="${EXTERNAL_IOS_SOURCE_DIR}/libwallet" LIBWALLET_URL="https://github.com/decred/libwallet.git" -LIBWALLET_VERSION="c890cb24bee480e7f6eadac3f22f10b9697b7e8a" +LIBWALLET_VERSION="82ed8a80ae9fa3b15a2d5609bc748fc663be7e37" # v2.2.1 if [ -e $LIBWALLET_PATH ]; then rm -fr $LIBWALLET_PATH diff --git a/scripts/macos/build_decred.sh b/scripts/macos/build_decred.sh index 716855d236..6f2b4eceff 100755 --- a/scripts/macos/build_decred.sh +++ b/scripts/macos/build_decred.sh @@ -4,7 +4,7 @@ LIBWALLET_PATH="${EXTERNAL_MACOS_SOURCE_DIR}/libwallet" LIBWALLET_URL="https://github.com/decred/libwallet.git" -LIBWALLET_VERSION="v2.0.0" +LIBWALLET_VERSION="82ed8a80ae9fa3b15a2d5609bc748fc663be7e37" # v2.2.1 echo "======================= DECRED LIBWALLET =========================" diff --git a/tool/configure.dart b/tool/configure.dart index d0c25ada26..3155c41975 100644 --- a/tool/configure.dart +++ b/tool/configure.dart @@ -1488,6 +1488,7 @@ Future generateDecred(bool hasImplementation) async { final outputFile = File(decredOutputPath); const decredCommonHeaders = """ import 'package:cw_core/wallet_credentials.dart'; +import 'package:cw_core/address_info.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/transaction_priority.dart'; import 'package:cw_core/output_info.dart'; @@ -1513,29 +1514,22 @@ abstract class Decred { WalletCredentials createDecredNewWalletCredentials( {required String name, WalletInfo? walletInfo}); WalletCredentials createDecredRestoreWalletFromSeedCredentials( - {required String name, - required String mnemonic, - required String password}); + {required String name, required String mnemonic, required String password}); WalletCredentials createDecredRestoreWalletFromPubkeyCredentials( - {required String name, - required String pubkey, - required String password}); - WalletService createDecredWalletService(Box walletInfoSource, - Box unspentCoinSource); + {required String name, required String pubkey, required String password}); + WalletService createDecredWalletService( + Box walletInfoSource, Box unspentCoinSource); List getTransactionPriorities(); - TransactionPriority getMediumTransactionPriority(); TransactionPriority getDecredTransactionPriorityMedium(); TransactionPriority getDecredTransactionPrioritySlow(); TransactionPriority deserializeDecredTransactionPriority(int raw); - int getFeeRate(Object wallet, TransactionPriority priority); - Object createDecredTransactionCredentials( - List outputs, TransactionPriority priority); + Object createDecredTransactionCredentials(List outputs, TransactionPriority priority); - List getAddresses(Object wallet); - String getAddress(Object wallet); - Future generateNewAddress(Object wallet); + List getAddressInfos(Object wallet); + Future updateAddress(Object wallet, String address, String label); + Future generateNewAddress(Object wallet, String label); String formatterDecredAmountToString({required int amount}); double formatterDecredAmountToDouble({required int amount});