Skip to content

frontend: enable top-up flow.#4166

Draft
bznein wants to merge 1 commit into
BitBoxSwiss:staging-sparkfrom
bznein:top-up-2
Draft

frontend: enable top-up flow.#4166
bznein wants to merge 1 commit into
BitBoxSwiss:staging-sparkfrom
bznein:top-up-2

Conversation

@bznein

@bznein bznein commented May 26, 2026

Copy link
Copy Markdown
Collaborator

Allow users to top up their lightning wallet directly in the app, by adding a new page that allows a user to select a source BTC account, an amount, and create the tx.

Before asking for reviews, here is a check list of the most common things you might need to consider:

  • updating the Changelog
  • writing unit tests
  • checking if your changes affect other coins or tokens in unintended ways
  • testing on multiple environments (Qt, Android, ...)
  • having an AI review your changes

@bznein

bznein commented May 26, 2026

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented May 26, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds a Lightning wallet top-up flow to the frontend. It introduces a new /lightning/topup route with a multi-step LightningTopUp component that loads Lightning balance, selects a BTC account, proposes a transaction, confirms it, and submits it. A reusable SendAbortedResult component is extracted for aborted states. Action buttons gain a top-up link and shared CSS updates. The Lightning page no longer renders the boarding address. New English localization strings, async-loading safeguards, fee-target reload handling, and a backend test cleanup are included.

Changes

Related issues: None provided.
Related PRs: None provided.
Suggested labels: frontend, feature, lightning
Suggested reviewers: benma, Beerosagos

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant TopUpForm
  participant LightningTopUp
  participant accountApi
  participant TopUpConfirm
  participant Keystore

  User->>TopUpForm: enter amount and select source account
  TopUpForm->>LightningTopUp: onReview()
  LightningTopUp->>accountApi: proposeTx(...)
  accountApi-->>LightningTopUp: proposal result
  LightningTopUp->>TopUpConfirm: render proposal
  User->>TopUpConfirm: confirm
  LightningTopUp->>Keystore: connectKeystore(...)
  LightningTopUp->>accountApi: sendTx(...)
  accountApi-->>LightningTopUp: success, aborted, or error
Loading

