diff --git a/cw_monero/lib/api/coins_info.dart b/cw_monero/lib/api/coins_info.dart index c1b634cc6..ef7d3cfd6 100644 --- a/cw_monero/lib/api/coins_info.dart +++ b/cw_monero/lib/api/coins_info.dart @@ -12,6 +12,18 @@ int countOfCoins() => monero.Coins_count(coins!); monero.CoinsInfo getCoin(int index) => monero.Coins_coin(coins!, index); +int? getCoinByKeyImage(String keyImage) { + final count = countOfCoins(); + for (int i = 0; i < count; i++) { + final coin = getCoin(i); + final coinAddress = monero.CoinsInfo_keyImage(coin); + if (keyImage == coinAddress) { + return i; + } + } + return null; +} + void freezeCoin(int index) => monero.Coins_setFrozen(coins!, index: index); void thawCoin(int index) => monero.Coins_thaw(coins!, index: index); diff --git a/cw_monero/lib/api/transaction_history.dart b/cw_monero/lib/api/transaction_history.dart index 662eb1bb5..7c253509c 100644 --- a/cw_monero/lib/api/transaction_history.dart +++ b/cw_monero/lib/api/transaction_history.dart @@ -6,6 +6,7 @@ import 'package:cw_monero/api/exceptions/creation_transaction_exception.dart'; import 'package:cw_monero/api/monero_output.dart'; import 'package:cw_monero/api/structs/pending_transaction.dart'; import 'package:cw_monero/api/wallet.dart'; +import 'package:cw_monero/exceptions/monero_transaction_creation_exception.dart'; import 'package:ffi/ffi.dart'; import 'package:monero/monero.dart' as monero; import 'package:monero/src/generated_bindings_monero.g.dart' as monero_gen; @@ -101,7 +102,11 @@ Future createTransactionSync( }); final address_ = address.toNativeUtf8(); - final paymentId_ = paymentId.toNativeUtf8(); + final paymentId_ = paymentId.toNativeUtf8(); + if (preferredInputs.isEmpty) { + throw MoneroTransactionCreationException("No inputs provided, transaction cannot be constructed"); + } + final preferredInputs_ = preferredInputs.join(monero.defaultSeparatorStr).toNativeUtf8(); final addraddr = address_.address; diff --git a/cw_monero/lib/api/wallet.dart b/cw_monero/lib/api/wallet.dart index 70c610418..667a1ff69 100644 --- a/cw_monero/lib/api/wallet.dart +++ b/cw_monero/lib/api/wallet.dart @@ -4,6 +4,7 @@ import 'dart:isolate'; import 'package:cw_monero/api/account_list.dart'; import 'package:cw_monero/api/exceptions/setup_wallet_exception.dart'; +import 'package:flutter/foundation.dart'; import 'package:monero/monero.dart' as monero; import 'package:mutex/mutex.dart'; @@ -129,6 +130,15 @@ Future setupNodeSync( throw SetupWalletException(message: error); } + if (kDebugMode) { + monero.Wallet_init3( + wptr!, argv0: '', + defaultLogBaseName: 'moneroc', + console: true, + logPath: '', + ); + } + return status == 0; } diff --git a/cw_monero/lib/monero_unspent.dart b/cw_monero/lib/monero_unspent.dart index 87d8f0b39..f45fcddaf 100644 --- a/cw_monero/lib/monero_unspent.dart +++ b/cw_monero/lib/monero_unspent.dart @@ -1,10 +1,32 @@ import 'package:cw_core/unspent_transaction_output.dart'; +import 'package:cw_monero/api/coins_info.dart'; +import 'package:monero/monero.dart' as monero; class MoneroUnspent extends Unspent { MoneroUnspent( String address, String hash, String keyImage, int value, bool isFrozen, this.isUnlocked) : super(address, hash, value, 0, keyImage) { - this.isFrozen = isFrozen; + } + + @override + set isFrozen(bool freeze) { + print("set isFrozen: $freeze ($keyImage): $freeze"); + final coinId = getCoinByKeyImage(keyImage!); + if (coinId == null) throw Exception("Unable to find a coin for address $address"); + if (freeze) { + freezeCoin(coinId); + } else { + thawCoin(coinId); + } + } + + @override + bool get isFrozen { + print("get isFrozen"); + final coinId = getCoinByKeyImage(keyImage!); + if (coinId == null) throw Exception("Unable to find a coin for address $address"); + final coin = getCoin(coinId); + return monero.CoinsInfo_frozen(coin); } final bool isUnlocked; diff --git a/cw_monero/lib/monero_wallet.dart b/cw_monero/lib/monero_wallet.dart index 5f53b30ba..2b302e745 100644 --- a/cw_monero/lib/monero_wallet.dart +++ b/cw_monero/lib/monero_wallet.dart @@ -309,9 +309,8 @@ abstract class MoneroWalletBase extends WalletBase element.walletId == id && element.accountIndex == walletAddresses.account!.id)) { - if (coin.isFrozen) frozenBalance += coin.value; + if (coin.isFrozen && !coin.isSending) frozenBalance += coin.value; } - return frozenBalance; } diff --git a/cw_wownero/lib/api/transaction_history.dart b/cw_wownero/lib/api/transaction_history.dart index 6b0923e83..ce93df7d1 100644 --- a/cw_wownero/lib/api/transaction_history.dart +++ b/cw_wownero/lib/api/transaction_history.dart @@ -6,13 +6,16 @@ import 'package:cw_wownero/api/exceptions/creation_transaction_exception.dart'; import 'package:cw_wownero/api/wallet.dart'; import 'package:cw_wownero/api/wownero_output.dart'; import 'package:cw_wownero/api/structs/pending_transaction.dart'; +import 'package:cw_wownero/exceptions/wownero_transaction_creation_exception.dart'; import 'package:ffi/ffi.dart'; import 'package:monero/wownero.dart' as wownero; import 'package:monero/src/generated_bindings_wownero.g.dart' as wownero_gen; String getTxKey(String txId) { - return wownero.Wallet_getTxKey(wptr!, txid: txId); + final ret = wownero.Wallet_getTxKey(wptr!, txid: txId); + wownero.Wallet_status(wptr!); + return ret; } wownero.TransactionHistory? txhistory; @@ -86,7 +89,10 @@ Future createTransactionSync( final amt = amount == null ? 0 : wownero.Wallet_amountFromString(amount); final address_ = address.toNativeUtf8(); - final paymentId_ = paymentId.toNativeUtf8(); + final paymentId_ = paymentId.toNativeUtf8(); + if (preferredInputs.isEmpty) { + throw WowneroTransactionCreationException("No inputs provided, transaction cannot be constructed"); + } final preferredInputs_ = preferredInputs.join(wownero.defaultSeparatorStr).toNativeUtf8(); final waddr = wptr!.address; diff --git a/lib/di.dart b/lib/di.dart index facdec912..be014f665 100644 --- a/lib/di.dart +++ b/lib/di.dart @@ -746,6 +746,7 @@ Future setup({ _transactionDescriptionBox, getIt.get().wallet!.isHardwareWallet ? getIt.get() : null, coinTypeToSpendFrom: coinTypeToSpendFrom ?? UnspentCoinType.any, + getIt.get(param1: coinTypeToSpendFrom), ), ); diff --git a/lib/view_model/send/send_view_model.dart b/lib/view_model/send/send_view_model.dart index 69011aa74..69a500c9b 100644 --- a/lib/view_model/send/send_view_model.dart +++ b/lib/view_model/send/send_view_model.dart @@ -19,6 +19,7 @@ import 'package:cake_wallet/tron/tron.dart'; import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/balance_view_model.dart'; import 'package:cake_wallet/view_model/hardware_wallet/ledger_view_model.dart'; +import 'package:cake_wallet/view_model/unspent_coins/unspent_coins_list_view_model.dart'; import 'package:cake_wallet/wownero/wownero.dart'; import 'package:cw_core/exceptions.dart'; import 'package:cw_core/transaction_info.dart'; @@ -64,6 +65,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor wallet.type == WalletType.tron; } + UnspentCoinsListViewModel unspentCoinsListViewModel; + SendViewModelBase( AppStore appStore, this.sendTemplateViewModel, @@ -71,7 +74,8 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor this.balanceViewModel, this.contactListViewModel, this.transactionDescriptionBox, - this.ledgerViewModel, { + this.ledgerViewModel, + this.unspentCoinsListViewModel, { this.coinTypeToSpendFrom = UnspentCoinType.any, }) : state = InitialExecutionState(), currencies = appStore.wallet!.balance.keys.toList(), @@ -530,6 +534,16 @@ abstract class SendViewModelBase extends WalletChangeListenerViewModel with Stor throw Exception('Priority is null for wallet type: ${wallet.type}'); } + if (hasCoinControl) { + bool isCoinSelected = false; + for (var coin in unspentCoinsListViewModel.items) { + isCoinSelected = isCoinSelected || (coin.isSending && !coin.isFrozen); + } + if (!isCoinSelected) { + throw Exception("No coin selected in coin control, you need to select a coin in order to spend"); + } + } + switch (wallet.type) { case WalletType.bitcoin: case WalletType.litecoin: