Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 50 additions & 13 deletions app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import org.lightningdevkit.ldknode.ChannelDetails
import to.bitkit.R
import to.bitkit.data.CacheStore
import to.bitkit.data.SettingsStore
import to.bitkit.env.Defaults
import to.bitkit.ext.amountOnClose
import to.bitkit.models.Toast
import to.bitkit.models.TransactionSpeed
Expand Down Expand Up @@ -194,13 +195,26 @@ class TransferViewModel @Inject constructor(
viewModelScope.launch {
val address = order.payment?.onchain?.address.orEmpty()

// Calculate if change would be dust and we should use sendAll
val spendableBalance =
lightningRepo.lightningState.value.balances?.spendableOnchainBalanceSats ?: 0uL
val txFee = lightningRepo.calculateTotalFee(
amountSats = spendableBalance,
address = address,
speed = speed,
).getOrElse { 0uL }

val expectedChange = spendableBalance.toLong() - order.feeSat.toLong() - txFee.toLong()
val shouldUseSendAll = expectedChange >= 0 && expectedChange < Defaults.dustLimit.toInt()

lightningRepo
.sendOnChain(
address = address,
sats = order.feeSat,
speed = speed,
isTransfer = true,
channelId = order.channel?.shortChannelId,
isMaxAmount = shouldUseSendAll,
)
.onSuccess { txId ->
cacheStore.addPaidOrder(orderId = order.id, txId = txId)
Expand Down Expand Up @@ -297,30 +311,53 @@ class TransferViewModel @Inject constructor(
isNodeRunning.first { it }
}

// Calculate the LSP fee to the total balance
blocktankRepo.estimateOrderFee(
// Two-pass fee estimation to match actual order creation
// First pass: estimate with availableAmount to get approximate clientBalance
val values1 = blocktankRepo.calculateLiquidityOptions(availableAmount).getOrNull()
if (values1 == null) {
_spendingUiState.update { it.copy(isLoading = false) }
return@launch
}
val lspBalance1 = maxOf(values1.defaultLspBalanceSat, values1.minLspBalanceSat)
val feeEstimate1 = blocktankRepo.estimateOrderFee(
spendingBalanceSats = availableAmount,
receivingBalanceSats = _transferValues.value.maxLspBalance
receivingBalanceSats = lspBalance1,
).getOrNull()

if (feeEstimate1 == null) {
_spendingUiState.update { it.copy(isLoading = false) }
return@launch
}

val lspFees1 = feeEstimate1.networkFeeSat.safe() + feeEstimate1.serviceFeeSat.safe()
val approxClientBalance = availableAmount.safe() - lspFees1.safe()

// Second pass: recalculate with actual clientBalance that order creation will use
val values2 = blocktankRepo.calculateLiquidityOptions(approxClientBalance).getOrNull()
if (values2 == null || values2.maxLspBalanceSat == 0uL) {
_spendingUiState.update { it.copy(isLoading = false, maxAllowedToSend = 0) }
return@launch
}
val lspBalance2 = maxOf(values2.defaultLspBalanceSat, values2.minLspBalanceSat)

blocktankRepo.estimateOrderFee(
spendingBalanceSats = approxClientBalance,
receivingBalanceSats = lspBalance2,
).onSuccess { estimate ->
maxLspFee = estimate.feeSat

// Calculate the available balance to send after LSP fee
val balanceAfterLspFee = availableAmount.safe() - maxLspFee.safe()
val lspFees = estimate.networkFeeSat.safe() + estimate.serviceFeeSat.safe()
val maxClientBalance = availableAmount.safe() - lspFees.safe()

_spendingUiState.update {
// Calculate the max available to send considering the current balance and LSP policy
it.copy(
maxAllowedToSend = min(
_transferValues.value.maxClientBalance.toLong(),
balanceAfterLspFee.toLong()
),
maxAllowedToSend = min(values2.maxClientBalanceSat.toLong(), maxClientBalance.toLong()),
isLoading = false,
balanceAfterFee = availableAmount.toLong()
balanceAfterFee = availableAmount.toLong(),
)
}
}.onFailure { exception ->
_spendingUiState.update { it.copy(isLoading = false) }
Logger.error("Failure", exception)
Logger.error("Failure", exception, context = TAG)
setTransferEffect(TransferEffect.ToastException(exception))
}
}
Expand Down
Loading