Skip to content

fix: await ln payment before gift card confetti#929

Draft
jvsena42 wants to merge 5 commits intomasterfrom
fix/gift-card-falsse-positive
Draft

fix: await ln payment before gift card confetti#929
jvsena42 wants to merge 5 commits intomasterfrom
fix/gift-card-falsse-positive

Conversation

@jvsena42
Copy link
Copy Markdown
Member

@jvsena42 jvsena42 commented May 5, 2026

This PR:

  1. Fixes a false-positive confetti animation in the gift card redemption flow when the user already has an open Lightning channel.
  2. Fixes the gift sheet re-opening after an app language change re-delivers the launching intent.

Description

1. False-positive confetti

BlocktankRepo.claimGiftCodeWithLiquidity previously returned success as soon as Blocktank's giftPay HTTP RPC returned — but that response only confirms the LSP accepted the request, not that the LN payment actually landed. If Blocktank's background payer subsequently failed to route the HTLC, Bitkit still played confetti and surfaced a "received" sheet with the requested amount, while no incoming payment ever arrived.

The repo now subscribes to LightningRepo.nodeEvents for the Event.PaymentReceived matching the freshly-created invoice's payment hash before triggering giftPay, then awaits that event with a 45 second timeout. On timeout it throws ServiceError.GiftClaimPaymentNotReceived, which the existing error handler in GiftViewModel routes to the standard GiftRoute.Error screen. The success result reports the actual received sat amount instead of the requested amount.

The without-liquidity path (channel open + openChannel) is unchanged — that path already blocks on the funding transaction.

2. Intent re-delivery on Activity recreate

When the app language is changed, Android destroys and recreates the Activity, re-delivering the original launching intent. MainActivity.onCreate was unconditionally calling handleDeeplinkIntent(intent), so any deeplink-driven flow (gift sheet, send sheet, pubky auth, etc.) re-fired on the locale switch.

onCreate now only processes the launching intent on a fresh start (savedInstanceState == null). New deeplinks that arrive while the app is running are still handled via onNewIntent.

Preview

N/A

QA Notes

Manual Tests

  • 1. Existing channel with inbound capacity → scan a gift code in a state where Blocktank accepts the request but its background LN payer cannot route (see "Reproducing the failure" below): Loading shown up to ~45s → GiftErrorSheet appears. No confetti, no NewTransactionSheet, no new activity entry, balance unchanged.
  • 2. Existing channel with inbound capacity → scan a known-good gift code: Loading shown briefly → confetti + NewTransactionSheet fires once with the actual received amount → single new activity entry. Balance increases by the received amount.
  • 3. regression: No inbound capacity → scan a gift code: existing channel-open flow runs (openChannel + ChannelReady), gift activity inserted, no regression vs current behavior.
  • 4. regression: Code already redeemed → "used" sheet still appears. Code with count exhausted → "used up" sheet still appears. (GIFT_CODE_ALREADY_USED and GIFT_CODE_USED_UP paths unchanged.)
  • 5. Pull adb logs for case 1 and confirm Failed to claim gift code is logged with GiftClaimPaymentNotReceived. For case 2 confirm Event.PaymentReceived is logged before Gift payment confirmed by LDK and Gift claim successful: SuccessWithLiquidity.
  • 6. Scan a gift QR (or fire the bitkit://gift-… deeplink) → on the gift sheet, change the app language via Settings → General → Language. The gift sheet should not re-open after the language switch. Same check applies to other deeplink-driven sheets (send / pubky auth) launched the same way.

Reproducing the failure path

The originally-reported gUCdzXfm12V71xztGzDoiQ-3000 is now exhausted and returns GIFT_CODE_USED_UP (a separate, already-handled path), so it can no longer reproduce the false-positive. To exercise case 1, use one of:

  • Coordinate with the Blocktank team to provide a code whose LSP-side LN payer is in an unpayable state (accepts the HTTP request but cannot route the HTLC).
  • Issue a valid code via the regtest blocktank harness, then disconnect the LSP peer / cut network between scanning the QR and the HTLC settling, so the inbound HTLC never arrives. giftPay returns success but Event.PaymentReceived never fires.

To make the wifi-toggle approach reliable, debug builds include a GIFT_QA_PRE_RECEIVE_DELAY constant in BlocktankRepo (default 15.seconds) that pauses after giftPay returns and before await PaymentReceived. Use the window to disable wifi (or kill the LSP peer) and force a routing failure. Set the constant to Duration.ZERO to skip the pause. The pause only fires when Env.isDebug is true.

Regtest helpers

Fire the gift deeplink directly without scanning a QR (replace the package id per flavor — to.bitkit.dev for dev/regtest, to.bitkit.tnet for testnet, to.bitkit for mainnet):

adb shell am start -a android.intent.action.VIEW -d "bitkit://gift-gUCdzXfm12V71xztGzDoiQ-3000" to.bitkit.dev

Tail the relevant logs while reproducing:

adb logcat -v time | grep -E "GiftViewModel|BlocktankRepo|LightningService|PaymentReceived"

Or pull the on-disk log file after the attempt:

adb shell "run-as to.bitkit.dev ls -t files/logs/ | head -1"
adb shell "run-as to.bitkit.dev cat files/logs/<filename>" > bitkit.log

@jvsena42 jvsena42 self-assigned this May 5, 2026
@jvsena42
Copy link
Copy Markdown
Member Author

jvsena42 commented May 5, 2026

Looking for a reliable way to reproduce it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant