-
Notifications
You must be signed in to change notification settings - Fork 142
Description
Description
When redeeming an LNURL-withdraw voucher, the success checkmark often fails to appear even after the voucher has been successfully redeemed and sats are reflected in the account balance. The UI remains stuck on the spinner indefinitely, forcing users to manually navigate away.
Expected Behavior
After a successful LNURL-withdraw redemption:
- The spinner should stop
- A success checkmark should appear
- User should receive clear confirmation that the voucher was redeemed
Actual Behavior
- Sometimes the checkmark appears correctly
- Most of the time, the spinner continues indefinitely
- User has to manually check their balance to confirm the redemption worked
- No success feedback is shown despite funds being received
Root Cause Analysis
The issue is in how redeem-bitcoin-result-screen.tsx detects payment success. Currently, success is only detected via a WebSocket subscription:
// redeem-bitcoin-result-screen.tsx:74
const invoicePaid = withdrawalInvoice?.paymentHash === lastHashThe lastHash value comes from the useLnUpdateHashPaid() hook, which relies on the myUpdates GraphQL subscription in my-ln-updates-sub.tsx:
const { data: dataSub } = useMyLnUpdatesSubscription()
React.useEffect(() => {
if (dataSub?.myUpdates?.update?.__typename === "LnUpdate") {
const update = dataSub.myUpdates.update
if (update.status === "PAID") {
setLastHash(update.paymentHash)
}
}
}, [dataSub, client])The problem: If the WebSocket subscription:
- Disconnects during the payment flow
- Has latency or reconnection issues
- Misses the
PAIDevent due to timing
...the UI will never show success, even though the payment completed successfully.
Unused Success Signal
Notably, the code already receives a success signal from the LNURL service but doesn't use it for UI feedback:
// redeem-bitcoin-result-screen.tsx:127-135
if (result.ok) {
const lnurlResponse = await result.json()
if (lnurlResponse?.status?.toLowerCase() !== "ok") {
// Only used for ERROR detection
setErr(LL.RedeemBitcoinScreen.redeemingError())
}
// SUCCESS case is completely ignored for UI purposes!
}When the LNURL service returns {status: "OK"}, this confirms the withdraw request was accepted and the invoice will be paid. This signal is currently ignored.
Suggested Solution
Show success on whichever signal arrives first
The fix should be straightforward: show the success checkmark when either of these events occurs (whichever comes first):
- The LNURL callback returns
{status: "OK"} - The WebSocket subscription confirms the invoice was paid
Both signals indicate the withdrawal succeeded. By treating either as sufficient for showing success, we eliminate the unreliable dependency on WebSocket alone.
Implementation approach
const [lnurlCallbackSuccess, setLnurlCallbackSuccess] = useState(false)
// In submitLNURLWithdrawRequest:
if (lnurlResponse?.status?.toLowerCase() === "ok") {
setLnurlCallbackSuccess(true)
}
// Success condition becomes:
const showSuccess = invoicePaid || lnurlCallbackSuccessRecommended UI copy
When either success signal is received, show:
"Withdrawal successful"
With the success checkmark animation. No need to distinguish between the two signals—users don't need to know the technical difference.
Optional: Add polling fallback
As a belt-and-suspenders approach, if neither signal arrives within N seconds, poll the invoice status via a query. This provides a third path to success detection.
Environment
- Affects both iOS and Android
- Occurs with various LNURL-withdraw providers
- More frequent on unstable network connections
Related Code
app/screens/redeem-lnurl-withdrawal-screen/redeem-bitcoin-result-screen.tsxapp/screens/receive-bitcoin-screen/my-ln-updates-sub.tsxapp/graphql/ln-update-context.ts