Skip to content

feat: suffix balance/amount fields with explicit unit (Sat or Msat)#2153

Open
Anshumancanrock wants to merge 19 commits intogetAlby:masterfrom
Anshumancanrock:suffix
Open

feat: suffix balance/amount fields with explicit unit (Sat or Msat)#2153
Anshumancanrock wants to merge 19 commits intogetAlby:masterfrom
Anshumancanrock:suffix

Conversation

@Anshumancanrock
Copy link
Copy Markdown
Contributor

@Anshumancanrock Anshumancanrock commented Mar 18, 2026

Summary

Several API response fields had ambiguous names like balance, amount, fee, localBalance , with no indication of whether the value is in sats or millisats. This makes it easy for clients to misinterpret values and introduce subtle bugs.

What this PR does

Fixes #2146

Adds a new suffixed field alongside every ambiguous numeric property so the unit is always explicit:

  • balance → also returns balanceMsat
  • amount → also returns amountMsat
  • feesPaid → also returns feesPaidMsat
  • localBalance → also returns localBalanceMsat
  • sendAmount → also returns sendAmountSat
  • etc.

Summary by CodeRabbit

  • New Features
    • API, client libraries and frontend now return explicit Sat and Msat fields for balances, amounts, fees, swaps, orders, channels, transactions and forwards for clearer unit reporting.
    • On-chain and Lightning balance responses, swap info, LSP order responses and app/budget totals include both Sat and Msat variants.
    • Budget usage and fee totals are exposed in msat alongside sat for more precise quota and fee visibility.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 18, 2026

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

Adds explicit satoshi (*Sat) and millisatoshi (*Msat) fields across API models, lnclient responses, and frontend types; populates them throughout backend implementations by deriving values (msat = sat * 1000, sat = msat / 1000). No breaking removals of existing fields; some function rename: GetBudgetUsage → GetBudgetUsageMsat.

Changes

Cohort / File(s) Summary
API models & public responses
api/models.go, api/api.go
Added many *Sat and *Msat fields to public response structs (App, ListAppsResponse, Channel, Swap, SwapInfoResponse, GetAutoSwapConfigResponse, GetForwardsResponse, Transaction, Boostagram, LSPOrderResponse, Rebalance responses). Populated these fields in API handlers by deriving sat/msat from existing values.
API handlers & helpers
api/transactions.go, api/lsp.go, api/rebalance.go, api/...
Derived and set new sat/msat fields when converting internal types to API responses; updated some fee arithmetic and renamed invoice-fee return to feeSat.
LN client models & implementations
lnclient/models.go, lnclient/lnd/lnd.go, lnclient/ldk/ldk.go, lnclient/cashu/cashu.go, lnclient/phoenixd/phoenixd.go
Extended onchain/lightning balance and transaction structs with sat/msat fields and populated them across different LN client backends; pending balance detail entries now include sat/msat.
Database query rename & tests
db/queries/get_budget_usage.go, db/queries/get_budget_usage_test.go
Renamed exported DB helper from GetBudgetUsageGetBudgetUsageMsat and updated tests/call sites accordingly (semantics unchanged; returns msat totals).
Business logic call sites
transactions/transactions_service.go, nip47/controllers/get_budget_controller.go
Switched callers to use GetBudgetUsageMsat and updated local variables/usage to handle msat values (converting to sat where needed).
Alby service & models
alby/models.go, alby/alby_oauth_service.go, alby/alby_service.go
Added sat/msat fields to AutoChannel/LSP models and populated msat fields from sat inputs; converted optional sat-fee pointers to msat equivalents.
Frontend types
frontend/src/types.ts
Extended TypeScript interfaces with *Sat and *Msat numeric fields across App, Channel, SwapInfo, AutoSwapConfig, LSPOrderResponse, Lightning/Onchain balance types, Transaction, Boostagram, etc.
Tests & mocks
tests/mock_ln_client.go
Updated mocks to include new sat/msat fields for LightningBalanceResponse.
UI tweak
frontend/src/screens/channels/IncreaseOutgoingCapacity.tsx
Initialized additional optional sat-sized min/max channel fields for the “Custom” peer option.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • rolznz

