Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import kotlinx.coroutines.launch
import to.bitkit.R
import to.bitkit.models.Toast
Expand Down Expand Up @@ -60,6 +61,7 @@ fun SpendingAmountScreen(
val blocktank = blocktankViewModel ?: return
val currencies = LocalCurrencies.current
val resources = LocalContext.current.resources
val transferValues by viewModel.transferValues.collectAsStateWithLifecycle()

ScreenColumn {
AppTopBar(
Expand All @@ -78,41 +80,39 @@ fun SpendingAmountScreen(
var isLoading by remember { mutableStateOf(false) }

val availableAmount = LocalBalances.current.totalOnchainSats - 512u // default tx fee

var maxClientBalance by remember { mutableStateOf(0uL) }
var maxLspBalance by remember { mutableStateOf(0uL) }
var maxLspFee by remember { mutableStateOf(0uL) }

val feeMaximum = max(0, availableAmount.toLong() - maxLspFee.toLong())
val maximum = min(maxClientBalance.toLong(), feeMaximum)
val maximum = min(
transferValues.maxClientBalance.toLong(),
feeMaximum
) // TODO USE MAX AVAILABLE TO TRANSFER INSTEAD OF MAX ONCHAIN BALANCE

// Update maxClientBalance Effect
LaunchedEffect(satsAmount) {
val transferValues = viewModel.calculateTransferValues(satsAmount.toULong())
maxClientBalance = transferValues.maxClientBalance
Logger.debug("maxClientBalance: $maxClientBalance", context = "SpendingAmountScreen")
}

// Update maxLspBalance Effect
LaunchedEffect(availableAmount) {
val transferValues = viewModel.calculateTransferValues(availableAmount)
maxLspBalance = transferValues.defaultLspBalance
Logger.debug("maxLspBalance: $maxLspBalance", context = "SpendingAmountScreen")
viewModel.updateTransferValues(satsAmount.toULong())
Logger.debug(
"satsAmount changed - maxClientBalance: ${transferValues.maxClientBalance}",
context = "SpendingAmountScreen"
)
}

// Update maxLspFee Effect
LaunchedEffect(availableAmount, maxLspBalance) {
LaunchedEffect(availableAmount, transferValues.maxLspBalance) { // TODO MOVE TO VIEWMODEL
runCatching {
val estimate = blocktank.estimateOrderFee(
spendingBalanceSats = availableAmount,
receivingBalanceSats = maxLspBalance,
receivingBalanceSats = transferValues.maxLspBalance,
)
maxLspFee = estimate.feeSat
}
}

Spacer(modifier = Modifier.height(32.dp))
Display(text = stringResource(R.string.lightning__spending_amount__title).withAccent(accentColor = Colors.Purple))
Display(
text = stringResource(R.string.lightning__spending_amount__title)
.withAccent(accentColor = Colors.Purple)
)
Spacer(modifier = Modifier.height(32.dp))

AmountInput(
Expand Down Expand Up @@ -167,11 +167,13 @@ fun SpendingAmountScreen(
PrimaryButton(
text = stringResource(R.string.common__continue),
onClick = {
if (maxLspBalance == 0uL) {
if (transferValues.maxLspBalance == 0uL) {
app.toast(
type = Toast.ToastType.ERROR,
title = resources.getString(R.string.lightning__spending_amount__error_max__title),
description = resources.getString(R.string.lightning__spending_amount__error_max__description_zero),
description = resources.getString(
R.string.lightning__spending_amount__error_max__description_zero
),
)
return@PrimaryButton
}
Expand Down
52 changes: 39 additions & 13 deletions app/src/main/java/to/bitkit/viewmodels/TransferViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -194,12 +194,40 @@ class TransferViewModel @Inject constructor(

// The maximum channel size the user can open including existing channels
val maxChannelSize2 = if (maxChannelSize1 > channelsSize) maxChannelSize1 - channelsSize else 0u
val maxChannelSize = min(maxChannelSize1, maxChannelSize2)
val maxChannelSizeAvailableToIncrease = min(maxChannelSize1, maxChannelSize2)

val minLspBalance = getMinLspBalance(clientBalanceSat, minChannelSizeSat)
val maxLspBalance = if (maxChannelSize > clientBalanceSat) maxChannelSize - clientBalanceSat else 0u
val maxLspBalance = if (maxChannelSizeAvailableToIncrease > clientBalanceSat) {
maxChannelSizeAvailableToIncrease - clientBalanceSat
} else {
0u
}
val defaultLspBalance = getDefaultLspBalance(clientBalanceSat, maxLspBalance)
val maxClientBalance = getMaxClientBalance(maxChannelSize)
val maxClientBalance = getMaxClientBalance(maxChannelSizeAvailableToIncrease)

if (maxChannelSizeAvailableToIncrease < clientBalanceSat) { // TODO DISPLAY ERROR
Logger.warn(
"Amount clientBalanceSat:$clientBalanceSat too large, max possible: $maxChannelSizeAvailableToIncrease",
context = TAG
)
}

if (defaultLspBalance < minLspBalance || defaultLspBalance > maxLspBalance) {
Logger.warn(
"Invalid defaultLspBalance:$defaultLspBalance " +
"min possible:$maxLspBalance, " +
"max possible: $minLspBalance",
context = TAG
)
}

if (maxChannelSizeAvailableToIncrease <= 0u) {
Logger.warn("Max channel size reached. current size: $channelsSize sats", context = TAG)
}

if (maxClientBalance <= 0u) {
Logger.warn("No liquidity available to purchase $maxClientBalance", context = TAG)
}

return TransferValues(
defaultLspBalance = defaultLspBalance,
Expand All @@ -225,16 +253,14 @@ class TransferViewModel @Inject constructor(
throw ServiceError.CurrencyRateUnavailable
}

// Safely calculate lspBalance to avoid arithmetic overflow
var lspBalance: ULong = 0u
if (defaultLspBalanceSats > clientBalanceSat) {
lspBalance = defaultLspBalanceSats - clientBalanceSat
}
if (lspBalance > threshold1) {
lspBalance = clientBalanceSat
}
if (lspBalance > threshold2) {
lspBalance = maxLspBalance
val lspBalance = if (clientBalanceSat < threshold1) { // 0-225€: LSP balance = 450€ - client balance
defaultLspBalanceSats - clientBalanceSat
} else if (clientBalanceSat < threshold2) { // 225-495€: LSP balance = client balance
clientBalanceSat
} else if (clientBalanceSat < maxLspBalance) { // 495-950€: LSP balance = max - client balance
maxLspBalance - clientBalanceSat
} else {
maxLspBalance
}

return min(lspBalance, maxLspBalance)
Expand Down