Skip to content

[Bounty] JMD Currency Precision — Full-Stack Fix #282

@islandbitcoin

Description

@islandbitcoin

Background

Flash is a Bitcoin banking platform for Jamaica. Users who have set JMD as their display currency are seeing incorrect amounts across three critical surfaces: their wallet balance, the send/receive flows, and their transaction history. These are not cosmetic issues — users are losing money on precision truncation and seeing a transaction history that changes every time the BTC price moves.

The bugs span the full stack. The backend serves stale/wrong data from its transaction adaptor layer, and the mobile app compounds the problem with its own conversion errors. Both must be fixed for JMD amounts to be trustworthy end-to-end.


Scope

This bounty covers all three affected surfaces:

Wallet Balance

The BTC wallet balance query (me.btcWallet.balance) currently returns a serialized class object ({}) instead of a number, because BtcWallet.balance serializes the WalletBalance class instance rather than its numeric value.

File: src/graphql/shared/types/object/btc-wallet.ts (~line 49)
Fix: Return Number(balance.asCents(8)) — the raw satoshi count as a number.

Send & Receive Flows

When a JMD user enters an amount (e.g., JMD$1,000) to create a Lightning invoice or send a payment via the USD wallet, the conversion from JMD to USD cents produces a JavaScript float (e.g., 625.78...). This float is sent directly to the GraphQL API, which expects an integer. The API truncates (e.g., to 625), and the user receives or sends a slightly different amount than intended — consistently losing a small amount to rounding error on every transaction.

Root cause: convertMoneyAmount() in use-price-conversion.ts only calls Math.round() when the target currency is BTC. USD conversions are left as floats.

Files:
app/hooks/use-price-conversion.ts (lines 74–81) — missing Math.round() for USD target
app/screens/receive-bitcoin-screen/payment/payment-request.ts (lines 155–167) — float passed to lnUsdInvoiceCreate
app/screens/send-bitcoin-screen/payment-details/intraledger.ts (lines 58–78) — float passed to intraLedgerUsdPaymentSend

Fix: Apply Math.round() to all currency conversion results, not just BTC. Additionally, add a defensive Math.round() at the API mutation call sites so the fix survives future refactoring.

Backend dependency: The backend price service (src/services/price/index.ts) currently derives the JMD/USD rate by triangulating through BTC rather than using the static ExchangeRates.jmd.sell value from config/yaml.ts. This compounds the mobile rounding error because the exchange rate itself is imprecise. The price service fix (see item 3 below) must land before mobile amounts can be validated as correct.

Transaction History

Transaction history is broken at two layers:

Backend layer (lnflash/flash repo):
The transaction history adaptor (src/app/wallets/get-transactions-for-wallet.ts) has four compounding bugs:
Hardcodes displayCurrency: "USD" regardless of the user's actual display currency
Uses the wrong exponent offset when converting the price, producing amounts off by a large factor
Calls Math.floor() on the price, which truncates sub-dollar JMD prices to 0
Does not apply the JMD/USD conversion — JMD users receive USD amounts from the API

Additionally, the price service derives JMD rate via BTC triangulation (src/services/price/index.ts) — it should use the static ExchangeRates.jmd.sell value from config/yaml.ts directly.

Files:
src/app/wallets/get-transactions-for-wallet.ts — fix displayCurrency, offset, Math.floor, and JMD conversion
src/services/price/index.ts — use ExchangeRates.jmd.sell instead of BTC triangulation

Mobile layer (lnflash/flash-mobile repo):
The TxItem component ignores the settlementDisplayAmount field already returned by the API (which contains the correct historical JMD amount locked in at settlement time) and instead re-computes the JMD amount using the current BTC price. This means every BTC transaction in history shows a different JMD amount each time the price moves. The transaction detail screen already does this correctly (using settlementDisplayAmount) — the list should match it.

Additionally, for USD wallet transactions, TxItem multiplies settlementAmount by 100 (tx.settlementAmount * 100), but settlementAmount is already in cents. This inflates all USD wallet transaction amounts by 100×.

For BTC wallet transactions from the Breez SDK, useBreezPayments.ts hardcodes settlementDisplayCurrency: "USD" and converts to USD, ignoring the user's JMD preference entirely.

Files:
app/components/transaction-item/TxItem.tsx (lines 42–60) — use settlementDisplayAmount / settlementDisplayCurrency instead of live price reconversion
app/components/transaction-item/TxItem.tsx (line 55) — remove * 100 for USD wallet amounts
app/hooks/useBreezPayments.ts (lines 13–18, 46–48) — use DisplayCurrency target and user's currency code, not hardcoded USD


Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions