Skip to content
Draft
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
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import to.bitkit.models.NewTransactionSheetType
import to.bitkit.models.NotificationDetails
import to.bitkit.models.PrimaryDisplay
import to.bitkit.models.formatToModernDisplay
import to.bitkit.repositories.ActivityRepo
import to.bitkit.models.msatCeilOf
import to.bitkit.repositories.ActivityRepo
import to.bitkit.repositories.CurrencyRepo
import to.bitkit.utils.Logger
import javax.inject.Inject
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/to/bitkit/fcm/WakeNodeWorker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import to.bitkit.ext.amountOnClose
import to.bitkit.ext.toUserMessage
import to.bitkit.models.BITCOIN_SYMBOL
import to.bitkit.models.BlocktankNotificationType
import to.bitkit.models.msatCeilOf
import to.bitkit.models.BlocktankNotificationType.cjitPaymentArrived
import to.bitkit.models.BlocktankNotificationType.incomingHtlc
import to.bitkit.models.BlocktankNotificationType.mutualClose
Expand All @@ -36,6 +35,7 @@ import to.bitkit.models.NewTransactionSheetDetails
import to.bitkit.models.NewTransactionSheetDirection
import to.bitkit.models.NewTransactionSheetType
import to.bitkit.models.NotificationDetails
import to.bitkit.models.msatCeilOf
import to.bitkit.repositories.ActivityRepo
import to.bitkit.repositories.BlocktankRepo
import to.bitkit.repositories.LightningRepo
Expand Down
12 changes: 10 additions & 2 deletions app/src/main/java/to/bitkit/repositories/LightningRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1019,12 +1019,20 @@ class LightningRepo @Inject constructor(
}