Poem

🐇 I nibble numbers, hop and write,
Sat and Msat now twinkling bright.
I split each value, count with care,
Millis, sats — now both are there.
Hooray — small hops, clear insight!

🚥 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 'feat: suffix balance/amount fields with explicit unit (Sat or Msat)' directly and clearly summarizes the main objective of the changeset: adding explicit unit suffixes to numeric fields.
Linked Issues check ✅ Passed The PR fully addresses issue #2146 by adding unit-suffixed companion fields (Sat/Msat) to all ambiguous numeric properties across API responses, models, and implementations while maintaining backward compatibility with existing fields.
Out of Scope Changes check ✅ Passed All changes are directly aligned with the linked issue #2146 objectives: adding unit-suffixed fields, marking deprecated fields, updating callers to use renamed functions like GetBudgetUsageMsat, and updating frontend types—with no database changes or unrelated modifications.

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

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

Copy link
Copy Markdown
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.

Caution

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

⚠️ Outside diff range comments (1)
api/models.go (1)

88-106: ⚠️ Potential issue | 🟡 Minor

App still ships ambiguous sat fields.

Line 98 already knows this value is sats (MaxAmountSat), but it is still serialized as maxAmount, and budgetUsage on Line 99 is still unitless too. Adding balanceMsat makes this payload only partially explicit; clients will still need special cases for two common app amounts. Add maxAmountSat/budgetUsageSat aliases or rename the JSON tags.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/models.go` around lines 88 - 106, The App struct exposes ambiguous sat
fields; change the JSON tags on MaxAmountSat and BudgetUsage to explicit sat
names (e.g., update MaxAmountSat `json:"maxAmount"` → `json:"maxAmountSat"` and
BudgetUsage `json:"budgetUsage"` → `json:"budgetUsageSat"`) or add alias fields
if backward compatibility is required so clients receive explicit sat units;
update any code that marshals/unmarshals App to use the new JSON keys
(references: struct App, fields MaxAmountSat and BudgetUsage, and consider
BalanceMsat consistency).
🧹 Nitpick comments (1)
lnclient/models.go (1)

196-199: Avoid publishing new ambiguous MPP balance fields.

Lines 196-199 add NextMaxSpendableMPP and NextMaxReceivableMPP right next to the explicit *MPPMsat fields. Since api/models.go:365 aliases lnclient.BalancesResponse, these tags go straight into the public HTTP schema, and the newly expanded surface is ambiguous again. Prefer exposing only the explicit-unit keys, or deprecate the unsuffixed aliases immediately.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lnclient/models.go` around lines 196 - 199, The new unsuffixed balance fields
NextMaxSpendableMPP and NextMaxReceivableMPP are ambiguous and will be published
via the lnclient.BalancesResponse alias used in api/models.go:365; remove or
hide those two fields from the public JSON surface by deleting them from
lnclient/models.go (or changing their struct tags to json:"-" and adding a
deprecation comment) and keep only the explicit-unit fields
NextMaxSpendableMPPMsat and NextMaxReceivableMPPMsat; ensure any internal code
referencing NextMaxSpendableMPP/NextMaxReceivableMPP is updated to use the
explicit-suffixed fields or converted appropriately.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@api/models.go`:
- Around line 88-106: The App struct exposes ambiguous sat fields; change the
JSON tags on MaxAmountSat and BudgetUsage to explicit sat names (e.g., update
MaxAmountSat `json:"maxAmount"` → `json:"maxAmountSat"` and BudgetUsage
`json:"budgetUsage"` → `json:"budgetUsageSat"`) or add alias fields if backward
compatibility is required so clients receive explicit sat units; update any code
that marshals/unmarshals App to use the new JSON keys (references: struct App,
fields MaxAmountSat and BudgetUsage, and consider BalanceMsat consistency).

---

Nitpick comments:
In `@lnclient/models.go`:
- Around line 196-199: The new unsuffixed balance fields NextMaxSpendableMPP and
NextMaxReceivableMPP are ambiguous and will be published via the
lnclient.BalancesResponse alias used in api/models.go:365; remove or hide those
two fields from the public JSON surface by deleting them from lnclient/models.go
(or changing their struct tags to json:"-" and adding a deprecation comment) and
keep only the explicit-unit fields NextMaxSpendableMPPMsat and
NextMaxReceivableMPPMsat; ensure any internal code referencing
NextMaxSpendableMPP/NextMaxReceivableMPP is updated to use the explicit-suffixed
fields or converted appropriately.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ee4d1014-7010-4769-8e4f-65d22dd8d189

📥 Commits

Reviewing files that changed from the base of the PR and between 956c841 and 46c3b07.

📒 Files selected for processing (11)
  • api/api.go
  • api/lsp.go
  • api/models.go
  • api/transactions.go
  • frontend/src/types.ts
  • lnclient/cashu/cashu.go
  • lnclient/ldk/ldk.go
  • lnclient/lnd/lnd.go
  • lnclient/models.go
  • lnclient/phoenixd/phoenixd.go
  • tests/mock_ln_client.go

Comment thread api/api.go Outdated
@rolznz
Copy link
Copy Markdown
Member

rolznz commented Mar 19, 2026

Thanks for the PR! I think there are some inconsistencies currently which need to be resolved. And we need to carefully review to ensure there are no breaking changes on the API level, only additions.

Copy link
Copy Markdown
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.

Caution

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

⚠️ Outside diff range comments (1)
api/api.go (1)

1214-1228: ⚠️ Potential issue | 🟠 Major

Update Cashu and Phoenixd GetBalances to populate all OnchainBalanceResponse fields.

The Cashu and Phoenixd implementations are incomplete. They only populate 4 fields (Spendable, SpendableSat, Total, TotalSat) with zeros and hardcoded values, while the OnchainBalanceResponse struct defines 15 fields. LDK and LND correctly populate all fields including SpendableMsat, TotalMsat, Reserved*, PendingBalances*, and InternalBalances.

This creates API inconsistency across backends. Per the interface design pattern, all four backend implementations (LDK, LND, Phoenixd, Cashu) must maintain consistent return values for the LNClient interface.

  • Cashu (lnclient/cashu/cashu.go, lines 270-275): Missing SpendableMsat, TotalMsat, Reserved*, PendingBalances*, InternalBalances
  • Phoenixd (lnclient/phoenixd/phoenixd.go, lines 116-121): Missing SpendableMsat, TotalMsat, Reserved*, PendingBalances*, InternalBalances

Update both implementations to return the complete struct with meaningful values where applicable, following the LDK and LND patterns.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/api.go` around lines 1214 - 1228, The Cashu and Phoenixd GetBalances
implementations are returning only Spendable, SpendableSat, Total and TotalSat;
update the GetBalances functions in the cashu and phoenixd LN client
implementations (the GetBalances methods in lnclient/cashu/cashu.go and
lnclient/phoenixd/phoenixd.go) to populate the full OnchainBalanceResponse
struct (include SpendableMsat, TotalMsat, ReservedOnchain, ReservedOnchainSat,
ReservedOnchainMsat, PendingBalancesOnchain, PendingBalancesOnchainSat,
PendingBalancesOnchainMsat, InternalBalances, InternalBalancesSat,
InternalBalancesMsat) following the same semantics used in LDK/LND: convert sat
-> msat (msat = sat*1000) for msat fields, fill Reserved*/Pending*/Internal*
with meaningful values (zero where not applicable) and ensure consistency
between sat and msat fields and between Spendable/Total and their sat/msat
counterparts so the LNClient GetBalances contract is met.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@api/api.go`:
- Around line 1214-1228: The Cashu and Phoenixd GetBalances implementations are
returning only Spendable, SpendableSat, Total and TotalSat; update the
GetBalances functions in the cashu and phoenixd LN client implementations (the
GetBalances methods in lnclient/cashu/cashu.go and
lnclient/phoenixd/phoenixd.go) to populate the full OnchainBalanceResponse
struct (include SpendableMsat, TotalMsat, ReservedOnchain, ReservedOnchainSat,
ReservedOnchainMsat, PendingBalancesOnchain, PendingBalancesOnchainSat,
PendingBalancesOnchainMsat, InternalBalances, InternalBalancesSat,
InternalBalancesMsat) following the same semantics used in LDK/LND: convert sat
-> msat (msat = sat*1000) for msat fields, fill Reserved*/Pending*/Internal*
with meaningful values (zero where not applicable) and ensure consistency
between sat and msat fields and between Spendable/Total and their sat/msat
counterparts so the LNClient GetBalances contract is met.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 52dc374b-226a-4014-9da9-be41349de925

📥 Commits

Reviewing files that changed from the base of the PR and between 46c3b07 and e2aa9b0.

📒 Files selected for processing (12)
  • PR_DESCRIPTION.md
  • api/api.go
  • api/lsp.go
  • api/models.go
  • api/transactions.go
  • frontend/src/types.ts
  • lnclient/cashu/cashu.go
  • lnclient/ldk/ldk.go
  • lnclient/lnd/lnd.go
  • lnclient/models.go
  • lnclient/phoenixd/phoenixd.go
  • tests/mock_ln_client.go
✅ Files skipped from review due to trivial changes (2)
  • PR_DESCRIPTION.md
  • frontend/src/types.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • api/transactions.go
  • lnclient/phoenixd/phoenixd.go
  • tests/mock_ln_client.go
  • lnclient/cashu/cashu.go
  • lnclient/models.go
  • api/models.go

Copy link
Copy Markdown
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: 1

🧹 Nitpick comments (1)
lnclient/cashu/cashu.go (1)

280-291: Explicitly set receivable *Sat fields instead of relying on zero-values.

TotalReceivableSat, NextMaxReceivableSat, and NextMaxReceivableMPPSat are currently implicit. Please set them explicitly for consistency and future maintainability.

Proposed patch
 		Lightning: lnclient.LightningBalanceResponse{
 			TotalSpendable:           balance,
 			TotalSpendableSat:        balance / 1000,
 			TotalSpendableMsat:       balance,
 			TotalReceivable:          0,
+			TotalReceivableSat:       0,
 			TotalReceivableMsat:      0,
 			NextMaxSpendable:         balance,
 			NextMaxSpendableSat:      balance / 1000,
 			NextMaxSpendableMsat:     balance,
 			NextMaxReceivable:        0,
+			NextMaxReceivableSat:     0,
 			NextMaxReceivableMsat:    0,
 			NextMaxSpendableMPP:      balance,
 			NextMaxSpendableMPPSat:   balance / 1000,
 			NextMaxSpendableMPPMsat:  balance,
 			NextMaxReceivableMPP:     0,
+			NextMaxReceivableMPPSat:  0,
 			NextMaxReceivableMPPMsat: 0,
 		},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lnclient/cashu/cashu.go` around lines 280 - 291, The struct literal in
cashu.go initializes multiple receivable and spendable fields but leaves the sat
variants implicit; explicitly add TotalReceivableSat, NextMaxReceivableSat, and
NextMaxReceivableMPPSat (set them to 0) alongside the existing fields (e.g.,
TotalReceivable, NextMaxReceivable, NextMaxReceivableMPP) so the initialization
is consistent and future-proof when editing functions like the
constructor/initializer that builds this balance object.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@api/lsp.go`:
- Around line 84-86: invoiceAmount was truncated to whole sats before computing
InvoiceAmountMsat, causing loss of sub-sat precision; instead preserve the
original decoded millisat amount (e.g., decodedMsat or invoiceMsatDecoded
captured from the decoder) and use that for the InvoiceAmountMsat field while
keeping InvoiceAmount and InvoiceAmountSat set to the truncated sats variable
(invoiceAmount). Update the struct assignment where InvoiceAmountMsat is set
(the InvoiceAmountMsat field in the code near the InvoiceAmount/InvoiceAmountSat
assignments) to use the preserved decoded msat variable rather than
invoiceAmount*1000.

---

Nitpick comments:
In `@lnclient/cashu/cashu.go`:
- Around line 280-291: The struct literal in cashu.go initializes multiple
receivable and spendable fields but leaves the sat variants implicit; explicitly
add TotalReceivableSat, NextMaxReceivableSat, and NextMaxReceivableMPPSat (set
them to 0) alongside the existing fields (e.g., TotalReceivable,
NextMaxReceivable, NextMaxReceivableMPP) so the initialization is consistent and
future-proof when editing functions like the constructor/initializer that builds
this balance object.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 53521dc9-b094-4d49-b9b0-f4ee44d15025

📥 Commits

Reviewing files that changed from the base of the PR and between e2aa9b0 and 477b3a0.

📒 Files selected for processing (11)
  • api/api.go
  • api/lsp.go
  • api/models.go
  • api/transactions.go
  • frontend/src/types.ts
  • lnclient/cashu/cashu.go
  • lnclient/ldk/ldk.go
  • lnclient/lnd/lnd.go
  • lnclient/models.go
  • lnclient/phoenixd/phoenixd.go
  • tests/mock_ln_client.go
✅ Files skipped from review due to trivial changes (2)
  • tests/mock_ln_client.go
  • lnclient/lnd/lnd.go
🚧 Files skipped from review as they are similar to previous changes (5)
  • lnclient/phoenixd/phoenixd.go
  • lnclient/models.go
  • lnclient/ldk/ldk.go
  • api/api.go
  • frontend/src/types.ts

Comment thread api/lsp.go Outdated
@Anshumancanrock
Copy link
Copy Markdown
Contributor Author

@rolznz Thanks for the review. I’ve addressed the inconsistencies by adding explicit Sat/Msat companion fields for ambiguous amount/balance values, while keeping existing fields unchanged for backward compatibility. I also rechecked the mapping paths to ensure this is API-additive only with no intended breaking changes.

I can also add explicit regression tests for the new Sat/Msat fields if you want extra safety before merge.

@rolznz rolznz requested a review from im-adithya March 24, 2026 04:15
@im-adithya
Copy link
Copy Markdown
Member

@rolznz do we want to do it this way, or just add a unit field which returns sat/msat? Wouldn't that be a better option?

@rolznz
Copy link
Copy Markdown
Member

rolznz commented Mar 31, 2026

@im-adithya can you give an example? how will it work in json?

@im-adithya
Copy link
Copy Markdown
Member

Whoops sorry, I saw no breaking changes note in the issue and thought this was about adding unit field to get_balance NWC method to let clients know what they are receiving, I was confused as to why we need this since NWC always deals in msats, makes sense now 👍

@im-adithya
Copy link
Copy Markdown
Member

@rolznz would be great if you can have a look, should be good now!

Copy link
Copy Markdown
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

Caution

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

⚠️ Outside diff range comments (1)
alby/alby_oauth_service.go (1)

1333-1373: ⚠️ Potential issue | 🟠 Major

Populate FeeMsat from the decoded invoice amount.

FeeMsat: fee * 1000 discards sub-sat precision even though this path already decodes the exact BOLT11 amount. Keep Fee/FeeSat from fee_total_sat, but use paymentRequest.MSatoshi for the new msat field so the response matches the invoice the client will actually pay.

Suggested fix
 var invoice string
 var fee uint64
+var feeMsat uint64

 if newAutoChannelResponse.Payment != nil {
 	invoice = newAutoChannelResponse.Payment.Bolt11.Invoice
 	fee, err = strconv.ParseUint(newAutoChannelResponse.Payment.Bolt11.FeeTotalSat, 10, 64)
 	if err != nil {
@@
 	paymentRequest, err := decodepay.Decodepay(invoice)
 	if err != nil {
 		logger.Logger.WithError(err).Error("Failed to decode bolt11 invoice")
 		return nil, err
 	}
+	feeMsat = uint64(paymentRequest.MSatoshi)

 	if fee != uint64(paymentRequest.MSatoshi/1000) {
 		logger.Logger.WithFields(logrus.Fields{
 			"invoice_amount": paymentRequest.MSatoshi / 1000,
 			"fee":            fee,
@@
 return &AutoChannelResponse{
 	Invoice:         invoice,
 	Fee:             fee,
 	FeeSat:          fee,
-	FeeMsat:         fee * 1000,
+	FeeMsat:         feeMsat,
 	ChannelSize:     channelSize,
 	ChannelSizeSat:  channelSize,
 	ChannelSizeMsat: channelSize * 1000,
 }, nil
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@alby/alby_oauth_service.go` around lines 1333 - 1373, The FeeMsat should come
from the decoded BOLT11 amount, not fee*1000; change the code that builds the
AutoChannelResponse so that when newAutoChannelResponse.Payment != nil you
capture paymentRequest.MSatoshi (from decodepay.Decodepay) into a variable (e.g.
invoiceMsat) and later set FeeMsat: invoiceMsat, while leaving Fee and FeeSat
derived from fee (fee_total_sat); ensure invoiceMsat is defined in the outer
scope so AutoChannelResponse uses paymentRequest.MSatoshi when available
(otherwise fall back to fee*1000).
♻️ Duplicate comments (1)
api/api.go (1)

466-467: ⚠️ Potential issue | 🟠 Major

Expose an explicit maxAmountSat field as well.

These handlers only add maxAmountMsat. Because api/models.go still serializes the sat value as legacy maxAmount, clients still do not get a unit-suffixed sat companion for this amount field. Please add maxAmountSat to the model/frontend type and populate it here in both GetApp and ListApps, while keeping maxAmount as the backward-compatible alias.

Also applies to: 631-632

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/api.go` around lines 466 - 467, Add an explicit maxAmountSat field to the
model/frontend type (in api/models.go) and populate it in the app response
objects in both GetApp and ListApps handlers: set maxAmountSat = maxAmount
(satoshis) alongside the existing MaxAmount (legacy alias) and MaxAmountMsat =
maxAmount * 1000; keep MaxAmount unchanged for backward compatibility and ensure
the new maxAmountSat is marshaled/serialized for clients so they receive a
unit-suffixed sat companion.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@alby/models.go`:
- Around line 108-111: Add explicit sat-suffixed fields to the model by adding
MinimumChannelSizeSat uint64 `json:"minimumChannelSizeSat"` and
MaximumChannelSizeSat uint64 `json:"maximumChannelSizeSat"` alongside the
existing fields in the struct in alby/models.go, and then populate those fields
where the model is constructed in alby/alby_service.go (use the existing msat
values or the already-computed sat values there to derive the sat companions,
e.g., divide MinimumChannelSizeMsat/MaximumChannelSizeMsat by 1000 or copy the
existing sat variables) so the JSON output includes deterministic sat-suffixed
properties.

In `@api/rebalance.go`:
- Around line 141-145: The current code calculates TotalFeeSat by dividing the
aggregated totalFeeMsat by 1000, which can introduce a rounding difference
versus the legacy per-component sat rounding; change it to compute TotalFeeSat
by summing each component in sats (invoice sats + fee sats) then subtracting
rebalanceChannelRequest.AmountSat (i.e., use paymentRequest.MSatoshi/1000 and
payRebalanceInvoiceResponse.FeeMsat/1000 as the sat components) and assign that
value to RebalanceChannelResponse.TotalFeeSat while keeping TotalFeeMsat as the
precise aggregate (totalFeeMsat) for TotalFeeMsat.

---

Outside diff comments:
In `@alby/alby_oauth_service.go`:
- Around line 1333-1373: The FeeMsat should come from the decoded BOLT11 amount,
not fee*1000; change the code that builds the AutoChannelResponse so that when
newAutoChannelResponse.Payment != nil you capture paymentRequest.MSatoshi (from
decodepay.Decodepay) into a variable (e.g. invoiceMsat) and later set FeeMsat:
invoiceMsat, while leaving Fee and FeeSat derived from fee (fee_total_sat);
ensure invoiceMsat is defined in the outer scope so AutoChannelResponse uses
paymentRequest.MSatoshi when available (otherwise fall back to fee*1000).