Poem
A rabbit hops to top up sats,
No boarding address, just clean formats.
Confirm, propose, and send with care,
Aborted paths now handled fair.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/lightning/topup.go`:
- Around line 92-99: The function getTopUpSourceAccount must guard against a nil
sourceAccount returned by getAccountFromCode before calling
sourceAccount.Coin(); add an explicit nil check after calling getAccountFromCode
(and return a suitable error, e.g., accountErrors.ErrInvalidAddress or wrap the
original err when err==nil) so you never dereference sourceAccount.Coin() on a
nil pointer; update the getTopUpSourceAccount control flow to handle
(sourceAccount == nil) and then proceed to compare sourceAccount.Coin().Code()
with coin.CodeBTC.

In `@frontends/web/src/routes/lightning/topup/topup.tsx`:
- Around line 365-398: The handleReview flow allows concurrent postTopUpSend
calls; add a local "isSubmitting" (or reuse state like "step") guard to return
early if a send is already in flight, set it true immediately before calling
postTopUpSend and set it false in all exit paths (catch, success, aborted, and
error branches) so duplicate clicks are ignored; update the UI trigger (the
button that calls handleReview) to be disabled when this flag is true. Apply the
same guard and state-reset logic to the other send handler at the second
occurrence (the block referenced at lines 514-518) so both send flows prevent
duplicate submissions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 14024729-8a19-4680-bba3-3275521ac0f3

📥 Commits

Reviewing files that changed from the base of the PR and between 2536dc6 and b5f7a5c.

📒 Files selected for processing (15)
  • backend/handlers/handlers.go
  • backend/lightning/handlers.go
  • backend/lightning/lightning.go
  • backend/lightning/topup.go
  • backend/lightning/topup_test.go
  • frontends/web/src/api/lightning.ts
  • frontends/web/src/components/dropdown/dropdown.tsx
  • frontends/web/src/locales/en/app.json
  • frontends/web/src/routes/account/send/feetargets.tsx
  • frontends/web/src/routes/lightning/components/action-buttons.module.css
  • frontends/web/src/routes/lightning/components/action-buttons.tsx
  • frontends/web/src/routes/lightning/lightning.tsx
  • frontends/web/src/routes/lightning/topup/topup.module.css
  • frontends/web/src/routes/lightning/topup/topup.tsx
  • frontends/web/src/routes/router.tsx
💤 Files with no reviewable changes (1)
  • frontends/web/src/routes/lightning/lightning.tsx

Comment thread backend/lightning/topup.go Outdated
Comment thread frontends/web/src/routes/lightning/topup/topup.tsx
@bznein bznein force-pushed the top-up-2 branch 4 times, most recently from 853b432 to 76f2799 Compare May 26, 2026 10:12
@bznein bznein requested a review from Beerosagos May 26, 2026 10:40
@bznein bznein marked this pull request as ready for review May 26, 2026 10:40
@bznein bznein requested a review from a team as a code owner May 26, 2026 10:40
@bznein

bznein commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/lightning/topup_test.go (1)

1-137: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider adding test coverage for Deactivate clearing the boarding address.

While the existing tests cover the main top-up flows, there's no test verifying that Lightning.Deactivate() clears the cached boarding address (via clearTopUpBoardingAddress() at lightning.go:146).

Adding a test would ensure this cleanup behavior remains correct across refactorings.

🧪 Suggested test
+func TestDeactivateClearsBoardingAddress(t *testing.T) {
+	lightning := makeTestLightning()
+	lightning.topUpBoardingAddress = "bc1qtopupaddress"
+	
+	// Assume Deactivate is callable in test context
+	// You may need to adjust based on test setup requirements
+	err := lightning.Deactivate()
+	
+	require.NoError(t, err)
+	require.Empty(t, lightning.topUpBoardingAddress)
+}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/lightning/topup_test.go` around lines 1 - 137, Add a unit test that
verifies Lightning.Deactivate() clears the cached boarding address: create a
Lightning via makeTestLightning(), set lightning.topUpBoardingAddress to a
non-empty value, call lightning.Deactivate(), then assert
lightning.topUpBoardingAddress is empty (this exercises
clearTopUpBoardingAddress()). Place the test alongside existing topup tests and
use require to fail on unexpected results.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/lightning/topup.go`:
- Around line 160-174: The cached topUpBoardingAddress is intentionally
session-scoped and not cleared by topUpSend; update the code comments and/or
function docs to state that topUpBoardingAddress is reused across multiple
top-ups until explicitly cleared by Lightning.Deactivate (and not by topUpSend
or clearTopUpBoardingAddress on success), referencing the topUpSend function,
the clearTopUpBoardingAddress helper, Lightning.Deactivate, and the existing
tests TestTopUpSendKeepsCachedBoardingAddress and
TestTopUpProposalUsesCachedBoardingAddress to make the intended lifecycle
explicit.

---

Outside diff comments:
In `@backend/lightning/topup_test.go`:
- Around line 1-137: Add a unit test that verifies Lightning.Deactivate() clears
the cached boarding address: create a Lightning via makeTestLightning(), set
lightning.topUpBoardingAddress to a non-empty value, call
lightning.Deactivate(), then assert lightning.topUpBoardingAddress is empty
(this exercises clearTopUpBoardingAddress()). Place the test alongside existing
topup tests and use require to fail on unexpected results.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 188da02e-427a-4c98-b068-809b85d38ecb

📥 Commits

Reviewing files that changed from the base of the PR and between b5f7a5c and b22e6de.

📒 Files selected for processing (15)
  • backend/handlers/handlers.go
  • backend/lightning/handlers.go
  • backend/lightning/lightning.go
  • backend/lightning/topup.go
  • backend/lightning/topup_test.go
  • frontends/web/src/api/lightning.ts
  • frontends/web/src/components/dropdown/dropdown.tsx
  • frontends/web/src/locales/en/app.json
  • frontends/web/src/routes/account/send/components/result.tsx
  • frontends/web/src/routes/lightning/components/action-buttons.module.css
  • frontends/web/src/routes/lightning/components/action-buttons.tsx
  • frontends/web/src/routes/lightning/lightning.tsx
  • frontends/web/src/routes/lightning/topup/topup.module.css
  • frontends/web/src/routes/lightning/topup/topup.tsx
  • frontends/web/src/routes/router.tsx

Comment thread backend/lightning/topup.go Outdated
@bznein bznein force-pushed the top-up-2 branch 6 times, most recently from 9f7b713 to c8e5ae0 Compare June 16, 2026 11:29
@bznein

bznein commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/lightning/topup_test.go`:
- Line 20: In the test setup for topup_test.go where RateUpdater is being
initialized, replace the hardcoded string "/dev/null" with os.DevNull to ensure
cross-platform compatibility. The os.DevNull constant automatically resolves to
the correct null device path for the operating system the tests are running on
(e.g., "/dev/null" on Unix-like systems and "nul" on Windows). This change will
prevent test failures on Windows runners.

