From 45710447e12beafaee320596991968d79b4b276a Mon Sep 17 00:00:00 2001 From: Padreug Date: Tue, 22 Sep 2026 22:51:13 +0200 Subject: [PATCH] fix(base): resolve the `sats` key in price conversions Every 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 unchanged --- .../base/composables/usePriceConversion.ts | 38 ++++++++++++++++--- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/src/modules/base/composables/usePriceConversion.ts b/src/modules/base/composables/usePriceConversion.ts index 5dfc083..b5766e5 100644 --- a/src/modules/base/composables/usePriceConversion.ts +++ b/src/modules/base/composables/usePriceConversion.ts @@ -21,6 +21,37 @@ function cacheKey(amount: number, from: string, to: string): string { * conversion preview as best-effort UX, never as a blocker. 60s in-memory * cache de-duplicates dialog re-renders. */ +/** + * Pull the converted value out of an LNbits `/conversion` response. + * + * The endpoint does not echo the `to` key it was given. It always names the + * satoshi amount `"sats"` (plural) regardless of whether the caller asked for + * "sat" or "sats", and it returns fiat under the upper-case ISO code: + * + * POST {from_: "EUR", to: "sat", amount: 5.50} -> {EUR: 5.5, sats: 7298, BTC: 7.298e-05} + * POST {from_: "sat", to: "EUR", amount: 7295} -> {BTC: ..., sats: 7295, EUR: 5.4972} + * + * So a lookup of `data["sat"]` misses, which meant every sat-denominated + * conversion silently returned null. + */ +function pickConverted(data: Record, to: string): number | undefined { + const lower = to.toLowerCase() + const candidates = + lower === 'sat' || lower === 'sats' + ? ['sats', 'sat'] + : [to, to.toUpperCase(), lower] + + for (const key of candidates) { + if (typeof data[key] === 'number') return data[key] + } + // Older/other shapes kept as a last resort. + return typeof data.amount === 'number' + ? data.amount + : typeof data.result === 'number' + ? data.result + : undefined +} + export function usePriceConversion() { const lnbitsAPI = injectService(SERVICE_TOKENS.LNBITS_API) @@ -38,12 +69,7 @@ export function usePriceConversion() { try { const data = await lnbitsAPI.getConversion({ from, to, amount }) - const result = - data[to] ?? - data[to.toUpperCase()] ?? - data[to.toLowerCase()] ?? - (data as Record).amount ?? - (data as Record).result + const result = pickConverted(data, to) if (typeof result !== 'number') return null cache.set(key, { value: result, expiresAt: Date.now() + TTL_MS }) return result -- 2.55.0