Repository navigation
my redirect moved nova's URL and stranded its bench rows - #3178
Closed
Flotapponnier wants to merge 5 commits into
Closed
Flotapponnier wants to merge 5 commits into
Flotapponnier wants to merge 5 commits into
Conversation
… copied
I said Nova could not go on bench 295 or the perp-cost tool because it
lists neither BTC nor ETH. That was wrong, and wrong for a reason worth
recording: I had read app.nova.markets/api/markets, which is the
Hyperliquid-routed FRONTEND list. The gTrade deployment is a different
thing and its pair 0 is BTC/USD, pair 1 ETH/USD.
All five selectors the leverage source uses answer on Nova's diamond, so
the reader takes the venue and the diamond as arguments instead of
hardcoding Gains', the same shape as the volume source. Measured on
chain 2026-10-22, both deployments, both markets:
standard pair 1.1x-200x, threshold 90/75 between 25x and 60x
gains 0.035% a leg nova 0.050% a leg
x500 degen 500x fixed, threshold 80% flat
gains 0.020% a leg nova 0.020% a leg
So it is one contract priced twice: identical thresholds and bounds, a
dearer standard pair. The fee sits inside the liquidation trigger, so
Nova leaves strictly less room at every leverage (10x: 8.90% against
8.93%; 200x: 0.275% against 0.305%).
THE NEAR MISS. perp-cost-model carries a spreadBps per row, and Gains'
is 1.0. Copying it would have looked right beside the identical
thresholds and been wrong: word 7 of pairs() is the pair spread at 1e10
and reads 100000000 on Gains but 0 on Nova. Nova is 5.0 bps a leg, not
5.5, and the earlier "43% dearer" I quoted from the taker fee alone is
really 25% once the spread is counted on both sides. The file's own
header says two venues were left out rather than guessed at; this is why.
Nova's figures were read a day after everything else in that file, so
rows can now carry their own measuredAt and the footer no longer claims
one date for figures gathered on two.
Found while editing bench 295, both pre-existing:
- Its methodology and Gains' formula said the bench reads "pairs 300 and
313, the x500 cohort rather than the standard pairs". It reads all
four: gainsPairs is {BTC: [0, 300], ETH: [1, 313]} and the publisher
picks per leverage tab. Corrected, and split in two because the
parameters and the pair list are separate facts.
- The prose said "four venues" in six places and, worse, claimed
"Papertrade leaves the most room and Gains the least". With Nova that
ordering is false. Both fixed.
Cache keys: all three move this time. Bench 295 declares dimensions, so
bench-filters matters here where it did not for 041 and 116, and a
provider missing from it is invisible on every tab. The cache-key test
did not know that key existed; it does now.
Tests: the two deployments agree on leverage and disagree on fee, the
degen rows are identical as their label claims, Nova's spread is zero and
not Gains', Nova leaves less room at every shared leverage, and the pair
INDICES are checked against each venue's own registry rather than trusted
to stay aligned, because a wrong index would publish another asset's
terms plausibly. Each verified against a mutation, including the copied
spread.
Review of my own previous commit. fetchGains became fetchGtrade but its helpers kept the Gains name, so the code read s.gainsCall(novaDiamond, ...) and gtradePairs was still gainsPairs while being the pair map for both deployments. Half a rename misleads more than either option. 24 references, compiler-verified. gainsArbRPC and gainsDiamondArb keep their names because they really are about Gains: one is the RPC that harness has always used, the other is Gains' own diamond address.
Audit of my own change, three things on the perp-cost page. "Nine of the ten products below charge a share of your notional" is now eleven of twelve. The meta description said "ten perp venues". The page listed ten PRODUCTS across nine venues, so it was already wrong, and Nova makes it accidentally right at ten venues and twelve products. A count that is only correct by coincidence is worse than no count, so it names no number now. And the footer gives every venue a paragraph saying how its figures were read, which left Nova's two rows marked "chain" with no account of what that meant. Nova now has its own, including the thing worth writing down: word 7 of pairs() is the pair spread at 1e10 and reads zero on Nova where Gains reads 0.0100%, so the row carries zero rather than the neighbouring value. Copying it would have put Nova at 5.5 bps a leg instead of 5.0 and looked right beside the identical thresholds. The existing Gains paragraph already documented that 0.0100% pair spread, which is independent confirmation that word 7 is the field I read rather than my own inference about it.
Found by reading the gauge after deploying: perp_leverage_max published 500.001 for Nova where Gains reads 500. Raw integers at 1e3: gains 300/313 min 500000 max 500000 min == max, genuinely fixed nova 300/313 min 500000 max 500001 a band one thousandth wide My own comparison script printed both as "500.00" because it formatted to two decimals. The difference was in the data from the first read and the display hid it, so three things shipped wrong in #3168: - the model carried maxLeverage 500 for a row whose gauge says 500.001, which had this page and bench 295 disagreeing about one venue - the row called itself "fixed, same terms as Gains". It is neither: its bounds do not coincide, so it is not fixed, and its ceiling is not Gains'. Now "x500 degen, Gains' terms at 500.001x" - the bench formula claimed "leverage bounds identical to Gains". The standard pair's are; the degen pair's maximum is not And the test asserted the two maxima were EQUAL. It passed because both rows carried the same wrong number, which is the failure mode worth naming: a test comparing two values I wrote by hand confirms I was consistent, not that I was right. It now pins 500 and 500.001 separately, asserts Gains' bounds coincide while Nova's do not, and the harness checks the same two figures against the contract on OCB_LIVE. The test log prints leverage to three decimals now, since the contract stores it at 1e3 and rounding to two is the thing that cost this. Nobody trades the difference. It is carried because the published gauge carries it, and because a fork that edited one digit of one parameter edited it on purpose.
Florent noticed that /products/nova-markets shows one bench, and that the one it shows says "awaiting". The first half is mine. #3157 closed the duplicate page with a hardcoded permanentRedirect on slug === "nova". That fixed the URL and nothing else: a product page's bench appearances are keyed by PROVIDER SLUG, and Nova has two. Bench 030 lists it as "nova-markets", benches 041, 116 and 295 as "nova". So the redirect sent readers to a page that lists bench 030 and none of the three the venue was just added to. The repo already had the mechanism, PRODUCT_ALIASES in providers.ts, and its own comment describes this exact situation for the -cold slugs: "Folding them here collapses their bench appearances onto the vendor's page and leaves one page per vendor". It does strictly more than the redirect, because canonicalize() moves the bench rows too, and the generic alias-to-canonical 308 already in the route then handles the URL without a special case. Both hardcoded redirects come out. The test I wrote with that redirect asserted the hardcoded line was present, so it pinned the mechanism I had reached for rather than the property that matters, and it would have passed forever while the bench rows stayed stranded. It now asserts canonicalize("nova") resolves to nova-markets, that the canonical slug is a fixed point so the redirect cannot loop, and that the generic 308 is still in both call sites. The two-call-site check matches on the shared comparison rather than a variable name, because the two sites spell the variable differently. Not fixed here, because it is a judgement on a bench I did not build: bench 030 renders Nova as "$0, ranked 105 of 105" while the product page says "awaiting". The product page's hasData requires p50 !== 0 unless the spec sets zero_is_a_value, and bench 030 does not set it, so 34 of its 105 providers read as missing data on their product pages while the bench ranks them on a measured zero.
Collaborator
Author
|
Superseded. #3176 landed the same alias as a sitemap hotfix while this was open, and the remaining parts (removing the now-redundant hardcoded redirect, the test fix, and the providers-v17 bump this was missing) move to the replacement branch off current dev. |
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.
Answers the first half of what you spotted: why /products/nova-markets lists one bench.
It is my regression
#3157 closed the duplicate page with a hardcoded
permanentRedirectonslug === "nova". That fixed the URL and nothing else. A product page's bench appearances are keyed by provider slug, and Nova has two:nova-marketsnovaSo the redirect sent readers to a page listing bench 030 and none of the three the venue had just been added to. Measured on prod and staging: 3 bench links, and they are
hyperliquid-frontends,perp-feesandperp-funding(the last two from the perp venue dashboard, benches Nova is not even on).The repo already had the right mechanism
PRODUCT_ALIASESinproviders.ts, whose own comment describes this exact case for the-coldslugs:It does strictly more than a redirect, because
canonicalize()moves the bench rows as well, and the generic alias-to-canonical 308 already in the route then handles the URL. Both hardcoded redirects come out.The test was wrong in an instructive way
It asserted the hardcoded
slug === "nova"line was present in the route. That pinned the mechanism I had reached for rather than the property that matters, so it would have passed forever while the bench rows stayed stranded.It now asserts
canonicalize("nova")resolves tonova-markets, that the canonical slug is a fixed point so the redirect cannot loop, and that the generic 308 is still in both call sites. That last check matches on the shared comparison rather than a variable name, because the two sites spell it differently (resolvedandp) and my first attempt at it failed for that reason.Verified against a mutation: dropping the alias fails the collapse test.
The second half, deliberately not fixed here
Bench 030's own page renders Nova as "$0, ranked 105 of 105". The product page says "awaiting / data warming up". Same number, two stories.
The product page's
hasDatais(rank > 0 || ...) && (p50 !== 0 || zeroIsAValue). Nova's p50 on 030 is 0 because it had 0 fills on the last complete UTC day (its 7-day mean is $1.42), and bench 030 does not setzero_is_a_value. Only 4 of 253 specs do.So 34 of that bench's 105 providers read as missing data on their product pages while the bench ranks them on a measured zero. The one-line fix is
zero_is_a_value: trueon bench 030, but that flag also feedscomputeFieldStats, andstats.tsrecords what happened last time it was switched on somewhere (bench 282 printed "Best $0"). The guard added then issuccessRate > 0, which cannot discriminate here: bench 030'ssuccessquery is a harness-freshness check that returns 1 for every builder. Your call, not mine to make silently.Gate: validate 253 specs, typecheck, lint at its 9 pre-existing warnings, test 713 pass.