Skip to content
Merged
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
104 changes: 78 additions & 26 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 = order.feeSat,
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 @@ -311,39 +325,77 @@ class TransferViewModel @Inject constructor(
viewModelScope.launch {
_spendingUiState.update { it.copy(isLoading = true) }

// Get the max available balance discounting onChain fee
val availableAmount = walletRepo.balanceState.value.maxSendOnchainSats

withTimeoutOrNull(1.minutes) {
isNodeRunning.first { it }
awaitNodeRunning()

val initialLspFees = estimateInitialLspFees(availableAmount)
if (initialLspFees == null) {
_spendingUiState.update { it.copy(isLoading = false) }
return@launch
}

// Calculate the LSP fee to the total balance
blocktankRepo.estimateOrderFee(
spendingBalanceSats = availableAmount,
receivingBalanceSats = _transferValues.value.maxLspBalance
).onSuccess { estimate ->
maxLspFee = estimate.feeSat
val balanceAfterLspFee = availableAmount.safe() - initialLspFees.safe()

// Calculate the available balance to send after LSP fee
val balanceAfterLspFee = availableAmount.safe() - maxLspFee.safe()
estimateFinalMaxSendAmount(availableAmount, balanceAfterLspFee)
}
}

_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()
),
isLoading = false,
balanceAfterFee = availableAmount.toLong()
)
}
}.onFailure { exception ->
_spendingUiState.update { it.copy(isLoading = false) }
Logger.error("Failure", exception)
setTransferEffect(TransferEffect.ToastException(exception))
private suspend fun awaitNodeRunning() {
withTimeoutOrNull(1.minutes) {
isNodeRunning.first { it }
}
}

private suspend fun estimateInitialLspFees(availableAmount: ULong): ULong? {
val liquidity = blocktankRepo
.calculateLiquidityOptions(availableAmount)
.getOrNull() ?: return null

val lspBalance = maxOf(liquidity.defaultLspBalanceSat, liquidity.minLspBalanceSat)

val orderFee = blocktankRepo.estimateOrderFee(
spendingBalanceSats = availableAmount,
receivingBalanceSats = lspBalance,
).getOrNull() ?: return null

return orderFee.networkFeeSat.safe() + orderFee.serviceFeeSat.safe()
}

private suspend fun estimateFinalMaxSendAmount(
availableAmount: ULong,
balanceAfterLspFee: ULong,
) {
val liquidity = blocktankRepo.calculateLiquidityOptions(balanceAfterLspFee).getOrNull()
if (liquidity == null || liquidity.maxLspBalanceSat == 0uL) {
_spendingUiState.update { it.copy(isLoading = false, maxAllowedToSend = 0) }
return
}

val receivingAmount = maxOf(liquidity.defaultLspBalanceSat, liquidity.minLspBalanceSat)

blocktankRepo.estimateOrderFee(
spendingBalanceSats = balanceAfterLspFee,
receivingBalanceSats = receivingAmount,
).onSuccess { estimate ->
maxLspFee = estimate.feeSat
val lspFees = estimate.networkFeeSat.safe() + estimate.serviceFeeSat.safe()
val maxClientBalance = availableAmount.safe() - lspFees.safe()

_spendingUiState.update {
it.copy(
maxAllowedToSend = min(
liquidity.maxClientBalanceSat.toLong(),
maxClientBalance.toLong()
),
isLoading = false,
balanceAfterFee = availableAmount.toLong(),
)
}
}.onFailure {
_spendingUiState.update { it.copy(isLoading = false) }
Logger.error("Failure", it, context = TAG)
setTransferEffect(TransferEffect.ToastException(it))
}
}

Expand Down
Loading