diff --git a/app/src/main/java/to/bitkit/domain/commands/NotifyPaymentReceivedHandler.kt b/app/src/main/java/to/bitkit/domain/commands/NotifyPaymentReceivedHandler.kt index 1992e08cd..629b84842 100644 --- a/app/src/main/java/to/bitkit/domain/commands/NotifyPaymentReceivedHandler.kt +++ b/app/src/main/java/to/bitkit/domain/commands/NotifyPaymentReceivedHandler.kt @@ -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 diff --git a/app/src/main/java/to/bitkit/fcm/WakeNodeWorker.kt b/app/src/main/java/to/bitkit/fcm/WakeNodeWorker.kt index fe9a86efc..6572848a8 100644 --- a/app/src/main/java/to/bitkit/fcm/WakeNodeWorker.kt +++ b/app/src/main/java/to/bitkit/fcm/WakeNodeWorker.kt @@ -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 @@ -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 diff --git a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt index 1d3eb6b40..9d428da52 100644 --- a/app/src/main/java/to/bitkit/repositories/LightningRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/LightningRepo.kt @@ -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) } diff --git a/app/src/main/java/to/bitkit/repositories/WalletRepo.kt b/app/src/main/java/to/bitkit/repositories/WalletRepo.kt index 199e2ff3e..dde2ee0a4 100644 --- a/app/src/main/java/to/bitkit/repositories/WalletRepo.kt +++ b/app/src/main/java/to/bitkit/repositories/WalletRepo.kt @@ -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 diff --git a/app/src/main/java/to/bitkit/services/CoreService.kt b/app/src/main/java/to/bitkit/services/CoreService.kt index 4349eced4..a5f8dc387 100644 --- a/app/src/main/java/to/bitkit/services/CoreService.kt +++ b/app/src/main/java/to/bitkit/services/CoreService.kt @@ -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 @@ -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) diff --git a/app/src/main/java/to/bitkit/ui/NodeInfoScreen.kt b/app/src/main/java/to/bitkit/ui/NodeInfoScreen.kt index 1b6f70840..43b87cd91 100644 --- a/app/src/main/java/to/bitkit/ui/NodeInfoScreen.kt +++ b/app/src/main/java/to/bitkit/ui/NodeInfoScreen.kt @@ -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 diff --git a/app/src/main/java/to/bitkit/ui/settings/lightning/ChannelDetailScreen.kt b/app/src/main/java/to/bitkit/ui/settings/lightning/ChannelDetailScreen.kt index 0b93407ac..a41243741 100644 --- a/app/src/main/java/to/bitkit/ui/settings/lightning/ChannelDetailScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/lightning/ChannelDetailScreen.kt @@ -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 diff --git a/app/src/main/java/to/bitkit/ui/settings/lightning/LightningConnectionsScreen.kt b/app/src/main/java/to/bitkit/ui/settings/lightning/LightningConnectionsScreen.kt index 89f42eb0f..4c1913954 100644 --- a/app/src/main/java/to/bitkit/ui/settings/lightning/LightningConnectionsScreen.kt +++ b/app/src/main/java/to/bitkit/ui/settings/lightning/LightningConnectionsScreen.kt @@ -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 diff --git a/app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt b/app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt index 0152e82a3..7e9e950a5 100644 --- a/app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt +++ b/app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt @@ -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 @@ -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) @@ -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() @@ -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 @@ -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( @@ -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() @@ -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() @@ -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() @@ -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( @@ -1700,6 +1699,7 @@ class AppViewModel @Inject constructor( private suspend fun handleQuickPayIfApplicable( amountSats: ULong, + fromMainScanner: Boolean, lnurlPay: LnurlPayData? = null, invoice: LightningInvoice? = null, ): Boolean { @@ -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 } @@ -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(