feat(activity): lead activity rows with the viewer's own currency - #1388
Merged
Conversation
An activity row showed whatever currency the payment was denominated in, so a tip sent in pesos read as pesos to someone who thinks in dollars. The row now leads with the viewer's currency and keeps what actually moved, flagged, on a second line when the two differ. A USDF payment converts from the USD value it settled at rather than from today's rate, so a $5 tip keeps reading $5 however far the peso moves. Any other mint has no such anchor — `localFiatOf` fills its `underlyingTokenAmount` with that mint's own quarks, not dollars — so it crosses through current rates instead, and falls back to a single line when there is no rate to cross with. The conversion runs at render time rather than in the mapper. The feed re-maps only when a profile or token cache lands, so a currency changed from a screen stacked over the list would never reach the rows otherwise. Only the preferred rate is observed; the rates map is read as a snapshot, so an FX tick doesn't recompose every visible row. Convert rows are untouched — they keep the second line for the swap fee.
bmc08gt
added a commit
to code-payments/code-ios-app
that referenced
this pull request
Sep 1, 2026
An activity row showed `nativeAmount` alone — the currency the payment was denominated in — so a 7,500 peso tip read "-$7,500.00" to someone who reads money in dollars, with nothing saying it was $5. The row now leads with the amount in the viewer's currency and, only when the transferred currency differs, shows what actually moved beneath it with that currency's flag in the secondary style. Both lines carry the same sign; a payment already in the viewer's currency renders one line as before. `ExchangedFiat.forViewer(preferredRate:rates:)` is the conversion. A USDF payment converts from the USD it settled at (`onChainAmount`), so a $5 tip keeps reading $5 however far the peso moves afterwards. Any other mint has no such anchor — `onChainAmount` holds that mint's own quarks — so it crosses through today's rates instead and falls back to a single line when the source currency has no rate to cross with. Deliberately not `usdfValue`: an activity's rate is synthesized as `nativeAmount / onChainAmount` for a bonded mint, so dividing back through it returns the token count rather than dollars. The conversion runs at render time rather than where the feed is mapped. Reading `RatesController` in the row's body is what subscribes the rows to it, so a currency changed on a screen stacked over the list reaches them. Feed amounts are magnitudes with the direction carried alongside, which is why the row supplies the sign at all. `FiatAmount.formatted(signPrefix:)` leaves a value that already carries its own "-" alone rather than printing "--$5.00". Swap rows keep the second line for the swap fee, unchanged. Ports code-payments/code-android-app#1388. Android updated two row implementations and flagged both lines in the per-token history, which had always flagged the one amount it showed. iOS has a single `ActivityRow` across the Recent previews, the cross-token history and the per-token history, and none of them flag today, so only the transferred line gains a flag.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
An activity row showed whatever currency the payment was denominated in, so a tip sent in pesos read as pesos to someone who thinks in dollars. The row now leads with the viewer's currency and keeps what actually moved, flagged, on a second line when the two differ — a 7,500 ARS tip reads
-$5.00with🇦🇷 -$7,500.00underneath.Which rate converts it
A USDF payment carries its own USD value, fixed when it settled, so it converts from that: a $5 tip keeps reading $5 however far the peso moves afterwards.
Any other mint has no such anchor.
localFiatOffills a non-USDFunderlyingTokenAmountwith that mint's own quarks rather than dollars, so those rows cross through current rates instead, and drop back to a single line when the transferred currency has no rate to cross with.Where the conversion happens
At render time, in
ActivityAmount, not inTransactionItemMapper. The mapper is pure and the feed re-maps only when a profile or token cache lands, so a currency changed from a screen stacked over the list would never reach the rows. OnlyobservePreferredRate()is collected; the rates map is read as a snapshot, so an FX tick doesn't recompose every visible row.Scope
Both row implementations:
ActivityFeedRow(Wallet "Recent", the cross-token Activity history, the token-info preview) and the per-tokenFeedItemSummary, which keeps the flag on its top line since it has always flagged the one amount it showed.Convert rows are untouched — they keep the second line for the swap fee.
TransactionListItem.amountwidens fromFiattoLocalFiat, since the row now needs the entry's mint and USD value, not just its native side.