@@ -4,7 +4,6 @@ import android.content.Context
44import androidx.lifecycle.ViewModel
55import androidx.lifecycle.viewModelScope
66import com.synonym.bitkitcore.BtOrderState2
7- import com.synonym.bitkitcore.IBtInfo
87import com.synonym.bitkitcore.IBtOrder
98import dagger.hilt.android.lifecycle.HiltViewModel
109import dagger.hilt.android.qualifiers.ApplicationContext
@@ -32,22 +31,17 @@ import to.bitkit.data.CacheStore
3231import to.bitkit.data.SettingsStore
3332import to.bitkit.env.Env
3433import to.bitkit.ext.amountOnClose
35- import to.bitkit.models.EUR_CURRENCY
3634import to.bitkit.models.Toast
3735import to.bitkit.models.TransactionSpeed
3836import to.bitkit.models.TransferType
3937import to.bitkit.models.safe
4038import to.bitkit.repositories.BlocktankRepo
41- import to.bitkit.repositories.CurrencyRepo
4239import to.bitkit.repositories.LightningRepo
4340import to.bitkit.repositories.TransferRepo
4441import to.bitkit.repositories.WalletRepo
4542import to.bitkit.ui.shared.toast.ToastEventBus
4643import to.bitkit.utils.Logger
47- import to.bitkit.utils.ServiceError
48- import java.math.BigDecimal
4944import javax.inject.Inject
50- import kotlin.math.max
5145import kotlin.math.min
5246import kotlin.math.roundToLong
5347import kotlin.time.Duration.Companion.minutes
@@ -63,7 +57,6 @@ class TransferViewModel @Inject constructor(
6357 private val lightningRepo : LightningRepo ,
6458 private val blocktankRepo : BlocktankRepo ,
6559 private val walletRepo : WalletRepo ,
66- private val currencyRepo : CurrencyRepo ,
6760 private val settingsStore : SettingsStore ,
6861 private val cacheStore : CacheStore ,
6962 private val transferRepo : TransferRepo ,
@@ -94,7 +87,8 @@ class TransferViewModel @Inject constructor(
9487 // region Spending
9588
9689 fun onConfirmAmount (satsAmount : Long ) {
97- if (_transferValues .value.maxLspBalance == 0uL ) {
90+ val values = blocktankRepo.calculateLiquidityOptions(satsAmount.toULong()).getOrNull()
91+ if (values == null || values.maxLspBalanceSat == 0uL ) {
9892 setTransferEffect(
9993 TransferEffect .ToastError (
10094 title = context.getString(R .string.lightning__spending_amount__error_max__title),
@@ -105,28 +99,20 @@ class TransferViewModel @Inject constructor(
10599 )
106100 return
107101 }
102+
103+ val lspBalance = maxOf(values.defaultLspBalanceSat, values.minLspBalanceSat)
104+
108105 viewModelScope.launch {
109106 _spendingUiState .update { it.copy(isLoading = true ) }
110107
111- val minAmount = getMinAmountToPurchase(satsAmount)
112- if (satsAmount < minAmount) {
113- setTransferEffect(
114- TransferEffect .ToastError (
115- title = context.getString(R .string.lightning__spending_amount__error_min__title),
116- description = context.getString(
117- R .string.lightning__spending_amount__error_min__description
118- ).replace(" {amount}" , " $minAmount " ),
119- )
120- )
121- _spendingUiState .update { it.copy(isLoading = false ) }
122- return @launch
123- }
124-
125108 withTimeoutOrNull(1 .minutes) {
126109 isNodeRunning.first { it }
127110 }
128111
129- blocktankRepo.createOrder(satsAmount.toULong())
112+ blocktankRepo.createOrder(
113+ spendingBalanceSats = satsAmount.toULong(),
114+ receivingBalanceSats = lspBalance,
115+ )
130116 .onSuccess { order ->
131117 settingsStore.update { it.copy(lightningSetupStep = 0 ) }
132118 onOrderCreated(order)
@@ -275,11 +261,6 @@ class TransferViewModel @Inject constructor(
275261 }
276262 }
277263
278- private suspend fun getMinAmountToPurchase (satsAmount : Long = 0L): Long {
279- val fee = lightningRepo.calculateTotalFee(satsAmount.toULong()).getOrNull() ? : 0u
280- return max((fee + maxLspFee).toLong(), Env .TransactionDefaults .dustLimit.toLong())
281- }
282-
283264 private suspend fun onOrderCreated (order : IBtOrder ) {
284265 settingsStore.update { it.copy(lightningSetupStep = 0 ) }
285266 _spendingUiState .update { it.copy(order = order, isAdvanced = false , defaultOrder = null ) }
@@ -361,120 +342,17 @@ class TransferViewModel @Inject constructor(
361342 // region Balance Calc
362343
363344 fun updateTransferValues (clientBalanceSat : ULong ) {
364- viewModelScope.launch {
365- _transferValues .value = calculateTransferValues(clientBalanceSat)
366- }
367- }
368-
369- fun calculateTransferValues (clientBalanceSat : ULong ): TransferValues {
370- val blocktankInfo = blocktankRepo.blocktankState.value.info
371- if (blocktankInfo == null ) return TransferValues ()
372-
373- // Calculate the total value of existing Blocktank channels
374- val channelsSize = totalBtChannelsValueSats(blocktankInfo)
375-
376- val minChannelSizeSat = blocktankInfo.options.minChannelSizeSat
377- val maxChannelSizeSat = blocktankInfo.options.maxChannelSizeSat
378-
379- // Because LSP limits constantly change depending on network fees
380- // Add a 2% buffer to avoid fluctuations while making the order
381- val maxChannelSize1 = (maxChannelSizeSat.toDouble() * 0.98 ).roundToLong().toULong()
382-
383- // The maximum channel size the user can open including existing channels
384- val maxChannelSize2 = maxChannelSize1.safe() - channelsSize.safe()
385- val maxChannelSizeAvailableToIncrease = min(maxChannelSize1, maxChannelSize2)
386-
387- val minLspBalance = getMinLspBalance(clientBalanceSat, minChannelSizeSat)
388- val maxLspBalance = maxChannelSizeAvailableToIncrease.safe() - clientBalanceSat.safe()
389- val defaultLspBalance = getDefaultLspBalance(clientBalanceSat, maxLspBalance)
390- val maxClientBalance = getMaxClientBalance(maxChannelSizeAvailableToIncrease)
391-
392- if (maxChannelSizeAvailableToIncrease < clientBalanceSat) {
393- Logger .warn(
394- " Amount clientBalanceSat:$clientBalanceSat too large, max possible: $maxChannelSizeAvailableToIncrease " ,
395- context = TAG
396- )
397- }
398-
399- if (defaultLspBalance !in minLspBalance.. maxLspBalance) {
400- Logger .warn(
401- " Invalid defaultLspBalance:$defaultLspBalance " +
402- " min possible:$maxLspBalance , " +
403- " max possible: $minLspBalance " ,
404- context = TAG
345+ val options = blocktankRepo.calculateLiquidityOptions(clientBalanceSat).getOrNull()
346+ _transferValues .value = if (options != null ) {
347+ TransferValues (
348+ defaultLspBalance = options.defaultLspBalanceSat,
349+ minLspBalance = options.minLspBalanceSat,
350+ maxLspBalance = options.maxLspBalanceSat,
351+ maxClientBalance = options.maxClientBalanceSat,
405352 )
406- }
407-
408- if (maxChannelSizeAvailableToIncrease <= 0u ) {
409- Logger .warn(" Max channel size reached. current size: $channelsSize sats" , context = TAG )
410- }
411-
412- if (maxClientBalance <= 0u ) {
413- Logger .warn(" No liquidity available to purchase $maxClientBalance " , context = TAG )
414- }
415-
416- return TransferValues (
417- defaultLspBalance = defaultLspBalance,
418- minLspBalance = minLspBalance,
419- maxLspBalance = maxLspBalance,
420- maxClientBalance = maxClientBalance,
421- )
422- }
423-
424- private fun getDefaultLspBalance (clientBalanceSat : ULong , maxLspBalance : ULong ): ULong {
425- // Calculate thresholds in sats
426- val threshold1 = currencyRepo.convertFiatToSats(BigDecimal (225 ), EUR_CURRENCY ).getOrNull()
427- val threshold2 = currencyRepo.convertFiatToSats(BigDecimal (495 ), EUR_CURRENCY ).getOrNull()
428- val defaultLspBalanceSats = currencyRepo.convertFiatToSats(BigDecimal (450 ), EUR_CURRENCY ).getOrNull()
429-
430- Logger .debug(" getDefaultLspBalance - clientBalanceSat: $clientBalanceSat " )
431- Logger .debug(" getDefaultLspBalance - maxLspBalance: $maxLspBalance " )
432- Logger .debug(" getDefaultLspBalance - defaultLspBalanceSats: $defaultLspBalanceSats " )
433-
434- if (threshold1 == null || threshold2 == null || defaultLspBalanceSats == null ) {
435- Logger .error(" Failed to get rates for lspBalance calculation" , context = TAG )
436- throw ServiceError .CurrencyRateUnavailable
437- }
438-
439- val lspBalance = if (clientBalanceSat < threshold1) { // 0-225€: LSP balance = 450€ - client balance
440- defaultLspBalanceSats.safe() - clientBalanceSat.safe()
441- } else if (clientBalanceSat < threshold2) { // 225-495€: LSP balance = client balance
442- clientBalanceSat
443- } else if (clientBalanceSat < maxLspBalance) { // 495-950€: LSP balance = max - client balance
444- maxLspBalance.safe() - clientBalanceSat.safe()
445353 } else {
446- maxLspBalance
354+ TransferValues ()
447355 }
448-
449- return min(lspBalance, maxLspBalance)
450- }
451-
452- private fun getMinLspBalance (clientBalance : ULong , minChannelSize : ULong ): ULong {
453- // LSP balance must be at least 2.5% of the channel size for LDK to accept (reserve balance)
454- val ldkMinimum = (clientBalance.toDouble() * 0.025 ).toULong()
455- // Channel size must be at least minChannelSize
456- val lspMinimum = minChannelSize.safe() - clientBalance.safe()
457-
458- return max(ldkMinimum, lspMinimum)
459- }
460-
461- private fun getMaxClientBalance (maxChannelSize : ULong ): ULong {
462- // Remote balance must be at least 2.5% of the channel size for LDK to accept (reserve balance)
463- val minRemoteBalance = (maxChannelSize.toDouble() * 0.025 ).toULong()
464-
465- return maxChannelSize.safe() - minRemoteBalance.safe()
466- }
467-
468- /* * Calculates the total value of channels connected to Blocktank nodes */
469- private fun totalBtChannelsValueSats (info : IBtInfo ? ): ULong {
470- val channels = lightningRepo.getChannels() ? : return 0u
471- val btNodeIds = info?.nodes?.map { it.pubkey } ? : return 0u
472-
473- val btChannels = channels.filter { btNodeIds.contains(it.counterpartyNodeId) }
474-
475- val totalValue = btChannels.sumOf { it.channelValueSats }
476-
477- return totalValue
478356 }
479357
480358 // endregion
0 commit comments