Skip to content

BUG: LNURL-Withdraw Success Feedback Unreliable - Spinner Continues After Successful Redemption Description #3564

@pretyflaco

Description

@pretyflaco

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:

  1. The spinner should stop
  2. A success checkmark should appear
  3. 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 === lastHash

The 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 PAID event 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):

  1. The LNURL callback returns {status: "OK"}
  2. 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 || lnurlCallbackSuccess

Recommended 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.tsx
  • app/screens/receive-bitcoin-screen/my-ln-updates-sub.tsx
  • app/graphql/ln-update-context.ts

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions