diff --git a/app/src/main/java/to/bitkit/ui/screens/transfer/SpendingConfirmScreen.kt b/app/src/main/java/to/bitkit/ui/screens/transfer/SpendingConfirmScreen.kt index a9dd415119..9946c6e1e1 100644 --- a/app/src/main/java/to/bitkit/ui/screens/transfer/SpendingConfirmScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/transfer/SpendingConfirmScreen.kt @@ -182,7 +182,7 @@ private fun Content( ) { val clientBalance = state.clientBalanceSat val lspFee = state.feeSat.safe() - clientBalance.safe() - val total = state.feeSat.safe() + miningFeeSats.safe() + val total = state.confirmLeavingAmountSats val lspBalance = state.lspBalanceSat VerticalSpacer(32.dp) diff --git a/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt index 06dd7397c6..6e72a865d2 100644 --- a/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt @@ -277,9 +277,7 @@ class TransferViewModel @Inject constructor( fun prepareSpendingConfirmFunding() { confirmFeeJob?.cancel() confirmFeeJob = viewModelScope.launch { - _spendingUiState.update { - it.copy(isConfirmFeeReady = false, miningFeeSats = 0uL) - } + _spendingUiState.update { it.copy(isConfirmFeeReady = false) } val order = _spendingUiState.value.order val target = SpendingFundingTarget( feeSat = _spendingUiState.value.feeSat, @@ -294,13 +292,19 @@ class TransferViewModel @Inject constructor( isConfirmFeeReady = true, miningFeeSats = plan.miningFeeSats, shouldUseSendAll = plan.shouldUseSendAll, + spendableBalance = plan.spendableBalance, ) } } .onFailure { spendingConfirmFundingPlan = null _spendingUiState.update { - it.copy(isConfirmFeeReady = false, miningFeeSats = 0uL, shouldUseSendAll = false) + it.copy( + isConfirmFeeReady = false, + miningFeeSats = 0uL, + shouldUseSendAll = false, + spendableBalance = 0uL, + ) } Logger.error("Failed to prepare transfer funding fee", it, context = TAG) if (it is AppError) { @@ -1915,12 +1919,20 @@ data class TransferToSpendingUiState( val isConfirmFeeReady: Boolean = false, val isConfirmPaying: Boolean = false, val shouldUseSendAll: Boolean = false, + val spendableBalance: ULong = 0uL, val receivingAmount: Long = 0, val feeEstimate: Long? = null, val fundingBudgetSats: ULong? = null, val hwFundingWalletId: String? = null, ) { val isBusy: Boolean get() = isConfirmPaying || isSigning + + val confirmLeavingAmountSats: ULong + get() = if (shouldUseSendAll) { + spendableBalance + } else { + feeSat.safe() + miningFeeSats.safe() + } } private data class SpendingFundingTarget( diff --git a/app/src/test/java/to/bitkit/viewmodels/TransferViewModelTest.kt b/app/src/test/java/to/bitkit/viewmodels/TransferViewModelTest.kt index 606d47768d..4de7a46385 100644 --- a/app/src/test/java/to/bitkit/viewmodels/TransferViewModelTest.kt +++ b/app/src/test/java/to/bitkit/viewmodels/TransferViewModelTest.kt @@ -17,6 +17,7 @@ import com.synonym.bitkitcore.TrezorFeatures import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentSetOf import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.suspendCancellableCoroutine import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.TimeoutCancellationException @@ -1023,10 +1024,62 @@ class TransferViewModelTest : BaseUnitTest() { assertEquals(true, state.isConfirmFeeReady) assertEquals(1_000uL, state.miningFeeSats) assertEquals(false, state.shouldUseSendAll) + assertEquals(100_000uL, state.spendableBalance) + assertEquals(99_000uL, state.confirmLeavingAmountSats) verify(lightningRepo).calculateTotalFee(eq(98_000uL), eq(WALLET_ADDRESS), any(), anyOrNull(), anyOrNull()) verify(blocktankRepo, never()).createOrder(any(), any(), any()) } + @Test + fun `prepareSpendingConfirmFunding shows spendable balance as total for send-all`() = test { + quoteOrder(spendingOrder(feeSat = 99_000uL)) + val selected = listOf(stubUtxo(100_000u)) + stubSpendableBalances(spendable = 100_000u) + whenever(lightningRepo.estimateSendAllFee(any(), any(), anyOrNull())).thenReturn(Result.success(500uL)) + whenever { + lightningRepo.selectUtxosWithAlgorithm(any(), any(), any(), anyOrNull()) + }.thenReturn(Result.success(selected)) + whenever(lightningRepo.calculateTotalFee(any(), any(), any(), anyOrNull(), anyOrNull())) + .thenReturn(Result.success(500uL)) + + sut.prepareSpendingConfirmFunding() + advanceUntilIdle() + + val state = sut.spendingUiState.value + assertEquals(true, state.isConfirmFeeReady) + assertEquals(500uL, state.miningFeeSats) + assertEquals(true, state.shouldUseSendAll) + assertEquals(100_000uL, state.spendableBalance) + assertEquals(100_000uL, state.confirmLeavingAmountSats) + verify(blocktankRepo, never()).createOrder(any(), any(), any()) + } + + @Test + fun `repreparing send-all funding keeps the leaving amount until the next plan`() = test { + quoteOrder(spendingOrder(feeSat = 99_000uL)) + val selected = listOf(stubUtxo(100_000u)) + stubSpendableBalances(spendable = 100_000u) + whenever(lightningRepo.estimateSendAllFee(any(), any(), anyOrNull())).thenReturn(Result.success(500uL)) + whenever { + lightningRepo.selectUtxosWithAlgorithm(any(), any(), any(), anyOrNull()) + }.thenReturn(Result.success(selected)) + whenever(lightningRepo.calculateTotalFee(any(), any(), any(), anyOrNull(), anyOrNull())) + .thenReturn(Result.success(500uL)) + + sut.prepareSpendingConfirmFunding() + advanceUntilIdle() + assertEquals(100_000uL, sut.spendingUiState.value.confirmLeavingAmountSats) + + whenever(lightningRepo.getBalancesAsync()).doSuspendableAnswer { suspendCancellableCoroutine { } } + sut.prepareSpendingConfirmFunding() + runCurrent() + + val refreshing = sut.spendingUiState.value + assertEquals(false, refreshing.isConfirmFeeReady) + assertEquals(true, refreshing.shouldUseSendAll) + assertEquals(100_000uL, refreshing.confirmLeavingAmountSats) + } + @Test fun `onTransferToSpendingConfirm uses send-all when selected inputs would create dust change`() = test { val order = spendingOrder(feeSat = 99_000uL) diff --git a/changelog.d/next/1326.fixed.md b/changelog.d/next/1326.fixed.md new file mode 100644 index 0000000000..a1996f111f --- /dev/null +++ b/changelog.d/next/1326.fixed.md @@ -0,0 +1 @@ +Spending confirm now shows the amount of bitcoin that leaves the wallet when a savings transfer uses send-all.