---

Duplicate comments:
In `@api/api.go`:
- Around line 466-467: Add an explicit maxAmountSat field to the model/frontend
type (in api/models.go) and populate it in the app response objects in both
GetApp and ListApps handlers: set maxAmountSat = maxAmount (satoshis) alongside
the existing MaxAmount (legacy alias) and MaxAmountMsat = maxAmount * 1000; keep
MaxAmount unchanged for backward compatibility and ensure the new maxAmountSat
is marshaled/serialized for clients so they receive a unit-suffixed sat
companion.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3d35f0fb-74ce-4348-8869-91464c449226

📥 Commits

Reviewing files that changed from the base of the PR and between 477b3a0 and ac6e421.

📒 Files selected for processing (15)
  • alby/alby_oauth_service.go
  • alby/alby_service.go
  • alby/models.go
  • api/api.go
  • api/lsp.go
  • api/models.go
  • api/rebalance.go
  • api/transactions.go
  • frontend/src/types.ts
  • lnclient/cashu/cashu.go
  • lnclient/ldk/ldk.go
  • lnclient/lnd/lnd.go
  • lnclient/models.go
  • lnclient/phoenixd/phoenixd.go
  • tests/mock_ln_client.go
✅ Files skipped from review due to trivial changes (1)
  • frontend/src/types.ts
