From eac22832d67bdb5317b599ed99a51d2a224ff29a Mon Sep 17 00:00:00 2001 From: HashEngineering Date: Tue, 23 Jan 2024 06:13:30 -0800 Subject: [PATCH] feat(dashpay): add coinjoin error handling to enter amount screen (#1247) * feat: add coinjoin error handling on enter amount screen * refactor: create InsufficientCoinJoinMoneyException for dry run exception handling --- common/src/main/res/values/strings.xml | 1 + .../crowdnode/CrowdNodeTxFilterTest.kt | 4 ++-- .../schildbach/wallet/ui/send/SendCoinsFragment.kt | 14 +++++++++----- .../wallet/ui/send/SendCoinsViewModel.kt | 13 +++++++++---- 4 files changed, 21 insertions(+), 11 deletions(-) diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index c3b972d2dc..f7d13355d1 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -83,6 +83,7 @@ %1$s DASH = %2$s The amount is too small to send Insufficient funds + Insufficient mixed funds. Wait for CoinJoin mixing to finish or disable this feature in the settings to complete this transaction. Syncing No internet connection Not available diff --git a/integrations/crowdnode/src/test/java/org/dash/wallet/integrations/crowdnode/CrowdNodeTxFilterTest.kt b/integrations/crowdnode/src/test/java/org/dash/wallet/integrations/crowdnode/CrowdNodeTxFilterTest.kt index ef4c221f15..680bc5beb3 100644 --- a/integrations/crowdnode/src/test/java/org/dash/wallet/integrations/crowdnode/CrowdNodeTxFilterTest.kt +++ b/integrations/crowdnode/src/test/java/org/dash/wallet/integrations/crowdnode/CrowdNodeTxFilterTest.kt @@ -228,10 +228,10 @@ class CrowdNodeTxFilterTest { on { isPubKeyHashMine(any(), any()) } doReturn true } - val connectedData = "0100000001a144eb2405271f2a56332ba40e61c4248a42c45dbc7ca453c5ac6bd1ca13f4ed170000006b483045022100a84e01ee3b13b6056d3a2e3ae2e000aa08897f6b8769c149ae940ad0d348305702207f494646bf8789610d2a44642dbf3136519c93c0a97df885ae8fba81848361850121022bc500272dc2263fda23e4ccd99d39942ad92127e8d43581876038c9b9014517ffffffff03409c0000000000001976a9147b5bea31861a1cfca6cb5cb93277fb7515bda7df88acabc55101000000001976a91486c1305dc7b556051ef2f3c1a7f8671f1abc349988ac8016a437000000001976a914ae0621debab253e792f2558dd1889b5c29110dff88ac00000000" + val connectedData = "0100000001a144eb2405271f2a56332ba40e61c4248a42c45dbc7ca453c5ac6bd1ca13f4ed170000006b483045022100a84e01ee3b13b6056d3a2e3ae2e000aa08897f6b8769c149ae940ad0d348305702207f494646bf8789610d2a44642dbf3136519c93c0a97df885ae8fba81848361850121022bc500272dc2263fda23e4ccd99d39942ad92127e8d43581876038c9b9014517ffffffff03409c0000000000001976a9147b5bea31861a1cfca6cb5cb93277fb7515bda7df88acabc55101000000001976a91486c1305dc7b556051ef2f3c1a7f8671f1abc349988ac8016a437000000001976a914ae0621debab253e792f2558dd1889b5c29110dff88ac00000000" // ktlint-disable max-line-length val connectedTx = Transaction(networkParams, Utils.HEX.decode(connectedData)) - val txData = "01000000017f8b38abf42bce8bfbfc9c5965096a9cfed3ef0a1fc7fe8a79881a90a393a790020000006a47304402203545c3a1ee67fef9f4733caa38619affd92e012271b01078a7eb80b179c56355022040ad74e6521c659104aec47621f7c8cf8defd1eb1c3f5484ada3b124cad4454a01210221a2fb697857eeb83d47a57050f27a38c84958ce7b79741d7f686c2867a4a895ffffffff03224e0000000000001976a914197b4429f61bdb64a859567b441a23e6630d533588ac224e0000000000001976a914f850a88d4f515ea7b92908ad4d08e7383922ca0988ac3779a337000000001976a914b658c3dac62e4c570b08cf317fb70436ea37870088ac00000000" + val txData = "01000000017f8b38abf42bce8bfbfc9c5965096a9cfed3ef0a1fc7fe8a79881a90a393a790020000006a47304402203545c3a1ee67fef9f4733caa38619affd92e012271b01078a7eb80b179c56355022040ad74e6521c659104aec47621f7c8cf8defd1eb1c3f5484ada3b124cad4454a01210221a2fb697857eeb83d47a57050f27a38c84958ce7b79741d7f686c2867a4a895ffffffff03224e0000000000001976a914197b4429f61bdb64a859567b441a23e6630d533588ac224e0000000000001976a914f850a88d4f515ea7b92908ad4d08e7383922ca0988ac3779a337000000001976a914b658c3dac62e4c570b08cf317fb70436ea37870088ac00000000" // ktlint-disable max-line-length val internalTx = Transaction(networkParams, Utils.HEX.decode(txData)) internalTx.inputs[0].connect(connectedTx.outputs[0]) diff --git a/wallet/src/de/schildbach/wallet/ui/send/SendCoinsFragment.kt b/wallet/src/de/schildbach/wallet/ui/send/SendCoinsFragment.kt index e8508ad2f3..f7f1de2f9e 100644 --- a/wallet/src/de/schildbach/wallet/ui/send/SendCoinsFragment.kt +++ b/wallet/src/de/schildbach/wallet/ui/send/SendCoinsFragment.kt @@ -24,6 +24,7 @@ import android.net.Uri import android.os.Bundle import android.view.View import android.widget.Toast +import androidx.annotation.StringRes import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.lifecycle.lifecycleScope @@ -174,11 +175,8 @@ class SendCoinsFragment: Fragment(R.layout.send_coins_fragment) { } else if (dryRunException != null) { errorMessage = when (dryRunException) { is Wallet.DustySendRequested -> getString(R.string.send_coins_error_dusty_send) - is InsufficientMoneyException -> if (!requirePinForBalance || userAuthorizedDuring) { - getString(R.string.send_coins_error_insufficient_money) - } else { - "" - } + is InsufficientCoinJoinMoneyException -> getErrorMessage(R.string.send_coins_error_insufficient_mixed_money) + is InsufficientMoneyException -> getErrorMessage(R.string.send_coins_error_insufficient_money) is Wallet.CouldNotAdjustDownwards -> getString(R.string.send_coins_error_dusty_send) else -> dryRunException.toString() } @@ -201,6 +199,12 @@ class SendCoinsFragment: Fragment(R.layout.send_coins_fragment) { ) } + private fun getErrorMessage(@StringRes errorMessage: Int) = if (!requirePinForBalance || userAuthorizedDuring) { + getString(errorMessage) + } else { + "" + } + private suspend fun authenticateOrConfirm() { if (!viewModel.everythingPlausible()) { return diff --git a/wallet/src/de/schildbach/wallet/ui/send/SendCoinsViewModel.kt b/wallet/src/de/schildbach/wallet/ui/send/SendCoinsViewModel.kt index 205ddbd1ae..eaded63b88 100644 --- a/wallet/src/de/schildbach/wallet/ui/send/SendCoinsViewModel.kt +++ b/wallet/src/de/schildbach/wallet/ui/send/SendCoinsViewModel.kt @@ -39,9 +39,7 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapLatest import kotlinx.coroutines.flow.onEach -import org.bitcoinj.coinjoin.CoinJoinCoinSelector import org.bitcoinj.core.Address import org.bitcoinj.core.Coin import org.bitcoinj.core.InsufficientMoneyException @@ -59,6 +57,7 @@ import javax.inject.Inject import kotlin.math.min class SendException(message: String) : Exception(message) +class InsufficientCoinJoinMoneyException(ex: InsufficientMoneyException) : InsufficientMoneyException(ex.missing, "${ex.message} [coinjoin]") @HiltViewModel class SendCoinsViewModel @Inject constructor( @@ -124,6 +123,8 @@ class SendCoinsViewModel @Inject constructor( val contactData: LiveData get() = _contactData + private var coinJoinMode: CoinJoinMode = CoinJoinMode.NONE + init { blockchainStateDao.observeState() .filterNotNull() @@ -134,11 +135,11 @@ class SendCoinsViewModel @Inject constructor( coinJoinConfig.observeMode() .map { mode -> + coinJoinMode = mode if (mode == CoinJoinMode.NONE) { MaxOutputAmountCoinSelector() } else { MaxOutputAmountCoinJoinCoinSelector(wallet) - // MaxOutputAmountCoinSelector() } } .flatMapLatest { coinSelector -> @@ -298,7 +299,11 @@ class SendCoinsViewModel @Inject constructor( dryrunSendRequest = sendRequest _dryRunSuccessful.value = true } catch (ex: Exception) { - dryRunException = ex + dryRunException = if (ex is InsufficientMoneyException && coinJoinMode != CoinJoinMode.NONE && !currentAmount.isGreaterThan(wallet.getBalance(MaxOutputAmountCoinSelector()))) { + InsufficientCoinJoinMoneyException(ex) + } else { + ex + } _dryRunSuccessful.value = false } }