From 740799f683ca5b139f17a272696044d00a838794 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Tue, 22 Sep 2026 20:48:49 +0200 Subject: [PATCH 1/3] fix: show the amount that leaves on spending confirm From 97762b999c06bdc6a48bf7c74436e366dc0e2899 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Tue, 22 Sep 2026 21:21:56 +0200 Subject: [PATCH 2/3] fix: show the amount that leaves on spending confirm --- .../screens/transfer/SpendingConfirmScreen.kt | 2 +- .../to/bitkit/viewmodels/TransferViewModel.kt | 18 +++++++++++-- .../viewmodels/TransferViewModelTest.kt | 26 +++++++++++++++++++ changelog.d/next/1326.fixed.md | 1 + 4 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 changelog.d/next/1326.fixed.md 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..b67e7fad32 100644 --- a/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt @@ -278,7 +278,7 @@ class TransferViewModel @Inject constructor( confirmFeeJob?.cancel() confirmFeeJob = viewModelScope.launch { _spendingUiState.update { - it.copy(isConfirmFeeReady = false, miningFeeSats = 0uL) + it.copy(isConfirmFeeReady = false, miningFeeSats = 0uL, spendableBalance = 0uL) } val order = _spendingUiState.value.order val target = SpendingFundingTarget( @@ -294,13 +294,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 +1921,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..59d0b961f4 100644 --- a/app/src/test/java/to/bitkit/viewmodels/TransferViewModelTest.kt +++ b/app/src/test/java/to/bitkit/viewmodels/TransferViewModelTest.kt @@ -1023,10 +1023,36 @@ 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 `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. From 2cf6873e92ac9ca6b08ce7b1ae0cb79e67f5ecd9 Mon Sep 17 00:00:00 2001 From: Ovi Trif Date: Wed, 23 Sep 2026 01:04:00 +0200 Subject: [PATCH 3/3] fix: keep the leaving amount while confirm funding refreshes --- .../to/bitkit/viewmodels/TransferViewModel.kt | 4 +-- .../viewmodels/TransferViewModelTest.kt | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt index b67e7fad32..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, spendableBalance = 0uL) - } + _spendingUiState.update { it.copy(isConfirmFeeReady = false) } val order = _spendingUiState.value.order val target = SpendingFundingTarget( feeSat = _spendingUiState.value.feeSat, diff --git a/app/src/test/java/to/bitkit/viewmodels/TransferViewModelTest.kt b/app/src/test/java/to/bitkit/viewmodels/TransferViewModelTest.kt index 59d0b961f4..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 @@ -1053,6 +1054,32 @@ class TransferViewModelTest : BaseUnitTest() { 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)