Skip to content

Commit f4c26b0

Browse files
authored
Merge pull request #552 from synonymdev/fix/send-amount-routing-fee
fix: max sendable lightning calc doesn't account for routing fee
2 parents 03f27fe + 1fcb36b commit f4c26b0

File tree

3 files changed

+56
-4
lines changed

3 files changed

+56
-4
lines changed

app/src/main/java/to/bitkit/models/BalanceState.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import kotlinx.serialization.Serializable
66
data class BalanceState(
77
val totalOnchainSats: ULong = 0uL,
88
val totalLightningSats: ULong = 0uL,
9-
val maxSendLightningSats: ULong = 0uL,
9+
val maxSendLightningSats: ULong = 0uL, // Without account routing fees
1010
val maxSendOnchainSats: ULong = 0uL,
1111
val balanceInTransferToSavings: ULong = 0uL,
1212
val balanceInTransferToSpending: ULong = 0uL,

app/src/main/java/to/bitkit/ui/screens/wallets/send/SendAmountScreen.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import to.bitkit.models.BalanceState
3333
import to.bitkit.models.BitcoinDisplayUnit
3434
import to.bitkit.models.NodeLifecycleState
3535
import to.bitkit.models.Toast
36+
import to.bitkit.models.safe
3637
import to.bitkit.repositories.CurrencyState
3738
import to.bitkit.ui.LocalBalances
3839
import to.bitkit.ui.LocalCurrencies
@@ -91,6 +92,12 @@ fun SendAmountScreen(
9192
currentOnEvent(SendEvent.AmountChange(amountInputUiState.sats.toULong()))
9293
}
9394

95+
LaunchedEffect(uiState.decodedInvoice, uiState.payMethod) {
96+
if (uiState.payMethod == SendMethod.LIGHTNING && uiState.decodedInvoice != null) {
97+
currentOnEvent(SendEvent.EstimateMaxRoutingFee)
98+
}
99+
}
100+
94101
SendAmountContent(
95102
walletUiState = walletUiState,
96103
uiState = uiState,
@@ -190,7 +197,11 @@ private fun SendAmountNodeRunning(
190197
val availableAmount = when {
191198
isLnurlWithdraw -> uiState.lnurl.data.maxWithdrawableSat().toLong()
192199
uiState.payMethod == SendMethod.ONCHAIN -> balances.maxSendOnchainSats.toLong()
193-
else -> balances.maxSendLightningSats.toLong()
200+
else -> {
201+
val maxLightning = balances.maxSendLightningSats
202+
val routingFee = uiState.estimatedRoutingFee
203+
(maxLightning.safe() - routingFee.safe()).toLong()
204+
}
194205
}
195206

196207
Column(

app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ import to.bitkit.models.NewTransactionSheetType
8383
import to.bitkit.models.Suggestion
8484
import to.bitkit.models.Toast
8585
import to.bitkit.models.TransactionSpeed
86+
import to.bitkit.models.safe
8687
import to.bitkit.models.toActivityFilter
8788
import to.bitkit.models.toTxType
8889
import to.bitkit.repositories.ActivityRepo
@@ -472,6 +473,10 @@ class AppViewModel @Inject constructor(
472473
SendEvent.SwipeToPay -> onSwipeToPay()
473474
is SendEvent.ConfirmAmountWarning -> onConfirmAmountWarning(it.warning)
474475
SendEvent.DismissAmountWarning -> onDismissAmountWarning()
476+
SendEvent.EstimateMaxRoutingFee -> viewModelScope.launch {
477+
estimateMaxAmountRoutingFee()
478+
}
479+
475480
SendEvent.PayConfirmed -> onConfirmPay()
476481
SendEvent.ClearPayConfirmation -> _sendUiState.update { s -> s.copy(shouldConfirmPay = false) }
477482
SendEvent.BackToAmount -> setSendEffect(SendEffect.PopBack(SendRoute.Amount))
@@ -1042,9 +1047,12 @@ class AppViewModel @Inject constructor(
10421047
if (_sendUiState.value.showSanityWarningDialog != null) return
10431048

10441049
val settings = settingsStore.data.first()
1045-
1050+
val balanceToCheck = when (_sendUiState.value.payMethod) {
1051+
SendMethod.ONCHAIN -> walletRepo.balanceState.value.maxSendOnchainSats
1052+
SendMethod.LIGHTNING -> walletRepo.balanceState.value.maxSendLightningSats
1053+
}
10461054
if (
1047-
amountSats > BigDecimal.valueOf(walletRepo.balanceState.value.totalSats.toLong())
1055+
amountSats > BigDecimal.valueOf(balanceToCheck.toLong())
10481056
.times(BigDecimal(MAX_BALANCE_FRACTION)).toLong().toUInt() &&
10491057
SanityWarning.OVER_HALF_BALANCE !in _sendUiState.value.confirmedWarnings
10501058
) {
@@ -1458,6 +1466,37 @@ class AppViewModel @Inject constructor(
14581466
}
14591467
}
14601468

1469+
private suspend fun estimateMaxAmountRoutingFee() {
1470+
val currentState = _sendUiState.value
1471+
if (currentState.payMethod != SendMethod.LIGHTNING) return
1472+
1473+
val decodedInvoice = currentState.decodedInvoice ?: return
1474+
val bolt11 = decodedInvoice.bolt11
1475+
1476+
val maxSendLightning = walletRepo.balanceState.value.maxSendLightningSats
1477+
if (maxSendLightning == 0uL) {
1478+
_sendUiState.update { it.copy(estimatedRoutingFee = 0uL) }
1479+
return
1480+
}
1481+
1482+
val buffer = 2uL
1483+
val amountToEstimate = maxSendLightning.safe() - buffer.safe()
1484+
1485+
val feeResult = lightningRepo.estimateRoutingFeesForAmount(
1486+
bolt11 = bolt11,
1487+
amountSats = amountToEstimate
1488+
)
1489+
1490+
feeResult.onSuccess { fee ->
1491+
_sendUiState.update {
1492+
it.copy(estimatedRoutingFee = fee + buffer)
1493+
}
1494+
}.onFailure { e ->
1495+
Logger.error("Failed to estimate routing fee for max amount", e, context = TAG)
1496+
_sendUiState.update { it.copy(estimatedRoutingFee = 0uL) }
1497+
}
1498+
}
1499+
14611500
private suspend fun getFeeEstimate(speed: TransactionSpeed? = null): Long {
14621501
val currentState = _sendUiState.value
14631502
return lightningRepo.calculateTotalFee(
@@ -2001,6 +2040,7 @@ data class SendUiState(
20012040
val feeRates: FeeRates? = null,
20022041
val fee: SendFee? = null,
20032042
val fees: Map<FeeRate, Long> = emptyMap(),
2043+
val estimatedRoutingFee: ULong = 0uL,
20042044
)
20052045

20062046
enum class SanityWarning(@StringRes val message: Int, val testTag: String) {
@@ -2064,6 +2104,7 @@ sealed interface SendEvent {
20642104
data object SpeedAndFee : SendEvent
20652105
data object PaymentMethodSwitch : SendEvent
20662106
data class ConfirmAmountWarning(val warning: SanityWarning) : SendEvent
2107+
data object EstimateMaxRoutingFee : SendEvent
20672108
data object DismissAmountWarning : SendEvent
20682109
data object PayConfirmed : SendEvent
20692110
data object ClearPayConfirmation : SendEvent

0 commit comments

Comments
 (0)