suspend fun waitForUsableChannels() = withContext(bgDispatcher) {
if (_lightningState.value.channels.any { it.isUsable }) return@withContext
val state = _lightningState.value
if (!state.nodeLifecycleState.canRun()) return@withContext

if (state.channels.isEmpty()) return@withContext // no channel exists, don't wait
if (state.channels.any { it.isUsable }) return@withContext

Logger.info("Waiting for usable channels before sending payment", context = TAG)

withTimeoutOrNull(CHANNELS_USABLE_TIMEOUT_MS) {
_lightningState.first { state -> state.channels.any { it.isUsable } }
_lightningState.first {
!it.nodeLifecycleState.canRun() ||
it.channels.isEmpty() ||
it.channels.any { channel -> channel.isUsable }
}
} ?: Logger.warn("Timed out waiting for usable channels", context = TAG)
}

Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/to/bitkit/repositories/WalletRepo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ import to.bitkit.ext.toHex
import to.bitkit.models.ALL_ADDRESS_TYPE_STRINGS
import to.bitkit.models.AddressModel
import to.bitkit.models.BalanceState
import to.bitkit.models.msatFloorOf
import to.bitkit.models.DEFAULT_ADDRESS_TYPE_STRING
import to.bitkit.models.msatFloorOf
import to.bitkit.models.toDerivationPath
import to.bitkit.services.CoreService
import to.bitkit.usecases.DeriveBalanceStateUseCase
Expand Down
4 changes: 1 addition & 3 deletions app/src/main/java/to/bitkit/services/CoreService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,11 @@ import to.bitkit.data.CacheStore
import to.bitkit.data.SettingsStore
import to.bitkit.env.Env
import to.bitkit.ext.amountSats
import to.bitkit.models.msatFloorOf
import to.bitkit.ext.channelId
import to.bitkit.ext.create
import to.bitkit.ext.latestSpendingTxid
import to.bitkit.models.addressTypeFromAddress
import to.bitkit.models.msatFloorOf
import to.bitkit.models.toCoreNetwork
import to.bitkit.utils.AppError
import to.bitkit.utils.Logger
Expand Down Expand Up @@ -1334,8 +1334,6 @@ class ActivityService(

// Check if any input spends a closed channel's funding UTXO (commitment tx)
findChannelByFundingUtxo(details, closedChannelsList)
// Check if any input's parent transaction is a channel-related activity
// (e.g., sweep tx spending from commitment tx)
?: findChannelByParentActivity(details)
}.onFailure { e ->
Logger.warn("Failed to check if transaction $txid spends closed channel funding UTXO", e, context = TAG)
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/to/bitkit/ui/NodeInfoScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,11 @@ import to.bitkit.ext.createChannelDetails
import to.bitkit.ext.ellipsisMiddle
import to.bitkit.ext.formatToString
import to.bitkit.ext.uri
import to.bitkit.models.msatFloorOf
import to.bitkit.models.NodeLifecycleState
import to.bitkit.models.NodePeer
import to.bitkit.models.alias
import to.bitkit.models.formatToModernDisplay
import to.bitkit.models.msatFloorOf
import to.bitkit.repositories.LightningState
import to.bitkit.ui.components.BodyM
import to.bitkit.ui.components.BodyMSB
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,8 @@ import to.bitkit.ext.DatePattern
import to.bitkit.ext.amountOnClose
import to.bitkit.ext.createChannelDetails
import to.bitkit.ext.setClipboardText
import to.bitkit.models.msatFloorOf
import to.bitkit.models.Toast
import to.bitkit.models.msatFloorOf
import to.bitkit.ui.Routes
import to.bitkit.ui.appViewModel
import to.bitkit.ui.components.Caption13Up
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,8 @@ import kotlinx.collections.immutable.toImmutableList
import to.bitkit.R
import to.bitkit.ext.amountOnClose
import to.bitkit.ext.createChannelDetails
import to.bitkit.models.msatFloorOf
import to.bitkit.models.formatToModernDisplay
import to.bitkit.models.msatFloorOf
import to.bitkit.ui.Routes
import to.bitkit.ui.components.BodyM
import to.bitkit.ui.components.BodyMSB
Expand Down
126 changes: 58 additions & 68 deletions app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -1306,6 +1306,7 @@ class AppViewModel @Inject constructor(
resetSendState()
resetQuickPay()

val fromMainScanner = isMainScanner
val input = result.removeLightningSchemes()

// TODO Workaround for https://github.com/synonymdev/bitkit-core/issues/63
Expand Down Expand Up @@ -1343,16 +1344,12 @@ class AppViewModel @Inject constructor(
.onSuccess { Logger.info("Handling decoded scan data: $it", context = TAG) }
.getOrNull()

if (isMainScanner && scan.isLightningRelated()) {
showSheet(Sheet.Send())
}

when (scan) {
is Scanner.OnChain -> onScanOnchain(scan.invoice, input)
is Scanner.Lightning -> onScanLightning(scan.invoice, input)
is Scanner.LnurlPay -> onScanLnurlPay(scan.data)
is Scanner.LnurlWithdraw -> onScanLnurlWithdraw(scan.data)
is Scanner.LnurlAuth -> onScanLnurlAuth(scan.data)
is Scanner.OnChain -> onScanOnchain(scan.invoice, input, fromMainScanner)
is Scanner.Lightning -> onScanLightning(scan.invoice, input, fromMainScanner)
is Scanner.LnurlPay -> onScanLnurlPay(scan.data, fromMainScanner)
is Scanner.LnurlWithdraw -> onScanLnurlWithdraw(scan.data, fromMainScanner)
is Scanner.LnurlAuth -> onScanLnurlAuth(scan.data, fromMainScanner)
is Scanner.LnurlChannel -> onScanLnurlChannel(scan.data)
is Scanner.NodeId -> onScanNodeId(scan)
is Scanner.Gift -> onScanGift(scan.code, scan.amount)
Expand All @@ -1369,7 +1366,11 @@ class AppViewModel @Inject constructor(
}

@Suppress("LongMethod", "CyclomaticComplexMethod", "ReturnCount")
private suspend fun onScanOnchain(invoice: OnChainInvoice, scanResult: String) {
private suspend fun onScanOnchain(
invoice: OnChainInvoice,
scanResult: String,
fromMainScanner: Boolean,
) {
val validatedAddress = runCatching { validateBitcoinAddress(invoice.address) }
.getOrElse {
hideSheet()
Expand Down Expand Up @@ -1416,14 +1417,11 @@ class AppViewModel @Inject constructor(
val quickPayHandled = handleQuickPayIfApplicable(
amountSats = lnAmountSats,
invoice = lnInvoice,
fromMainScanner = fromMainScanner,
)
if (quickPayHandled) return

if (isMainScanner) {
showSheet(Sheet.Send(SendRoute.Confirm))
} else {
setSendEffect(SendEffect.NavigateToConfirm)
}
navigateToSendRoute(fromMainScanner, SendRoute.Confirm, SendEffect.NavigateToConfirm)
refreshOnchainSendIfNeeded()
estimateLightningRoutingFeesIfNeeded()
return
Expand Down Expand Up @@ -1465,14 +1463,14 @@ class AppViewModel @Inject constructor(
context = TAG,
)

if (isMainScanner) {
showSheet(Sheet.Send(SendRoute.Amount))
} else {
setSendEffect(SendEffect.NavigateToAmount)
}
navigateToSendRoute(fromMainScanner, SendRoute.Amount, SendEffect.NavigateToAmount)
}

private suspend fun onScanLightning(invoice: LightningInvoice, scanResult: String) {
private suspend fun onScanLightning(
invoice: LightningInvoice,
scanResult: String,
fromMainScanner: Boolean,
) {
if (invoice.isExpired) {
hideSheet()
toast(
Expand All @@ -1484,7 +1482,11 @@ class AppViewModel @Inject constructor(
return
}

val quickPayHandled = handleQuickPayIfApplicable(amountSats = invoice.amountSatoshis, invoice = invoice)
val quickPayHandled = handleQuickPayIfApplicable(
amountSats = invoice.amountSatoshis,
invoice = invoice,
fromMainScanner = fromMainScanner,
)
if (quickPayHandled) return

lightningRepo.waitForUsableChannels()
Expand Down Expand Up @@ -1515,23 +1517,15 @@ class AppViewModel @Inject constructor(
if (invoice.amountSatoshis > 0uL) {
Logger.info("Found amount in invoice, proceeding with payment", context = TAG)

if (isMainScanner) {
showSheet(Sheet.Send(SendRoute.Confirm))
} else {
setSendEffect(SendEffect.NavigateToConfirm)
}
navigateToSendRoute(fromMainScanner, SendRoute.Confirm, SendEffect.NavigateToConfirm)
return
}
Logger.info("No amount found in invoice, proceeding to enter amount", context = TAG)

if (isMainScanner) {
showSheet(Sheet.Send(SendRoute.Amount))
} else {
setSendEffect(SendEffect.NavigateToAmount)
}
navigateToSendRoute(fromMainScanner, SendRoute.Amount, SendEffect.NavigateToAmount)
}

private suspend fun onScanLnurlPay(data: LnurlPayData) {
private suspend fun onScanLnurlPay(data: LnurlPayData, fromMainScanner: Boolean) {
Logger.debug("LNURL: $data", context = TAG)

val isFixed = data.isFixedAmount()
Expand Down Expand Up @@ -1561,26 +1555,22 @@ class AppViewModel @Inject constructor(
if (isFixed) {
Logger.info("Found fixed amount '$displaySats' sats in lnurlPay, proceeding with payment", context = TAG)

val quickPayHandled = handleQuickPayIfApplicable(amountSats = displaySats, lnurlPay = data)
val quickPayHandled = handleQuickPayIfApplicable(
amountSats = displaySats,
lnurlPay = data,
fromMainScanner = fromMainScanner,
)
if (quickPayHandled) return

if (isMainScanner) {
showSheet(Sheet.Send(SendRoute.Confirm))
} else {
setSendEffect(SendEffect.NavigateToConfirm)
}
navigateToSendRoute(fromMainScanner, SendRoute.Confirm, SendEffect.NavigateToConfirm)
return
}

Logger.info("No amount found in lnurlPay, proceeding to enter amount manually", context = TAG)
if (isMainScanner) {
showSheet(Sheet.Send(SendRoute.Amount))
} else {
setSendEffect(SendEffect.NavigateToAmount)
}
navigateToSendRoute(fromMainScanner, SendRoute.Amount, SendEffect.NavigateToAmount)
}

private suspend fun onScanLnurlWithdraw(data: LnurlWithdrawData) {
private suspend fun onScanLnurlWithdraw(data: LnurlWithdrawData, fromMainScanner: Boolean) {
Logger.debug("LNURL: $data", context = TAG)

val isFixed = data.isFixedAmount()
Expand Down Expand Up @@ -1609,30 +1599,39 @@ class AppViewModel @Inject constructor(

if (isFixed || minWithdrawable == maxWithdrawable) {
delay(TRANSITION_SCREEN_MS)
if (isMainScanner) {
showSheet(Sheet.Send(SendRoute.WithdrawConfirm))
} else {
setSendEffect(SendEffect.NavigateToWithdrawConfirm)
}
navigateToSendRoute(
fromMainScanner,
SendRoute.WithdrawConfirm,
SendEffect.NavigateToWithdrawConfirm,
)
return
}

if (isMainScanner) {
showSheet(Sheet.Send(SendRoute.Amount))
} else {
setSendEffect(SendEffect.NavigateToAmount)
}
navigateToSendRoute(fromMainScanner, SendRoute.Amount, SendEffect.NavigateToAmount)
}

private suspend fun onScanLnurlAuth(data: LnurlAuthData) {
private suspend fun onScanLnurlAuth(data: LnurlAuthData, fromMainScanner: Boolean) {
Logger.debug("LNURL: $data", context = TAG)
if (!isMainScanner) {
if (!fromMainScanner) {
hideSheet()
delay(TRANSITION_SCREEN_MS)
}
showSheet(Sheet.LnurlAuth(domain = data.domain, lnurl = data.uri, k1 = data.k1))
}

private fun navigateToSendRoute(
fromMainScanner: Boolean,
route: SendRoute,
effect: SendEffect,
) {
if (fromMainScanner) {
showSheet(Sheet.Send(route))
return
}

setSendEffect(effect)
}

fun requestLnurlAuth(callback: String, k1: String, domain: String) {
viewModelScope.launch {
lightningRepo.requestLnurlAuth(
Expand Down Expand Up @@ -1700,6 +1699,7 @@ class AppViewModel @Inject constructor(

private suspend fun handleQuickPayIfApplicable(
amountSats: ULong,
fromMainScanner: Boolean,
lnurlPay: LnurlPayData? = null,
invoice: LightningInvoice? = null,
): Boolean {
Expand Down Expand Up @@ -1733,11 +1733,7 @@ class AppViewModel @Inject constructor(

Logger.debug("QuickPayData: $quickPayData", context = TAG)

if (isMainScanner) {
showSheet(Sheet.Send(SendRoute.QuickPay))
} else {
setSendEffect(SendEffect.NavigateToQuickPay)
}
navigateToSendRoute(fromMainScanner, SendRoute.QuickPay, SendEffect.NavigateToQuickPay)
return true
}

Expand Down Expand Up @@ -2627,12 +2623,6 @@ class AppViewModel @Inject constructor(
}
}

private fun Scanner?.isLightningRelated(): Boolean = when (this) {
is Scanner.Lightning, is Scanner.LnurlPay -> true
is Scanner.OnChain -> invoice.params?.containsKey("lightning") == true
else -> false
}

// region send contract
@Stable
data class SendUiState(
Expand Down
Loading