Skip to content

fix: return optional total balance in list apps response for subwallets#2057

Merged
im-adithya merged 5 commits intomasterfrom
fix/subwallet-balance
Feb 26, 2026
Merged

fix: return optional total balance in list apps response for subwallets#2057
im-adithya merged 5 commits intomasterfrom
fix/subwallet-balance

Conversation

@im-adithya
Copy link
Member

@im-adithya im-adithya commented Feb 11, 2026

Fixes #2031

Summary by CodeRabbit

  • New Features

    • Subwallet lists now include an aggregated totalBalance returned from the server.
  • UI Changes

    • Subwallet screens use server-provided totalCount and totalBalance; promo/creation prompts respect the configured free-subwallet limit.
    • Subwallet filtering refined to support include / exclude / neutral options.
  • Bug Fixes / Accuracy

    • Balance calculation accounts for incoming/outgoing amounts, fees, and reserves across relevant transaction states.
  • Tests

    • Added tests validating total subwallet balance computation.

@im-adithya im-adithya requested a review from rolznz February 11, 2026 18:52
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 11, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 746e654 and e19f59d.

📒 Files selected for processing (1)
  • frontend/src/screens/subwallets/NewSubwallet.tsx

📝 Walkthrough

Walkthrough

Computes total subwallet balance and returns it in ListApps; refactors SubWallets filtering to tri-state (true/false/nil) and switches metadata key lookups to a new constant. Frontend requests subwallets and consumes totalCount and totalBalance.

Changes

Cohort / File(s) Summary
API: endpoint & models
api/api.go, api/models.go
ListApps: tri-state SubWallets filtering; use constants.METADATA_APPSTORE_APP_ID_KEY for metadata lookups; call new balance query when subWallets=true. ListAppsResponse now includes TotalBalance *int64.
DB: queries & tests
db/queries/get_total_subwallet_balance.go, db/queries/get_total_subwallet_balance_test.go
New GetTotalSubwalletBalance(tx *gorm.DB) (int64, error) computes net subwallet balance (SUM incoming settled − SUM outgoing (amount+fee+reserve) settled or pending). Unit test added validating calculation.
Constants
constants/constants.go, frontend/src/constants.ts
Added backend METADATA_APPSTORE_APP_ID_KEY = "app_store_app_id" and frontend MAX_FREE_SUBWALLETS = 3.
Frontend: types & UI
frontend/src/types.ts, frontend/src/screens/subwallets/SubwalletList.tsx, frontend/src/screens/subwallets/NewSubwallet.tsx
Frontend requests subWallets: true, renames data variables to subwalletAppsData, uses totalCount and new totalBalance, and replaces hardcoded free-subwallet limit with MAX_FREE_SUBWALLETS.
NIP-47: controller & tests
nip47/controllers/get_info_controller.go, nip47/controllers/get_info_controller_test.go
Replaced literal "app_store_app_id" key with constants.METADATA_APPSTORE_APP_ID_KEY in code and tests.

Sequence Diagram

sequenceDiagram
    participant FE as Frontend (SubwalletList)
    participant API as API (ListApps)
    participant DB as Database (Queries)

    FE->>API: ListApps(subWallets: true)
    API->>API: Apply SubWallets tri-state filtering (use METADATA_APPSTORE_APP_ID_KEY)
    API->>DB: GetTotalSubwalletBalance(tx)
    DB->>DB: Find app IDs where metadata[METADATA_APPSTORE_APP_ID_KEY] = SUBWALLET_APPSTORE_APP_ID
    DB->>DB: SUM INCOMING amounts (state = SETTLED)
    DB->>DB: SUM OUTGOING (amount+fee+fee_reserve) (state IN (SETTLED,PENDING))
    DB-->>API: Return net balance (received - spent)
    API-->>FE: ListAppsResponse { Apps, TotalCount, TotalBalance }
    FE->>FE: Render list using totalCount and totalBalance
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I hopped through metadata and rows,

counted ins and outs where ledger flows,
tiny balances found their song,
totals tucked where they belong,
a joyful hop — the sums belong.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding an optional total balance field to the list apps response for subwallets.
Linked Issues check ✅ Passed The PR addresses the linked issue #2031 by adding totalBalance to the list apps response and refactoring subwallet filtering to use proper pagination totals.
Out of Scope Changes check ✅ Passed All changes are directly related to fixing subwallet page pagination and balance display; no unrelated modifications are present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/subwallet-balance

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 and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@api/api.go`:
- Around line 530-535: The call to queries.GetTotalSubwalletBalance currently
assumes a single int64 return and masks DB errors by setting totalBalance to 0;
change the call-site in the block that checks filters.SubWallets to handle the
new signature (int64, error) from GetTotalSubwalletBalance: call
totalBalanceMsat, err := queries.GetTotalSubwalletBalance(api.db), on error log
the error via the existing logger and return the error (or otherwise degrade by
leaving totalBalance nil), and only set totalBalance = &totalBalanceMsat when
err == nil; update references to totalBalance accordingly to avoid exposing a
zero balance on DB failures.

In `@db/queries/get_total_subwallet_balance.go`:
- Around line 10-33: GetTotalSubwalletBalance currently ignores database errors
from the two Scan calls (Scan(&received) and Scan(&spent)), so change its
signature to return (int64, error), capture the returned *gorm.DB from each Scan
(e.g., res := tx.Table(...).Scan(&received)), check res.Error and return 0, err
on failure, and only compute and return received.Sum - spent.Sum with nil error
when both queries succeed; update the caller in api/api.go to handle the new
(int64, error) result accordingly.
🧹 Nitpick comments (1)
db/queries/get_total_subwallet_balance_test.go (1)

25-59: Consider checking errors from svc.DB.Save() calls.

Multiple svc.DB.Save(...) calls (Lines 25, 31, 39, 49, 59) don't assert on the returned error. A silent save failure would cause a misleading test result. Using require.NoError(t, svc.DB.Save(...).Error) would make failures easier to diagnose.

Copy link
Contributor

@rolznz rolznz left a comment

Choose a reason for hiding this comment

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

utACK, minor comments

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
frontend/src/screens/subwallets/NewSubwallet.tsx (1)

50-53: Use appsData.totalCount for quota checks instead of page length.

Line 52 uses subwalletApps?.length, which is page-bounded. Using totalCount keeps this correct even if paging limits change.

Proposed tweak
-      if (
-        !albyMe?.subscription.plan_code &&
-        subwalletApps?.length >= MAX_FREE_SUBWALLETS
-      ) {
+      if (
+        !albyMe?.subscription.plan_code &&
+        (appsData.totalCount ?? 0) >= MAX_FREE_SUBWALLETS
+      ) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/screens/subwallets/NewSubwallet.tsx` around lines 50 - 53, The
quota check in NewSubwallet.tsx currently uses the paged array length
(subwalletApps?.length) which can be incorrect; replace that check with the
total count from the server (appsData?.totalCount) so the condition reads that
the user has no plan_code and appsData.totalCount >= MAX_FREE_SUBWALLETS,
keeping references to albyMe, appsData.totalCount, subwalletApps and
MAX_FREE_SUBWALLETS so you only change the comparison source and leave the rest
of the logic intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@db/queries/get_total_subwallet_balance.go`:
- Around line 20-22: The aggregate SELECT uses SUM(...) which returns NULL when
no rows match; update the Select call in get_total_subwallet_balance (the query
building that currently uses Select("SUM(amount_msat) as sum")) to use COALESCE
around the aggregate (e.g., Select("COALESCE(SUM(amount_msat), 0) AS sum")) so
GORM can scan a zero instead of NULL into the int64 variable; keep the WHERE and
Scan(&received) logic unchanged.

In `@nip47/controllers/get_info_controller.go`:
- Around line 94-96: The code in get_info_controller.go unsafely asserts
metadata["lud16"].(string) which can panic if lud16 exists but is not a string;
update the block that sets responsePayload.LightningAddress to perform a safe
type assertion using the comma-ok pattern (e.g., v, ok :=
metadata["lud16"].(string)) and only set LightningAddress when ok is true, and
mirror the same fix where the same pattern appears in api/api.go (look for uses
of metadata["lud16"] and responsePayload.LightningAddress).

---

Nitpick comments:
In `@frontend/src/screens/subwallets/NewSubwallet.tsx`:
- Around line 50-53: The quota check in NewSubwallet.tsx currently uses the
paged array length (subwalletApps?.length) which can be incorrect; replace that
check with the total count from the server (appsData?.totalCount) so the
condition reads that the user has no plan_code and appsData.totalCount >=
MAX_FREE_SUBWALLETS, keeping references to albyMe, appsData.totalCount,
subwalletApps and MAX_FREE_SUBWALLETS so you only change the comparison source
and leave the rest of the logic intact.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6c67745 and 746e654.

📒 Files selected for processing (9)
  • api/api.go
  • constants/constants.go
  • db/queries/get_total_subwallet_balance.go
  • db/queries/get_total_subwallet_balance_test.go
  • frontend/src/constants.ts
  • frontend/src/screens/subwallets/NewSubwallet.tsx
  • frontend/src/screens/subwallets/SubwalletList.tsx
  • nip47/controllers/get_info_controller.go
  • nip47/controllers/get_info_controller_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • db/queries/get_total_subwallet_balance_test.go

@im-adithya
Copy link
Member Author

Addressed all comments, merging!

@im-adithya im-adithya merged commit a2af8fd into master Feb 26, 2026
11 of 12 checks passed
@im-adithya im-adithya deleted the fix/subwallet-balance branch February 26, 2026 06:55
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.

Bug: Subwallet page only shows count of wallets on first page

2 participants