Skip to content

feat(tokens): add How Market Cap Works explainer - #1649

Merged
bmc08gt merged 8 commits into
code/cashfrom
feat/market-cap-explainer
Oct 2, 2026
Merged

bmc08gt merged 8 commits into
code/cashfrom
feat/market-cap-explainer

Conversation

@bmc08gt

@bmc08gt bmc08gt commented Oct 2, 2026 •

Copy link
Copy Markdown
Collaborator

Adds a "How Market Cap Works" screen to token info, built from the v0.1 spec (section 10776:1757, screens 10761:2077 and 10761:1773). It shows what the holder's balance would be worth if the coin's reserve grew to a chosen size.

The math is in BondingCurveProjection (com.flipcash.app.tokens.bondingcurve), on top of new reserve and supply conversions on DiscreteBondingCurve (exposed through Estimator). The slider axis is reserve in USD, positioned by log(reserve). Supply at a stop comes from the curve's supply-from-value, and "would be worth" is the fee-free sell value of the held quarks at that supply. Display goes through the preferred currency rate with Fiat.convertingTo; the math stays in USD. The acceptance vectors (1.25M supply, 12,400 held) match iOS:

Stop Price Worth
Today ($22.7K reserve) $0.02994 $369.18
$5K $0.0144 $177
$100K $0.0977 $1,205
$1M $0.887 $10,941
$10M $8.78 $108,304

Stops are $5K, $100K, $1M and $10M. While the top stop is at most twice today's reserve, a stop 10× higher is added, capped at the reserve where all 21M tokens are sold, so Today always has room to slide up.

The screen

  • Material 3 Slider with a custom thumb: a white capsule that grows and turns translucent while pressed, to match the iOS 26 thumb. The track runs continuously under it. Haptic tick at each stop, and it springs back to Today on release.
  • Colors come from the theme: success for the curve, slider fill, row icon and a gain; errorText for a loss; surfaceVariant for the icon disc and inactive track.
  • The chart's x range covers both the selection and Today. Sizing it from the selection alone pushed Today's dot off the right edge when sliding below Today.
  • Axis labels are measured; Today always draws, and any label within 6dp of a drawn one is dropped.
  • Reserve labels use Fiat.abbreviated(), so they show three significant digits ($1.86M, not $1.9M).

AnimatedNumberText now rolls digits the way SwiftUI's .numericText() does, and the explainer uses it. This changes every caller, not just the explainer:

  • Slots are keyed by role (prefix, integer digit from the point, point, fraction digit, suffix), so a digit keeps its place when the string grows or shrinks.
  • Each digit springs, can be retargeted mid-roll, travels at most 0.3 of the glyph height, and blurs on API 31+ (fade only below).
  • Digits are fixed-width at the widest digit's width. The AutoSize path measures with that width so it does not overflow.
  • It still mounts the roller only after the first change, and a settled number is one node per character.

Other callers: TokenCard, TokenBalanceRow, CurrencyAppreciationLabel, DescriptionSelectionScreen, ReportDetailsScreen, MarketCapSection, LinkCardView, AmountArea.

Adds CodeTypography.displayCompact (30sp Bold) to mirror iOS's appDisplayCompact, for the amount readout. The Market Cap header loses its chevron.

Tests cover the projection math and stops, the label layout, the roll motion (NumberRollMotionTest), the existing AnimatedNumberTextTest, and the view model (currency conversion, appreciation sign, preformatted labels).

iOS counterpart: code-payments/code-ios-app#946.

@bmc08gt bmc08gt self-assigned this Oct 2, 2026
@github-actions github-actions Bot added type: feature New functionality area: ui Compose UI, theme, components, resources area: tokens Token accounts, balances, token info and removed type: feature New functionality labels Oct 2, 2026
30sp Bold, mirroring iOS appDisplayCompact. Nothing sat between the 24sp and
40sp display styles.
Opens from the Market Cap header on token info and from a new row under the
range chips (spec section 10776:1757). A slider over reserve shows what the
balance would be worth, springing back to Today on release. Readouts roll
the way SwiftUI's numericText does.
@bmc08gt
bmc08gt force-pushed the feat/market-cap-explainer branch from 2366890 to 4d4298f Compare October 2, 2026 00:25
@github-actions github-actions Bot added the type: feature New functionality label Oct 2, 2026
Replace ExplainerGreen with CodeTheme.colors.success (the token the
existing price chart uses) and successText for positive appreciation.
The icon disc and slider inactive track use surfaceVariant, and the
slider's glass thumb tint uses toggleUncheckedTrackColor. The curve icon
drawable is tinted from code.
AnimatedNumberText now uses the market cap explainer's roll, and the
explainer calls it directly. ExplainerNumberText is removed.

Slots are keyed by role (prefix, integer digit from the point, point,
fraction digit, suffix), so a digit keeps its slot when the string
grows or shrinks. Each digit is a spring that is retargeted mid-roll
with its current offset and velocity, travels at most 0.3 of the glyph
height, and blurs up to 11dp on API 31+. Below 31 it fades only.
Digits take the widest digit's width; AutoSize sizes the string with
every digit set to that width so fixed-width digits do not overflow.

The roller still mounts only after the first change, seeded with the
value on screen. The outgoing glyph leaves the tree once it has faded,
and an emptied slot leaves it once its width reaches zero, so a settled
number is one node per character.

The pure motion helpers move to NumberRollMotion with their tests.
…ngCurveProjection

MarketCapExplainerCalculator moves to com.flipcash.app.tokens.bondingcurve
as BondingCurveProjection, and the view model's state field becomes
projection. The view model stays in shared.

ExplainerCurrency, ExplainerFormat, ExplainerLabelBox and
layoutExplainerLabels are only used by the explainer UI, so they move to
the feature's internal/explainer package as ExplainerFormatting. Their
tests move with them to ExplainerFormattingTest; the formatted
assertions that ran on calculator output now use the same literal
values, and the projection test keeps the raw-number checks.
ExplainerCurrency only held a Rate. Its convert duplicated
Fiat.convertingTo, and its compact reserve label duplicated core's
Fiat.abbreviated.

The labels that do not move with the slider (tick labels, tokens held,
price per token and both supply shares) are now formatted in
MarketCapExplainerViewModel, and they are recomputed when the projection
loads or the preferred rate changes. The reserve readout, scrub label and
worth follow the finger, so Content derives them from the snapshot with
convertingTo(rate) and abbreviated().

Reserve labels now use abbreviated()'s three significant digits instead
of one decimal: $1.86M rather than $1.9M, and $1.23K rather than $1.2K.
The ticks and Today read the same.

ExplainerFormatting.kt only holds the label layout now, so it becomes
ExplainerLabelLayout.kt. The price and percent tests move to
MarketCapExplainerViewModelTest.
@bmc08gt
bmc08gt merged commit e41372c into code/cash Oct 2, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: tokens Token accounts, balances, token info area: ui Compose UI, theme, components, resources type: feature New functionality

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant