fix(prices): NIGHT has 6 decimals — seed and map it as 1 NIGHT = 10^6 Stars - #60
Merged
Conversation
… Stars known_tokens.decimals means "base units per PRICED coin" (resolveTokenPrice() divides an asset's per-coin price by 10^decimals; the value is never rescaled again downstream). NIGHT's seed row (packages/database/migrations/000-init.sql, landed in PR #54) carried decimals=0, which prices ONE STAR (NIGHT's base unit) at NIGHT's whole-coin price of ~$0.019 instead of at ~$0.000000019 — every GET /v1/prices, GET /v1/quote and sponsorship threshold touching NIGHT was off by 10^6, and anything registered to mirror NIGHT's row (e.g. sNight on midnight-1-offers, per its own Q14) inherited the same error. Ledger source of truth: STARS_PER_NIGHT = 1_000_000 in midnight-ledger/ledger/src/structure.rs (MAX_SUPPLY = 24_000_000_000 * STARS_PER_NIGHT) — 1 NIGHT is 10^6 Stars. - packages/database/migrations/000-init.sql: NIGHT decimals 0 -> 6. USDC's placeholder row also moves 0 -> 6 (real USDC is 6 decimals on every chain it exists on, same reasoning already applied to the USDM placeholder). Faucet- minted dev/test tokens are untouched: they keep the table's DEFAULT of 0, which is genuinely correct for them (1000 base units = 1000 coins). - packages/database/price-map.ts: documented that DEFAULT_NAME_ASSET_MAP entries cannot effectively carry `decimals` — resolveAssetId() only reads that field off the PRICE_FEED_MAP-parsed override map, never off the built-in name map — so the seed row is the one place NIGHT's decimals must be correct. No behavioural change to this file. - Tests updated with the corrected numbers, computed with the same exact decimal-string arithmetic tokenPriceFromAsset() uses (no floats): packages/database/asset-prices.test.ts (NIGHT/USDC decimals, a new NIGHT per-base-unit-price test mirroring the existing USDM one), packages/node/api.test.ts (GET /v1/prices), packages/node/offers-sponsorship.test.ts and packages/batcher/sponsorship-gate.test.ts (the sponsorship gate's give_usd/want_usd, which round to 2 decimals for display — 0.01918181 -> "0.02" — since this fixture's 1,000,000 base units is exactly 1 NIGHT). - Docs (README.md, API.md, FRONTEND-API-HANDOFF.md, .env.mainnet.example, .env.preview.example, deploy/.env.example): state that `decimals` is base units per priced coin, never a colour's own display decimals, and that NIGHT is seeded at 6. Non-breaking for the API shape; behaviour-changing for NIGHT-priced quotes and sponsorship thresholds, which now move by 10^6 (that is the fix). A SEED VALUE only, no schema shape change (one-schema-file rule respected): an EXISTING database keeps its old NIGHT/USDC rows (decimals=0) until migrated by hand.
acedward
added a commit
that referenced
this pull request
Sep 3, 2026
main corrected NIGHT to 6 decimals (1 NIGHT = 10^6 Stars, PR #60), which is the follow-up this branch had filed. sNight is seeded with NIGHT's decimals by design, so the SNIGHT row moves 0 -> 6 with it and the pair stays at par; the test still asserts SNIGHT.decimals against NIGHT's ROW, not a literal, so the two can only ever move together. Conflicts, all in the seeded-token block and its documentation: * 000-init.sql — main's NIGHT/USDC 6-decimals notes kept, the SNIGHT row and its "patch this row for another network" note kept, seeded at 6. * asset-prices.test.ts — main's USDC comment and the SNIGHT expectation kept side by side; the NAME-map backstop case now uses 6. * .env.preview.example, deploy/.env.example — both paragraphs kept.
This was referenced Sep 3, 2026
Merged
Draft
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.
What / why
known_tokens.decimalsmeans base units per PRICED coin (see the table's own comment inpackages/database/migrations/000-init.sqlandresolveTokenPrice()/tokenPriceFromAsset()inpackages/database/price-map.ts): a token's price is served per base unit asasset_prices.price_usd / 10^decimals, and that value is never rescaled again downstream.NIGHT's seed row (landed in #54, "seeded asset prices") carries
decimals: 0. That prices one Star (NIGHT's base unit) at NIGHT's whole-coin price (~$0.019) instead of at ~$0.000000019 — everyGET /v1/prices,GET /v1/quoteand sponsorship threshold touching NIGHT is off by 10^6. Anything registered to mirror NIGHT's row (e.g. sNight onmidnight-1-offers, see that project's Q14 plan question) inherits the same error.Ledger source of truth:
STARS_PER_NIGHT = 1_000_000inmidnight-ledger/ledger/src/structure.rs(MAX_SUPPLY = 24_000_000_000 * STARS_PER_NIGHT) — 1 NIGHT = 10^6 Stars. Owner decision (2026-09-03): "decimals for Night = sNight = 6".Changes
packages/database/migrations/000-init.sql— NIGHTdecimals0 → 6. Reviewed the USDC placeholder row (1111…1111) too: real USDC is 6 decimals on every chain it exists on (same reasoning already applied to the USDM placeholder row), so it moves 0 → 6 as well, with the reasoning recorded in the comment. USDM was already 6. Faucet-minted dev/test tokens (WBTC, WETH, TESTTOKEN*, …) are untouched — they keep the table'sDEFAULT 0, which is genuinely correct for them (the faucet mints 1000 base units = 1000 coins).packages/database/price-map.ts—PriceMapEntry.decimalscan be set syntactically onDEFAULT_NAME_ASSET_MAPentries, but I found it would be dead there:resolveAssetId()only ever readsdecimalsoff theoverridesparameter (thePRICE_FEED_MAP-parsed map), never off the built-in name-fallback map. So the seed row is the one and only place NIGHT'sdecimalshas to be correct — documented this in a comment rather than adding a no-opdecimals: 6to the built-in map entry. No behavioural change to this file.tokenPriceFromAsset()uses (no floats in the expectations):packages/database/asset-prices.test.ts— NIGHT/USDCdecimalsnow 6; added a NIGHT per-base-unit-price test mirroring the existing USDM one (0.01918181 / 10^6="0.00000001918181", exact).packages/node/api.test.ts—GET /v1/pricesNIGHT row:decimals: 6,price_usd: "0.00000001918181".packages/node/offers-sponsorship.test.tsandpackages/batcher/sponsorship-gate.test.ts— the sponsorship gate'sgive_usd/want_usd. The proven fixture gives exactly 1,000,000 base units of NIGHT, which at 6 decimals is exactly 1 NIGHT, sogive_usdis numerically the coin price itself (0.01918181) — and both display paths (round2()inpackages/node/offer-sponsorship.ts,usd()=.toFixed(2)inpackages/batcher/celestia.ts) round that to"0.02". The underlying sponsorship decision (percentage-based, full double precision) is unaffected by the scale change — only the previously-19181.81-now-0.02 displayed USD amounts needed updating.README.md,API.md,FRONTEND-API-HANDOFF.md,.env.mainnet.example,.env.preview.example,deploy/.env.example) — state thatdecimalsis base units per priced coin, never a colour's own display decimals, and that NIGHT is seeded at 6 (1 NIGHT = 10^6 Stars), with the ledger reference.Rollout note
This changes a SEED VALUE only — no schema shape change (one-schema-file rule respected:
000-init.sqledited in place, no new migration file). An existing database keeps its old NIGHT/USDC rows (decimals: 0) after this merges; a fresh redeploy picks up 6 automatically. For a live deployment (e.g. preprod) that must not be redeployed from zero, an operator with DB access runs:(or just
WHERE name = 'NIGHT'if the USDC placeholder decimals is not wanted — see point 1 above for why I moved it too).Breaking / behaviour change
Non-breaking for the API shape —
GET /v1/pricesandGET /v1/quotereturn the same fields. Behaviour-changing for the VALUES: every NIGHT-priced quote and sponsorship threshold moves by 10^6 — that is the fix, not a regression. Anyone consuming absolute USD amounts for NIGHT (rather than ratios) will see numbers 10^6 times smaller than before, which is now correct.Testing
CI is the gate (no local docker stack was brought up, per repo convention). Ran the exact
unit-testsCI job locally (bun test packages/database packages/validator packages/node packages/offer-guard packages/batcher packages/solver-core packages/solver packages/solver-frontend packages/tests/grand-e2e, with the same Compact-artifact stub CI creates):main(see below).packages/tests/grand-e2e/lib/solver-offerfiles-real-image-secret-scan.test.ts("handles a large physical inventory and internal-link fanout without process amplification"), is pre-existing and unrelated: it creates 15,000 hard links + 1,000 symlinks and asserts a Docker-based scan completes inside a 25s budget. I reproduced the identical failure (Expected: 0, Received: 70) on a cleanmaincheckout run through the same full suite, on this same (shared, loaded) machine — it passes standalone (9/9) but is flaky under load as part of the full run, onmainas much as on this branch. Not touched by this PR's diff.packages/database/asset-prices.test.ts,price-map.test.ts,packages/node/offers-sponsorship.test.ts,packages/node/api.test.ts,packages/batcher/sponsorship-gate.test.ts,packages/batcher/price-lookup.test.ts— is 145/145 green, run in isolation and as part of the full suite, across multiple repeated runs.bun run check:pgtypes— clean, no generated-query drift from the SQL comment/value changes.bun run typecheck(backend + solver + price-feed) — 0 diagnostics in all three gates.typecheck-playgroundCI job locally (bun buildthe five api-examples entrypoints +bunx tsc -bindocs/with the same generated-contract stub) — clean.No attribution lines.