In `@frontends/web/src/routes/lightning/topup/topup.tsx`:
- Around line 293-319: The connectKeystore() call should be extracted outside of
the try/catch block to follow the early-return pattern outlined in the coding
guidelines. First, call connectKeystore() and immediately return early if it
fails (checking connectResult.success). After this early-return guard, then
establish the try/catch block that wraps only the postTopUpSend() call and its
result handling (including the setStep() calls for 'confirming', 'success',
'aborted', 'form' states and setSendError() calls). This way, connectKeystore()
failures use simple early-return logic while postTopUpSend() failures are
handled by the catch block.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1f26ba0c-57c4-4fd6-abc2-54c842ad874f

📥 Commits

Reviewing files that changed from the base of the PR and between b22e6de and c8e5ae0.

📒 Files selected for processing (19)
  • backend/accounts/tx_responses.go
  • backend/coins/btc/handlers/handlers.go
  • backend/handlers/handlers.go
  • backend/lightning/handlers.go
  • backend/lightning/lightning.go
  • backend/lightning/topup.go
  • backend/lightning/topup_test.go
  • frontends/web/src/api/lightning.ts
  • frontends/web/src/locales/en/app.json
  • frontends/web/src/routes/account/send/components/result.tsx
  • frontends/web/src/routes/lightning/components/action-buttons.module.css
  • frontends/web/src/routes/lightning/components/action-buttons.tsx
  • frontends/web/src/routes/lightning/lightning.tsx
  • frontends/web/src/routes/lightning/topup/topup-confirm.tsx
  • frontends/web/src/routes/lightning/topup/topup-form.tsx
  • frontends/web/src/routes/lightning/topup/topup-result.tsx
  • frontends/web/src/routes/lightning/topup/topup.module.css
  • frontends/web/src/routes/lightning/topup/topup.tsx
  • frontends/web/src/routes/router.tsx
💤 Files with no reviewable changes (1)
  • frontends/web/src/routes/lightning/lightning.tsx

Comment thread backend/lightning/topup_test.go Outdated

