Skip to content

feat(activity): lead activity rows with the viewer's own currency - #1388

Merged
bmc08gt merged 1 commit into
code/cashfrom
feat/activity-row-viewer-currency
Sep 1, 2026
Merged

feat(activity): lead activity rows with the viewer's own currency#1388
bmc08gt merged 1 commit into
code/cashfrom
feat/activity-row-viewer-currency

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Sep 1, 2026

Copy link
Copy Markdown
Collaborator

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.00 with 🇦🇷 -$7,500.00 underneath.

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. localFiatOf fills a non-USDF underlyingTokenAmount with 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 in TransactionItemMapper. 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. Only observePreferredRate() 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-token FeedItemSummary, 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.amount widens from Fiat to LocalFiat, since the row now needs the entry's mint and USD value, not just its native side.

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 bmc08gt self-assigned this Sep 1, 2026
@github-actions github-actions Bot added type: feature New functionality area: payments Payments, transfers, intents, billing labels Sep 1, 2026
@bmc08gt
bmc08gt merged commit 6d59b6d into code/cash Sep 1, 2026
3 checks passed
@bmc08gt
bmc08gt deleted the feat/activity-row-viewer-currency branch September 1, 2026 16:07
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: payments Payments, transfers, intents, billing type: feature New functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant