Skip to content
Open
Show file tree
Hide file tree
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
79 changes: 54 additions & 25 deletions app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,8 @@ class TransferViewModel @Inject constructor(
}

/** Pays for the order and start watching it for state updates */
fun onTransferToSpendingConfirm(order: IBtOrder, speed: TransactionSpeed? = null) {
@Suppress("LongMethod")
fun onTransferToSpendingConfirm(order: IBtOrder, speed: TransactionSpeed = TransactionSpeed.Fast) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
fun onTransferToSpendingConfirm(order: IBtOrder, speed: TransactionSpeed = TransactionSpeed.Fast) {
fun onTransferToSpendingConfirm(order: IBtOrder) {

A custom speed is never set anywhere, could remove this parameter

viewModelScope.launch {
val address = order.payment?.onchain?.address.orEmpty()

Expand All @@ -230,53 +231,70 @@ class TransferViewModel @Inject constructor(

val expectedChange =
spendableBalance.toLong() - order.feeSat.toLong() - sendAllFee.toLong()
// Match iOS: proactive drain only for dust change. Negative change means the drain
// output would underpay the order — never send-all in that case.
val shouldUseSendAll =
expectedChange >= 0 && expectedChange < TRANSFER_SEND_ALL_THRESHOLD_SATS
Comment on lines 236 to 237

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: could apply a small refactor converting this to a range check


val miningFee = if (shouldUseSendAll) {
sendAllFee
} else {
lightningRepo.calculateTotalFee(
amountSats = order.feeSat,
address = address,
speed = speed,
).getOrElse {
Logger.warn("Failed to estimate transfer funding fee", it, context = TAG)
0uL
}
}
val txTotalSats = if (shouldUseSendAll) {
spendableBalance
} else {
order.feeSat.safe() + miningFee.safe()
}
val maxSendable =
if (sendAllFee >= spendableBalance) 0uL else spendableBalance - sendAllFee
Comment on lines +238 to +239

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use .safe() here. It has this clamp-to-zero internal logic


Logger.debug(
"BT confirm: spendable=$spendableBalance, feeSat=${order.feeSat}, " +
"sendAllFee=$sendAllFee, expectedChange=$expectedChange, sendAll=$shouldUseSendAll",
context = TAG,
)

lightningRepo
suspend fun fund(isMaxAmount: Boolean, txTotalSats: ULong) = lightningRepo
.sendOnChain(
address = address,
sats = order.feeSat,
speed = speed,
isTransfer = true,
channelId = order.channel?.shortChannelId,
isMaxAmount = shouldUseSendAll,
isMaxAmount = isMaxAmount,
)
.onSuccess { txId ->
fundPaidOrder(
order = order,
txId = txId,
txTotalSats = txTotalSats,
preTransferOnchainSats = balanceDetails?.totalOnchainBalanceSats ?: spendableBalance,
preTransferOnchainSats = balanceDetails?.totalOnchainBalanceSats
?: spendableBalance,
)
}
.onFailure { error ->

if (shouldUseSendAll) {
fund(isMaxAmount = true, txTotalSats = spendableBalance)
.onFailure { ToastEventBus.send(it) }
return@launch
}

val miningFee = lightningRepo.calculateTotalFee(
amountSats = order.feeSat,
address = address,
speed = speed,
).getOrElse {
Logger.warn("Failed to estimate transfer funding fee", it, context = TAG)
0uL
}
fund(
isMaxAmount = false,
txTotalSats = order.feeSat.safe() + miningFee.safe(),
).onFailure { error ->
// Match iOS: if fixed send fails (e.g. coin selection), drain only when the
// drain output still covers order.feeSat — never underpay by emptying the wallet.
if (maxSendable >= order.feeSat) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still checking this logic

Logger.warn(
"Normal transfer funding failed, retrying send-all",
error,
context = TAG,
)
fund(isMaxAmount = true, txTotalSats = spendableBalance)
.onFailure { ToastEventBus.send(it) }
} else {
ToastEventBus.send(error)
}
}
}
}

Expand Down Expand Up @@ -415,10 +433,21 @@ class TransferViewModel @Inject constructor(
viewModelScope.launch {
_spendingUiState.update { it.copy(isLoading = true) }

val availableAmount = walletRepo.balanceState.value.maxSendOnchainSats

awaitNodeRunning()

// Match iOS: start from raw spendable (not maxSendOnchainSats — that already reserved
// a default-tier send-all fee), then subtract exactly one fast mining fee.
val spendable = lightningRepo.getBalancesAsync().getOrNull()?.spendableOnchainBalanceSats
?: 0uL
val miningFee = lightningRepo.estimateSendAllFee(
speed = TransactionSpeed.Fast,
).getOrElse {
Logger.warn("Failed to estimate transfer mining fee reserve", it, context = TAG)
(spendable.toDouble() * Defaults.fallbackFeePercent).toULong()
}
Comment thread
greptile-apps[bot] marked this conversation as resolved.
val availableAmount =
if (miningFee >= spendable) 0uL else spendable - miningFee
Comment on lines +448 to +449

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use .safe() here too


val initialLspFees = estimateInitialLspFees(availableAmount)
if (initialLspFees == null) {
_spendingUiState.update { it.copy(isLoading = false) }
Expand Down
Loading
Loading