feat(wallet): denominate invoices in any currency the server accepts #166
No reviewers
Labels
No labels
app:activities
app:chat
app:chatelet
app:events
app:forum
app:libra
app:market
app:restaurant
app:tasks
app:wallet
app:webapp
bug
enhancement
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
aiolabs/webapp!166
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/wallet-fiat-invoices"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Receiving was sats-only. An invoice can now be quoted in any currency the LNbits instance allows, roughly 165 by default and narrowable via
LNBITS_ALLOWED_CURRENCIES. That is what pricing in EUR actually needs.Stacked on #165. The first commit here is that conversion-key fix, which the live sat preview depends on. Merge #165 first and this reduces to its own single commit.
How it works
LNbits does the conversion.
unit: "EUR"withamount: 5.50yields an ordinary BOLT11 invoice for the equivalent sats and records the original denomination on the payment'sextra:Nothing about the payment rail changes. The payer still settles over Lightning; only the quote is in fiat.
Notes on the design
useCurrenciesgoes in the base module beside the other shared payment primitives. It fetches the currency list and the instance default once per process and de-duplicates concurrent callers. Four modules (market, events, expenses, accounting) already duplicate this samegetCurrencies()call, so they have somewhere to converge later.Intl.NumberFormatrather than a hardcoded table. Fiat is not uniformly two decimals, and "5.50 JPY" is meaningless. The step is 0.01 for EUR and 1 for JPY.Verification
Driven headlessly against the live LNbits, in a real browser:
The bolt11 amount matches the preview exactly.
vue-tsc --noEmitis clean andnpm run build:walletsucceeds.Out of scope
Fiat amounts in the transaction history are deliberately not included. That applies to all payments rather than just newly created ones, and it touches the shared payment mapper that #161 rewrites. Worth a follow-up issue.
Send-side fiat is also untouched. The LNURL-pay endpoint does accept a
unit, so quoting a send in fiat is possible later.satskey in price conversions 45710447e1Every fiat-to-sat conversion returned null. `convert()` looked up the response by the `to` key it had passed in, but LNbits does not echo that key: it always names the satoshi amount "sats" (plural), whatever the caller asked for. So `data["sat"]` missed and the function fell through to its null path. POST {from_: "EUR", to: "sat", amount: 5.50} -> {"EUR": 5.5, "sats": 7298, "BTC": 7.298e-05} This silently broke the one shipped caller that converts in that direction: `PurchaseTicketDialog` computes `lightningSats` via `convert(amount, currency, 'sat')`, so it was always null. `PriceConversionPreview` has a `to === 'sat'` formatting branch that could never be reached either. Key selection moves into `pickConverted`, which maps sat/sats onto the plural key the server actually uses and keeps the previous fallbacks. The reverse direction (sat to fiat) already worked and is unchanged. Verified against the live LNbits: EUR -> sat (5.50): old=null new=7299 FIXED USD -> sat (10): old=null new=11588 FIXED JPY -> sat (1000): old=null new=7360 FIXED sat -> EUR (7295): old=5.4969 new=5.4969 unchangedReceiving was sats-only. An invoice can now be quoted in any currency the LNbits instance allows (~165 by default, narrowable via `LNBITS_ALLOWED_CURRENCIES`), which is what a merchant pricing in EUR actually needs. LNbits does the conversion: `unit: "EUR"` with `amount: 5.50` yields an ordinary BOLT11 invoice for the equivalent sats and records the original denomination on the payment's `extra` (`fiat_currency`, `fiat_amount`, `fiat_rate`). Nothing about the payment rail changes — the payer still settles over Lightning. - `useCurrencies` (base module) fetches the currency list and the instance default once per process, de-duplicating concurrent callers. It sits beside the other shared payment primitives because four modules already duplicate this same `getCurrencies()` call; they can adopt it later. - Decimal places come from `Intl.NumberFormat`, not a hardcoded table, so the input step is 0.01 for EUR and 1 for JPY, which has no minor unit. Amount validation tracks the unit too. - Switching unit clears a typed amount. Reinterpreting "10" from sats to EUR would silently create an invoice for a wildly different value. - The created invoice leads with what the payer was quoted (€5.50) and shows the sat amount as secondary, noting the rate was fixed at creation. - A live "≈ N sats" preview uses the shared conversion composable. It is best-effort: a failed lookup renders nothing and never blocks creation, since LNbits converts authoritatively server-side. - Sats stays the default, and the selector is hidden entirely if the currency list is unavailable, so the wallet still works if the rate service is down. Verified in a headless browser against the live LNbits: selector lists 166 options (sats + 165 currencies) EUR 5.50 -> preview "≈ 7,301 sats" -> POST {amount: 5.5, unit: "EUR"} -> invoice lnbc73010n... (7301 sats), shown as "€5.50" with "Payable as 7,301 sats" sats 250 -> POST {amount: 250, unit: "sat"}, no fiat line JPY step/min 1; EUR step/min 0.01 switching EUR -> JPY clears the typed "12.34" Fiat in the transaction history is deliberately left out; it applies to all payments rather than just newly created ones, and is tracked separately.