Skip to content

feat: show unselead balance in card details#170

Open
r1b2ns wants to merge 8 commits intoreez:mainfrom
r1b2ns:feat/unselead-balance-in-carddetails
Open

feat: show unselead balance in card details#170
r1b2ns wants to merge 8 commits intoreez:mainfrom
r1b2ns:feat/unselead-balance-in-carddetails

Conversation

@r1b2ns
Copy link
Copy Markdown
Collaborator

@r1b2ns r1b2ns commented Apr 8, 2026

SATSCARD allows multiple slots, and funds left in previous (unselead) slots can easily go unnoticed. This PR makes those forgotten balances visible directly in the card detail header, reducing the risk of users losing access to sats they didn't realize were still there.

Changes:

  • SatsCardDetailViewModel: fetches and sums balances from all historical slots after the main balance fetch
  • ActiveSlotView / BalanceHeaderView: renders a warning row with the unselead balance when non-zero; tapping navigates to the full slot list (SlotsRowListView)

Evd
https://github.com/user-attachments/assets/f3660e67-6761-4cf8-b54a-bf7b39da23a3

@r1b2ns r1b2ns requested a review from reez April 8, 2026 11:45
@reez reez linked an issue Apr 8, 2026 that may be closed by this pull request
Comment thread SatsBuddy/View/ActiveSlotView.swift Outdated
@reez
Copy link
Copy Markdown
Owner

reez commented Apr 8, 2026

I watched the demo (thanks for adding that its always SUPER helpful!) and the intended flow looks good.

I connected the open Issue for this to this PR.

One thing I’d still like to double check is whether the unsealed aggregate always stays in sync on a cold load, since historical slot balances appear to be loaded lazily elsewhere and this aggregate is computed once here. If you’ve already verified that path, I’m good with this.

@notmandatory
Copy link
Copy Markdown

Are you only able to sweep the unsealed balance by unsealing the current slot ? That's probably OK since as in this case you need both the unsealed and sealed balance to make a big enough transaction to not be dust. Also probably don't want to get into complicated coin/slot selection issues.

@r1b2ns
Copy link
Copy Markdown
Collaborator Author

r1b2ns commented Apr 10, 2026

Are you only able to sweep the unsealed balance by unsealing the current slot ? That's probably OK since as in this case you need both the unsealed and sealed balance to make a big enough transaction to not be dust. Also probably don't want to get into complicated coin/slot selection issues.

Yes, sweeping balances in unsealed slots is fine. This PR simply exposes any unsealed balances to make users aware of “forgotten” funds in previously used slots. I call them “forgotten” because reusing unsealed slots is not recommended, even though those addresses can still receive new coins

@r1b2ns
Copy link
Copy Markdown
Collaborator Author

r1b2ns commented Apr 10, 2026

I watched the demo (thanks for adding that its always SUPER helpful!) and the intended flow looks good.

I connected the open Issue for this to this PR.

One thing I’d still like to double check is whether the unsealed aggregate always stays in sync on a cold load, since historical slot balances appear to be loaded lazily elsewhere and this aggregate is computed once here. If you’ve already verified that path, I’m good with this.

In this case, the unsealed balance is aggregated when the CardDetail screen is displayed. Do you think there’s a better way to load these balances maybe when apps opens?

@reez
Copy link
Copy Markdown
Owner

reez commented Apr 10, 2026

One thing I’d still like to double check is whether the unsealed aggregate always stays in sync on a cold load

CardDetail still feels like the right place for this I wouldn’t move it to app launch. The main reason I asked about cold load sync is this code path: historical slots are created with balance=nil in CkTapCardService.swift, CardDetail copies those slots and computes unseleadBalance once after fetching the displayed slot, and there’s also a later path that can fill slot balances in getBalance(for:), but that later path doesn’t recompute the aggregate. If historical balances are already guaranteed to be populated before updateUnseleadBalance() runs, then there's no issue (I just couldn't totally tell). If not, I think the fix is just to recompute the aggregate when slot balances are updated? I could totally be missing something though!

@r1b2ns r1b2ns force-pushed the feat/unselead-balance-in-carddetails branch from 82ca8dc to 4298404 Compare April 10, 2026 20:21
Refetch balance for each historical slot by address instead of summing
cached values, ensuring accurate unsealed balance. Also fixes "Unselead"
typo in the balance header label.
@r1b2ns
Copy link
Copy Markdown
Collaborator Author

r1b2ns commented Apr 10, 2026

One thing I’d still like to double check is whether the unsealed aggregate always stays in sync on a cold load

CardDetail still feels like the right place for this I wouldn’t move it to app launch. The main reason I asked about cold load sync is this code path: historical slots are created with balance=nil in CkTapCardService.swift, CardDetail copies those slots and computes unseleadBalance once after fetching the displayed slot, and there’s also a later path that can fill slot balances in getBalance(for:), but that later path doesn’t recompute the aggregate. If historical balances are already guaranteed to be populated before updateUnseleadBalance() runs, then there's no issue (I just couldn't totally tell). If not, I think the fix is just to recompute the aggregate when slot balances are updated? I could totally be missing something though!

Your comment pointed out a bug. I assumed the unsealed balances were already computed at this stage, but in CkTapCardService.swift we don’t fetch the balance for each slot. We should handle that in SatsCardDetailViewModel.

I just pushed an update

@r1b2ns r1b2ns requested a review from reez April 10, 2026 20:34
Copy link
Copy Markdown
Owner

@reez reez left a comment

Choose a reason for hiding this comment

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

Just needs passing CI XCTAssertEqual failed: ("bc1qoldslot") is not equal to ("bc1qfinalslot") https://github.com/reez/SatsBuddy/actions/runs/24262943387/job/70851068506?pr=170

@r1b2ns
Copy link
Copy Markdown
Collaborator Author

r1b2ns commented Apr 13, 2026

Just needs passing CI XCTAssertEqual failed: ("bc1qoldslot") is not equal to ("bc1qfinalslot") https://github.com/reez/SatsBuddy/actions/runs/24262943387/job/70851068506?pr=170

Done @reez

@reez
Copy link
Copy Markdown
Owner

reez commented Apr 13, 2026

looks good. only outstanding item is to make sure notmandatory is cool w this

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.

Warn when unsealed SATSCARD slots still hold balance

3 participants