🚧 Files skipped from review as they are similar to previous changes (7)
  • tests/mock_ln_client.go
  • lnclient/phoenixd/phoenixd.go
  • lnclient/ldk/ldk.go
  • lnclient/models.go
  • lnclient/lnd/lnd.go
  • api/transactions.go
  • api/models.go

Comment thread alby/models.go Outdated
Comment thread api/rebalance.go
Comment thread alby/models.go Outdated
Comment thread alby/alby_service.go
Comment thread api/api.go Outdated
Comment thread lnclient/cashu/cashu.go Outdated
@im-adithya im-adithya requested a review from rolznz April 10, 2026 04:25
@hermes-alby
Copy link
Copy Markdown

API Breaking Change Review 🔍

I've done a thorough review of every file in this diff checking for breaking changes to the Alby Hub API.

✅ No Breaking Changes to Existing API Responses

The PR follows a solid backward-compatible pattern: old fields are preserved (with // deprecated comments) alongside new explicit *Sat/*Msat variants. Every existing JSON key continues to return the same value it did before. Verified field-by-field:

Existing field Unit (before & after) New alternatives
maxAmount sats maxAmountSat, maxAmountMsat
budgetUsage sats budgetUsageSat, budgetUsageMsat
balance msat balanceSat, balanceMsat
localBalance / remoteBalance / localSpendableBalance msat *Sat, *Msat variants
amount / feesPaid (Transaction) msat amountSat/amountMsat, feesPaidSat/feesPaidMsat
totalBalance (ListAppsResponse) msat totalBalanceSat, totalBalanceMsat
unspendablePunishmentReserve sats *Sat, *Msat variants
Swap, LSP, auto-channel fields varies (preserved) *Sat/*Msat variants

This is a clean additive change — existing clients will continue to work unchanged. 👍


⚠️ Issues Worth Addressing Before Merge

1. Inconsistent *Sat/*Msat coverage — Some structs get both variants, while others only get *Sat:

  • AutoChannelResponse: has channelSizeSat but no channelSizeMsat
  • LSPOrderResponse: has incomingLiquiditySat but no incomingLiquidityMsat; has outgoingLiquiditySat but no outgoingLiquidityMsat

API consumers will likely expect a uniform pattern — either always provide both, or document why one is omitted.

2. Deprecated fields should document their actual unit — The // deprecated comments don't specify what unit the old field returns. For example:

Balance int64 `json:"balance"` // deprecated

Should be:

Balance int64 `json:"balance"` // deprecated — returns msat, use balanceSat or balanceMsat instead

This matters because some deprecated fields return sats (e.g., budgetUsage, unspendablePunishmentReserve) while others return msat (e.g., balance, localBalance). Without documentation, clients migrating to the new fields might pick the wrong one.

3. Cashu/Phoenix backends: new fields rely on Go zero values — These backends removed explicit Spendable: 0, Total: 0 and TotalReceivable: 0, NextMaxReceivable: 0 initializations. The new *Sat/*Msat fields will be 0 by default (Go zero value), which is correct. But for consistency with LDK/LND, it would be better to set them explicitly — makes the intent clear and avoids future bugs if someone adds initialization logic.

4. PendingBalanceDetails.Amount is in sats (not msat) — Since AmountSat = amount and AmountMsat = amount * 1000, the deprecated Amount field returns sats. This is different from most other deprecated fields that return msat. Worth calling out explicitly in the deprecation comment.

5. Internal rename: GetBudgetUsageGetBudgetUsageMsat — Not an API break since it's internal, but worth noting for anyone who might be importing db/queries externally.


Overall this is a well-structured PR. The additive approach with deprecated fields is the right pattern for a gradual migration. The main ask would be: document the unit on each deprecated field and make the *Sat/*Msat coverage consistent.

@hermes-alby
Copy link
Copy Markdown

Update on point 1 (inconsistent *Sat/*Msat coverage): After further thought, I think the PR's approach is actually correct here. Channel sizes, incoming/outgoing liquidity — these are on-chain UTXO amounts that are inherently whole sats. Including a *Msat variant would be misleading because it implies millisat precision exists where it physically can't (the smallest on-chain unit is 1 sat, and there's a dust limit below that).

The pattern should be:

  • *Sat + *Msat → for lightning balances, payment amounts, routing fees (where msat precision exists)
  • *Sat only → for on-chain / channel-size fields (always whole sats)

So what looked like inconsistency is actually intentional and correct. Withdrawn! 🙏

Comment thread alby/alby_oauth_service.go Outdated
Comment thread alby/alby_oauth_service.go Outdated
Comment thread alby/alby_service.go Outdated
Comment thread alby/models.go Outdated
Comment thread api/api.go Outdated
ForwardingFeeProportionalMillionths: channel.ForwardingFeeProportionalMillionths,
UnspendablePunishmentReserve: channel.UnspendablePunishmentReserve,
UnspendablePunishmentReserveSat: channel.UnspendablePunishmentReserve,
UnspendablePunishmentReserveMsat: channel.UnspendablePunishmentReserve * 1000,
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

does msats exist here? (same with CounterpartyUnspendablePunishmentReserve)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Fixed!

Comment thread api/api.go Outdated
Comment thread api/api.go Outdated
Comment thread api/api.go Outdated
Comment thread api/rebalance.go Outdated
Comment thread frontend/src/types.ts
Comment thread lnclient/cashu/cashu.go Outdated
Comment thread nip47/controllers/get_budget_controller.go
Copy link
Copy Markdown
Member

@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

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.

suffix balances with "Sat" or "Msat"

4 participants