func topUpTestAccountConfig() *accounts.AccountConfig {
return &accounts.AccountConfig{
RateUpdater: rates.NewRateUpdater(nil, "/dev/null"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use a cross-platform null device in test setup.

Line 20 hardcodes "/dev/null", which is Unix-specific and can break go test on Windows runners. Use os.DevNull instead.

Proposed fix
 import (
+	"os"
 	"testing"
@@
 func topUpTestAccountConfig() *accounts.AccountConfig {
 	return &accounts.AccountConfig{
-		RateUpdater: rates.NewRateUpdater(nil, "/dev/null"),
+		RateUpdater: rates.NewRateUpdater(nil, os.DevNull),
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/lightning/topup_test.go` at line 20, In the test setup for
topup_test.go where RateUpdater is being initialized, replace the hardcoded
string "/dev/null" with os.DevNull to ensure cross-platform compatibility. The
os.DevNull constant automatically resolves to the correct null device path for
the operating system the tests are running on (e.g., "/dev/null" on Unix-like
systems and "nul" on Windows). This change will prevent test failures on Windows
runners.

Comment thread frontends/web/src/routes/lightning/topup/topup.tsx Outdated
@bznein

bznein commented Jun 16, 2026

Copy link
Copy Markdown
Collaborator Author

@Beerosagos this is ready for review; I know the PR is quite big but it's not really easy to slim it down even more

@Beerosagos

Copy link
Copy Markdown
Collaborator

Did a quick test and found a couple of bugs:

  1. In this screen, the Manage Accounts button doesn't seem to work correctly
image
  1. When connecting the keystore while in the top up screen, the account balance isn't loaded
image

I wonder if it wouldn't be simpler to grey out the Top up button if no keystore is connected. wdyt?

cc @jadzeidan

@Beerosagos Beerosagos left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should show the incoming payment in the transaction list if possible.
Can you have a look at this? https://sdk-doc-spark.breez.technology/guide/receive_payment.html#bitcoin

We should probably manage the right events to handle incoming top ups
Image

{t('generic.send')}
</span>
)}
<Link key="topUp" to={'/lightning/topup'} className={style.topUp}>

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should go after receive

@Beerosagos

Copy link
Copy Markdown
Collaborator

cc @thisconnect or @shonsirsha for frontend code 😇

@bznein bznein marked this pull request as draft July 1, 2026 08:48
@bznein

bznein commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator Author

I moved this back to draft for a bit to:

  1. figure out a few things based on some recent changes I made
  2. Address review comment
  3. Look at frontend: enable top-up flow. #4166 (review)

Will ping people when ready for review :)

@jadzeidan

Copy link
Copy Markdown
Contributor

I wonder if it wouldn't be simpler to grey out the Top up button if no keystore is connected. wdyt?

@Beerosagos a grey button is simpler but imo not ideal since you don't know why it is greyed out. I think it makes sense to keep as is but change the message "No Bitcoin accounts active. Please activate a Bitcoin account to top-up." With a CTA to manage accounts.

@Beerosagos

Copy link
Copy Markdown
Collaborator

I think we should show the incoming payment in the transaction list if possible.

btw we can address this in a separate PR as well. Don't want to blow things up too much 🙏

@bznein

bznein commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator Author

I wonder if it wouldn't be simpler to grey out the Top up button if no keystore is connected. wdyt?

@Beerosagos a grey button is simpler but imo not ideal since you don't know why it is greyed out. I think it makes sense to keep as is but change the message "No Bitcoin accounts active. Please activate a Bitcoin account to top-up." With a CTA to manage accounts.

The CTA to manage accounts only works if there are no bitcoin accounts active (but a keystore connected); in other cases we should either prompt the user to connect or simply redirect to the homepage where we already tell users to connect the BitBox

@bznein

bznein commented Jul 1, 2026

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontends/web/src/routes/lightning/topup/topup.tsx`:
- Around line 343-344: The empty-state CTA in TopUp is driven by the coarse
hasAccounts flag, which can send disconnected users with existing accounts into
the broken manage-accounts flow. Update the TopUp render path that returns
TopUpNoBitcoinAccounts to pass connection state (or determine the CTA inside
TopUp) using the existing connection/keystore signals so disconnected users are
routed through the shared connect flow instead of /settings/manage-accounts.
- Around line 30-44: The Lightning balance fetch in the top-up screen only runs
once on mount, so a failed initial `getLightningBalance()` call leaves the
balance stuck as undefined after the user connects. Update the `useEffect` in
`topup.tsx` to re-run when the keystore connection state changes, or trigger the
fetch from `connectKeystore()` after a successful connect, so `setBalance` gets
called again once `mounted.current` is still true.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0db3ff69-39d3-45f3-90fc-59dc46fbda25

📥 Commits

Reviewing files that changed from the base of the PR and between c8e5ae0 and d51bb86.

📒 Files selected for processing (12)
  • backend/lightning/payments_test.go
  • frontends/web/src/locales/en/app.json
  • frontends/web/src/routes/account/send/components/result.tsx
  • frontends/web/src/routes/lightning/components/action-buttons.module.css
  • frontends/web/src/routes/lightning/components/action-buttons.tsx
  • frontends/web/src/routes/lightning/lightning.tsx
  • frontends/web/src/routes/lightning/topup/topup-confirm.tsx
  • frontends/web/src/routes/lightning/topup/topup-form.tsx
  • frontends/web/src/routes/lightning/topup/topup-result.tsx
  • frontends/web/src/routes/lightning/topup/topup.module.css
  • frontends/web/src/routes/lightning/topup/topup.tsx
  • frontends/web/src/routes/router.tsx

Comment thread frontends/web/src/routes/lightning/topup/topup.tsx Outdated
Comment thread frontends/web/src/routes/lightning/topup/topup.tsx
@shonsirsha shonsirsha self-requested a review July 2, 2026 06:16

@shonsirsha shonsirsha left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see that it's still in draft, but just commenting what Im seeing rn:)

setFiatAmount('');
return;
}
const data = await convertToCurrency({

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm..

could we somehow ignore stale fiat conversion responses here?

Feels like an edge case but im thinking something like this can happen since its all async

Type A => request A starts.
Type B => request B starts.
B returns first => UI is correct for B.
A returns later => stale A overwrites B. UI is wrong.

{sourceAccount && (
<Column>
<FeeTargets
accountCode={sourceAccount.code}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FeeTargets calls useLoad w/o accountCode in the deps - so it only loads for the first account that it sees.

Here tho, I see that we're using GroupedAccountSelector to change the source account.

Pretty sure it'll re-render FeeTargets on account change, but that useLoad will still have stale value, I think. Because the useEffect inside it doesn't rerun unless deps change.

@bznein bznein force-pushed the top-up-2 branch 2 times, most recently from 52427d8 to 2615b2f Compare July 6, 2026 09:30
@bznein bznein requested a review from shonsirsha July 6, 2026 09:31
@bznein

bznein commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator Author

@CodeRabbit review

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontends/web/src/hooks/api.ts (1)

57-69: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Stale-request guard looks correct, but rejections are still unhandled.

apiCall().then(...) has no .catch. If the call rejects, setResponse is never called, so the hook stays stuck at undefined (perpetual loading) and the error is silently swallowed as an unhandled rejection — callers get no error feedback at all.

Based on learnings, custom hooks performing API calls should surface errors via structured state (e.g. { data, error }) rather than letting failures disappear silently.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontends/web/src/hooks/api.ts` around lines 57 - 69, The `load` flow in
`useApi` handles only the success path, so rejected `apiCall()` promises are
swallowed and leave the hook stuck with no error state. Update the hook to
surface failures through explicit structured state alongside the existing
response handling (for example, add an error state near `request`/`mounted` and
clear it before each call). In the `apiCall().then(...)` chain, add rejection
handling that checks the same stale-request guard (`currentRequest` and
`mounted.current`) before storing the error, so callers can render both success
and failure states instead of relying on an unhandled rejection.

Source: Learnings

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontends/web/src/routes/lightning/topup/topup.tsx`:
- Around line 323-345: Add the same mounted.current post-await guard used
elsewhere in this component to the handleReview async flow so state updates only
run while the component is still mounted. In the sendTx() success, aborted,
error, and finally paths, check mounted.current before calling setStep,
setSendError, or setIsSubmitting, and keep the existing isSubmittingRef reset
behavior intact. Use the handleReview function and the surrounding async
state-update pattern in topup.tsx as the reference points.

---

Outside diff comments:
In `@frontends/web/src/hooks/api.ts`:
- Around line 57-69: The `load` flow in `useApi` handles only the success path,
so rejected `apiCall()` promises are swallowed and leave the hook stuck with no
error state. Update the hook to surface failures through explicit structured
state alongside the existing response handling (for example, add an error state
near `request`/`mounted` and clear it before each call). In the
`apiCall().then(...)` chain, add rejection handling that checks the same
stale-request guard (`currentRequest` and `mounted.current`) before storing the
error, so callers can render both success and failure states instead of relying
on an unhandled rejection.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: f3bc9871-3ca5-4656-877c-5192f6f94b79

📥 Commits

Reviewing files that changed from the base of the PR and between d51bb86 and 2615b2f.

📒 Files selected for processing (16)
  • backend/lightning/payments_test.go
  • frontends/web/src/hooks/api.test.ts
  • frontends/web/src/hooks/api.ts
  • frontends/web/src/locales/en/app.json
  • frontends/web/src/routes/account/send/components/result.tsx
  • frontends/web/src/routes/account/send/feetargets.test.tsx
  • frontends/web/src/routes/account/send/feetargets.tsx
  • frontends/web/src/routes/lightning/components/action-buttons.module.css
  • frontends/web/src/routes/lightning/components/action-buttons.tsx
  • frontends/web/src/routes/lightning/lightning.tsx
  • frontends/web/src/routes/lightning/topup/topup-confirm.tsx
  • frontends/web/src/routes/lightning/topup/topup-form.tsx
  • frontends/web/src/routes/lightning/topup/topup-result.tsx
  • frontends/web/src/routes/lightning/topup/topup.module.css
  • frontends/web/src/routes/lightning/topup/topup.tsx
  • frontends/web/src/routes/router.tsx

Comment on lines +323 to +345
try {
setStep('confirming');
setSendError(undefined);
const result = await accountApi.sendTx(sourceAccount.code, note);
if (result.success) {
setStep('success');
return;
}

if ('aborted' in result) {
setStep('aborted');
return;
}
setStep('form');
setSendError(result.errorCode ? t(`send.error.${result.errorCode}`) : result.errorMessage || t('genericError'));
} catch (error) {
setStep('form');
setSendError(String(error));
} finally {
isSubmittingRef.current = false;
setIsSubmitting(false);
}
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Add mounted.current guard to post-await state updates in handleReview.

Every other async flow in this file (convertToFiat, convertFromFiat, validateAndDisplayFee, useLightningBalance) guards state updates with mounted.current after an await, but this block does not. If the user navigates away while sendTx() is in flight, setStep/setSendError/setIsSubmitting will fire on an unmounted component.

🛡️ Proposed fix
     try {
       setStep('confirming');
       setSendError(undefined);
       const result = await accountApi.sendTx(sourceAccount.code, note);
+      if (!mounted.current) {
+        return;
+      }
       if (result.success) {
         setStep('success');
         return;
       }

       if ('aborted' in result) {
         setStep('aborted');
         return;
       }
       setStep('form');
       setSendError(result.errorCode ? t(`send.error.${result.errorCode}`) : result.errorMessage || t('genericError'));
     } catch (error) {
+      if (!mounted.current) {
+        return;
+      }
       setStep('form');
       setSendError(String(error));
     } finally {
       isSubmittingRef.current = false;
-      setIsSubmitting(false);
+      if (mounted.current) {
+        setIsSubmitting(false);
+      }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try {
setStep('confirming');
setSendError(undefined);
const result = await accountApi.sendTx(sourceAccount.code, note);
if (result.success) {
setStep('success');
return;
}
if ('aborted' in result) {
setStep('aborted');
return;
}
setStep('form');
setSendError(result.errorCode ? t(`send.error.${result.errorCode}`) : result.errorMessage || t('genericError'));
} catch (error) {
setStep('form');
setSendError(String(error));
} finally {
isSubmittingRef.current = false;
setIsSubmitting(false);
}
};
try {
setStep('confirming');
setSendError(undefined);
const result = await accountApi.sendTx(sourceAccount.code, note);
if (!mounted.current) {
return;
}
if (result.success) {
setStep('success');
return;
}
if ('aborted' in result) {
setStep('aborted');
return;
}
setStep('form');
setSendError(result.errorCode ? t(`send.error.${result.errorCode}`) : result.errorMessage || t('genericError'));
} catch (error) {
if (!mounted.current) {
return;
}
setStep('form');
setSendError(String(error));
} finally {
isSubmittingRef.current = false;
if (mounted.current) {
setIsSubmitting(false);
}
}
};
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontends/web/src/routes/lightning/topup/topup.tsx` around lines 323 - 345,
Add the same mounted.current post-await guard used elsewhere in this component
to the handleReview async flow so state updates only run while the component is
still mounted. In the sendTx() success, aborted, error, and finally paths, check
mounted.current before calling setStep, setSendError, or setIsSubmitting, and
keep the existing isSubmittingRef reset behavior intact. Use the handleReview
function and the surrounding async state-update pattern in topup.tsx as the
reference points.

Allow users to top up their lightning wallet directly in the app, by
adding a new page that allows a user to select a source BTC account, an
amount, and create the tx.
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.

4 participants