diff --git a/app/src/androidTest/java/to/bitkit/ui/screens/wallets/send/SendConfirmScreenTest.kt b/app/src/androidTest/java/to/bitkit/ui/screens/wallets/send/SendConfirmScreenTest.kt index 7c301dfdc9..bc1acc5d41 100644 --- a/app/src/androidTest/java/to/bitkit/ui/screens/wallets/send/SendConfirmScreenTest.kt +++ b/app/src/androidTest/java/to/bitkit/ui/screens/wallets/send/SendConfirmScreenTest.kt @@ -30,6 +30,7 @@ import androidx.compose.ui.test.swipeRight import androidx.compose.ui.test.swipeUp import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.dp +import com.synonym.bitkitcore.FeeRates import com.synonym.paykit.PaymentRequestLifecycleState import dagger.hilt.android.testing.HiltAndroidRule import dagger.hilt.android.testing.HiltAndroidTest @@ -41,6 +42,7 @@ import to.bitkit.models.PaykitAmount import to.bitkit.models.PaykitAsset import to.bitkit.models.PaykitRequestPricing import to.bitkit.models.PubkyProfile +import to.bitkit.models.TransactionSpeed import to.bitkit.repositories.MethodId import to.bitkit.repositories.PaykitPaymentRequest import to.bitkit.repositories.PaykitRecurrenceUnit @@ -340,4 +342,31 @@ class SendConfirmScreenTest { assertEquals(amountBounds, amount.getUnclippedBoundsInRoot()) assertEquals(swipeBounds, swipe.getUnclippedBoundsInRoot()) } + + @Test + fun customFeeShowsTheConfirmationTimeOfItsTier() { + composeTestRule.setContent { + AppThemeSurface { + SendConfirmContent( + uiState = SendUiState( + amount = 25_000u, + address = "bcrt1qkgfgyxyqhvkdqh04sklnzxphmcds6vft6y7h0r", + isAmountInputValid = true, + payMethod = SendMethod.ONCHAIN, + speed = TransactionSpeed.Custom(4u), + feeRates = FeeRates(fast = 2u, mid = 1u, slow = 1u), + onchainFeeUi = OnchainFeeUi(rate = FeeRate.CUSTOM), + ), + isNodeRunning = true, + isLoading = false, + showBiometrics = false, + modifier = Modifier.sheetHeight(), + ) + } + } + + composeTestRule.onNodeWithTag("SendConfirmToggleDetails").performClick() + composeTestRule.onNodeWithText("±10-20 minutes").performScrollTo().assertIsDisplayed() + composeTestRule.onNodeWithText("Depends on the fee").assertDoesNotExist() + } } diff --git a/app/src/main/java/to/bitkit/models/FeeRate.kt b/app/src/main/java/to/bitkit/models/FeeRate.kt index 05883f9b0a..38abdd54ad 100644 --- a/app/src/main/java/to/bitkit/models/FeeRate.kt +++ b/app/src/main/java/to/bitkit/models/FeeRate.kt @@ -94,6 +94,13 @@ enum class FeeRate( } } + /** The tier whose confirmation time applies to [speed], so a custom rate reads like the presets. */ + fun forConfirmation(speed: TransactionSpeed, feeRates: FeeRates?): FeeRate { + val custom = speed as? TransactionSpeed.Custom ?: return fromSpeed(speed) + if (feeRates == null || custom.satsPerVByte == 0u) return CUSTOM + return fromSatsPerVByte(custom.satsPerVByte.toULong(), feeRates) + } + fun Context.getFeeShortDescription( feeRate: ULong, feeRates: FeeRates?, diff --git a/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendConfirmScreen.kt b/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendConfirmScreen.kt index 9d9f3d6d11..991e3134ff 100644 --- a/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendConfirmScreen.kt +++ b/app/src/main/java/to/bitkit/ui/screens/wallets/send/SendConfirmScreen.kt @@ -700,7 +700,12 @@ private fun OnChainDetails( tint = Colors.Brand, modifier = Modifier.size(16.dp) ) - BodySSB(stringResource(feeUi.rate.description)) + val confirmationRate = if (feeUi.rate == FeeRate.CUSTOM) { + FeeRate.forConfirmation(uiState.speed, uiState.feeRates) + } else { + feeUi.rate + } + BodySSB(stringResource(confirmationRate.description)) } } } diff --git a/app/src/test/java/to/bitkit/models/FeeRateTest.kt b/app/src/test/java/to/bitkit/models/FeeRateTest.kt new file mode 100644 index 0000000000..6ddeac61a3 --- /dev/null +++ b/app/src/test/java/to/bitkit/models/FeeRateTest.kt @@ -0,0 +1,39 @@ +package to.bitkit.models + +import com.synonym.bitkitcore.FeeRates +import org.junit.Test +import kotlin.test.assertEquals + +class FeeRateTest { + private val feeRates = FeeRates(fast = 10u, mid = 5u, slow = 2u) + + @Test + fun `custom rate at or above the fast tier confirms like fast`() { + assertEquals(FeeRate.FAST, FeeRate.forConfirmation(TransactionSpeed.Custom(10u), feeRates)) + assertEquals(FeeRate.FAST, FeeRate.forConfirmation(TransactionSpeed.Custom(40u), feeRates)) + } + + @Test + fun `custom rate between tiers confirms like the tier below it`() { + assertEquals(FeeRate.NORMAL, FeeRate.forConfirmation(TransactionSpeed.Custom(7u), feeRates)) + assertEquals(FeeRate.SLOW, FeeRate.forConfirmation(TransactionSpeed.Custom(3u), feeRates)) + } + + @Test + fun `custom rate below the slow tier confirms like minimum`() { + assertEquals(FeeRate.MINIMUM, FeeRate.forConfirmation(TransactionSpeed.Custom(1u), feeRates)) + } + + @Test + fun `custom rate without fee tiers keeps the custom description`() { + assertEquals(FeeRate.CUSTOM, FeeRate.forConfirmation(TransactionSpeed.Custom(4u), null)) + assertEquals(FeeRate.CUSTOM, FeeRate.forConfirmation(TransactionSpeed.Custom(0u), feeRates)) + } + + @Test + fun `preset speeds keep their own tier`() { + assertEquals(FeeRate.FAST, FeeRate.forConfirmation(TransactionSpeed.Fast, feeRates)) + assertEquals(FeeRate.NORMAL, FeeRate.forConfirmation(TransactionSpeed.Medium, feeRates)) + assertEquals(FeeRate.SLOW, FeeRate.forConfirmation(TransactionSpeed.Slow, feeRates)) + } +} diff --git a/changelog.d/next/1634.added.md b/changelog.d/next/1634.added.md new file mode 100644 index 0000000000..14be48211c --- /dev/null +++ b/changelog.d/next/1634.added.md @@ -0,0 +1 @@ +The send confirmation now estimates the confirmation time for a custom on-chain fee from the current fee tiers. diff --git a/journeys/send/custom-fee-confirmation-estimate.xml b/journeys/send/custom-fee-confirmation-estimate.xml new file mode 100644 index 0000000000..71931dc9c5 --- /dev/null +++ b/journeys/send/custom-fee-confirmation-estimate.xml @@ -0,0 +1,16 @@ + + + Verifies that a custom on-chain fee shows the confirmation time of the fee tier it reaches, + the same text the preset speeds use, instead of "Depends on the fee". Preconditions: a synced + regtest wallet with at least 30,000 confirmed sats in Savings and the current fee tiers loaded. + The custom rate of 50 ₿/vbyte is above the regtest backend's Fast tier; on a backend whose Fast + tier is higher, use a rate above it. + + + Start an on-chain send of 5,000 sats from Savings to a fresh regtest address and reach the confirmation screen (testTag "SendConfirmContent") + Tap Show details (testTag "SendConfirmToggleDetails") + Open the speed picker by tapping the Speed and Fee value, then tap Custom (testTag "fee_CUSTOM_button") + On the custom fee screen (testTag "fee_screen"), clear the field with Remove (testTag "NRemove") and enter 50 with the number pad (testTags "N5", "N0"), then tap Continue (testTag "continue_btn") + Back on confirmation with details shown, verify Speed and Fee reads Custom and Confirming In reads "±10-20 minutes", the Fast speed's time, not "Depends on the fee" + +