feat(send): improve Lightning send failure recovery - #1140
Conversation
6a53759 to
873d26e
Compare
873d26e to
776366e
Compare
Greptile SummaryThe PR replaces toast-only Lightning send failures with a recoverable failure screen, localized failure reasons, support-report prefilling, and routing-cache resets for routing-related retries.
Confidence Score: 4/5The PR should not merge until pending QuickPay failures can retry without crashing from cleared payment state. The new Pending branch clears the only QuickPay request state, while the corresponding failure route explicitly returns to a destination that requires that state to be non-null. Files Needing Attention: app/src/main/java/to/bitkit/ui/screens/wallets/send/SendQuickPayScreen.kt, app/src/main/java/to/bitkit/ui/sheets/SendSheet.kt
|
| Filename | Overview |
|---|---|
| app/src/main/java/to/bitkit/ui/screens/wallets/send/SendQuickPayScreen.kt | Moves QuickPay cleanup to terminal results, but clearing state on Pending breaks the newly added pending-failure retry route. |
| app/src/main/java/to/bitkit/ui/sheets/SendSheet.kt | Adds typed failure navigation, support reporting, and routing-aware retry behavior; its QuickPay retry destination still requires state that the Pending path clears. |
| app/src/main/java/to/bitkit/repositories/LightningRepo.kt | Adds graph/scorer cache reset and bounded routing-data refresh orchestration without an independently established defect. |
| app/src/main/java/to/bitkit/viewmodels/AppViewModel.kt | Routes active Lightning failures into the send sheet and preserves typed failure details and payment requests. |
| app/src/main/java/to/bitkit/viewmodels/WalletViewModel.kt | Serializes routing-reset retries with a mutex and exposes retry progress to the UI. |
| app/src/main/java/to/bitkit/ext/PaymentFailureReasonExt.kt | Maps LDK failure reasons to localized user copy and sanitized compact support metadata. |
Sequence Diagram
sequenceDiagram
participant User
participant QuickPay as QuickPay Screen
participant AppVM as AppViewModel
participant Pending as Pending Screen
participant Error as Failure Screen
QuickPay->>AppVM: resetQuickPay()
QuickPay->>Pending: "navigate with retryRoute=QuickPay"
Pending-->>Error: payment failure
User->>Error: Try Again
Error->>QuickPay: clear stack and navigate
QuickPay->>AppVM: read quickPayData
AppVM-->>QuickPay: null
QuickPay--xQuickPay: requireNotNull crashes
Reviews (1): Last reviewed commit: "feat(send): improve Lightning send failu..." | Re-trigger Greptile
| val scope = rememberCoroutineScope() | ||
| SendErrorScreen( | ||
| title = stringResource(route.failureTitle(sendUiState.payMethod)), | ||
| message = route.message, | ||
| isRetrying = isRetrying, | ||
| onRetry = { | ||
| navController.navigateTo(SendRoute.Recipient) { | ||
| popUpTo(navController.graph.id) { inclusive = true } | ||
| if (isRetrying) return@SendErrorScreen | ||
| scope.launch { | ||
| val shouldResetRoutingCaches = route.shouldResetRoutingCaches( | ||
| routingCacheResetAttempted = routingCacheResetAttempted | ||
| ) | ||
| if (shouldResetRoutingCaches) routingCacheResetAttempted = true | ||
| val resetResult = if (shouldResetRoutingCaches) { | ||
| walletViewModel.resetPaymentRoutingCachesAndWait() | ||
| } else { | ||
| Result.success(Unit) | ||
| } |
There was a problem hiding this comment.
dismissing the error screen mid-reset may cancel the coroutine between node stop() and start(), leaving the node stopped
| val resetRoutingCachesOnRetry: Boolean = false, | ||
| val failureType: String = "Unknown", | ||
| val paymentRequest: String? = null, | ||
| val routingCacheResetAttempted: Boolean = false, |
| data class Failure(override val paymentHash: String) : Command | ||
| data class Failure( | ||
| override val paymentHash: String, | ||
| val reason: PaymentFailureReason? = null, |
| context = TAG, | ||
| ) | ||
|
|
||
| stop().onFailure { |
There was a problem hiding this comment.
the node stops here and I only see it starting again on the VSS-delete failure path worth double checking if the is some path that Kees the node on stopped state
| _sendUiState.update { | ||
| it.copy( | ||
| amount = amountSats, | ||
| decodedInvoice = invoice, |
There was a problem hiding this comment.
This will make it matches in closeActiveSendForFailedPayment and race with the similar logic of watchUntil in QuickPayVIewModel
| SendMethod.ONCHAIN -> { | ||
| val address = _sendUiState.value.address | ||
| val tags = _sendUiState.value.selectedTags | ||
| sendOnchain(address, amount, tags = tags) | ||
| .onSuccess { txId -> | ||
| Logger.info("Onchain send result txid: $txId", context = TAG) | ||
| onSendSuccess( | ||
| NewTransactionSheetDetails( | ||
| type = NewTransactionSheetType.ONCHAIN, | ||
| direction = NewTransactionSheetDirection.SENT, | ||
| paymentHashOrTxId = txId, | ||
| sats = amount.toLong(), | ||
| isLoadingDetails = true, | ||
| ) | ||
| ) | ||
| lightningRepo.sync() | ||
| activityRepo.syncActivities() | ||
| _successSendUiState.update { it.copy(isLoadingDetails = false) } | ||
| }.onFailure { e -> | ||
| Logger.error("Error sending onchain payment", e, context = TAG) | ||
| toast( | ||
| type = Toast.ToastType.ERROR, |
There was a problem hiding this comment.
The SendMethod.ONCHAIN still do Toast + hideSheet()
| onBack: () -> Unit, | ||
| navigateResultScreen: (Boolean) -> Unit, | ||
| ) { | ||
| LaunchedEffect(prefillMessage) { |
There was a problem hiding this comment.
This re-runs on every composition recreation while the ViewModel survives
Description
Ports the iOS Lightning send-failure retry/support behavior to Android.
Optional(...),NodeError, orDuplicatePayment.routeNotFoundandretriesExhausted.SendPendingScreenand fail later.WalletViewModelso overlapping retry flows cannot surface stale timeout errors.Closes #829
Preview
Normal payment:
Screen.Recording.2026-08-11.at.17.59.27.mov
QuickPay:
Screen.Recording.2026-08-12.at.14.55.13.mov
QA Notes
Tested on mainnet emulator:
ROUTE_NOT_FOUNDsurfaces user-facing payment failure copy.RETRIES_EXHAUSTEDfailures surface the localized retries-exhausted copy instead of generic failure copy.Automated checks:
compileDevDebugKotlinPaymentFailureReasonExtTest.ktAppViewModelSendFlowTest.ktdetekt