Skip to content

fix(token-prices): price a token from the median of its deepest paths - #398

Closed
tamaralipows wants to merge 1 commit into
mainfrom
tnl/token-price-depth-floor
Closed

fix(token-prices): price a token from the median of its deepest paths#398
tamaralipows wants to merge 1 commit into
mainfrom
tnl/token-price-depth-floor

Conversation

@tamaralipows

@tamaralipows tamaralipows commented Aug 3, 2026

Copy link
Copy Markdown
Contributor

Bug

Token prices come from the candidate path with the tightest round-trip spread. Spread only measures whether a probe-sized round trip returns symmetric. It says nothing about whether the rate is right, and a pool holding one side and almost none of the other quotes a wrong rate with a tight spread and deep liquidity.

On BSC such a WBNB-USDT pool priced USDT 12191x too low. No threshold on that pool alone can catch it: its reserves are internally consistent, so it looks like a legitimate pool for a token that really trades at that rate. What catches it is that no other path agrees.

The price feeds solving, not just reporting. water_fill.rs converts gas through it, which sets amount_out_net_gas — the router's ranking key. most_liquid.rs normalizes edge depth through it. hindsight's value_usd divides by it.

Fix

Rank candidate paths by the depth of their thinnest hop and simulate deepest first, until PRICED_PATHS_PER_TOKEN (5) have quoted.

From three quotes up, the token's price is their median, so a lone disagreeing pool is outvoted. Below three it is the deepest path's, since two quotes cannot outvote each other.

Depth replaces spread as the ranking key because the wrong pool wins on spread, so ranking by spread would fill the sample with it.

Depth ranks paths rather than admitting them. A hop with no depth recorded reads as zero and sorts last instead of being dropped, so a token whose pools are all thin keeps a rough price rather than losing it.

Token prices now require pool_depths, which reorders the derived schedule to spot prices → depths → token prices.

Cost

The cap counts quotes, not attempts, so a path that fails to simulate does not use up a token's budget. Worst case matches today — a token whose paths all fail tries them all, as the round-robin already did — and the common case is lower, since a token stops at five successes.

None of this is on the solve path. It runs per block in ComputationManager, and all three algorithms declare allow_stale("token_prices"), so no worker blocks on it. The one real cost is scheduling: prices now run after depths instead of alongside, so per-block freshness lags by the depth computation.

Worth a look

  • Selection changes for every token on every chain, not only mispriced ones. A token with one usable path is unaffected; a token with two gets the deeper of the two.
  • 5 is the sample size, so it is also how many independent quotes one wrong pool has to outvote.
  • Ranking by depth means a token's price can now come from a different pool than before even where nothing was wrong.

🤖 Generated with Claude Code

@tamaralipows
tamaralipows force-pushed the tnl/token-price-depth-floor branch from f32296c to cc447d8 Compare August 3, 2026 18:08
@tamaralipows tamaralipows changed the title fix(derived): require a depth floor before pricing through a pool fix(token gas price): require a depth floor before pricing through a pool Aug 3, 2026
@tamaralipows tamaralipows changed the title fix(token gas price): require a depth floor before pricing through a pool fix(derived): price a token from the median of its deepest paths Aug 3, 2026
@tamaralipows
tamaralipows force-pushed the tnl/token-price-depth-floor branch from 4a7bd89 to 81112f1 Compare August 3, 2026 20:52
@tamaralipows tamaralipows changed the title fix(derived): price a token from the median of its deepest paths fix(token-prices): price a token from the median of its deepest paths Aug 3, 2026
@tamaralipows
tamaralipows force-pushed the tnl/token-price-depth-floor branch from 9453f54 to fd7c708 Compare August 3, 2026 21:30
Token prices came from the candidate path with the tightest round-trip
spread. Spread only measures whether a probe-sized round trip returns
symmetric; it says nothing about whether the rate is right. A pool holding
one side and almost none of the other quotes a wrong rate with a tight
spread and deep liquidity, and on BSC such a WBNB-USDT pool priced USDT
12191x too low. No threshold on that pool alone can catch it, because its
reserves are internally consistent. What catches it is that no other path
agrees.

Candidates are now ranked by the depth of their thinnest hop and the
deepest PRICED_PATHS_PER_TOKEN are simulated. From three quotes up the
token's price is their median, so a lone disagreeing pool is outvoted;
below three it is the deepest path's, since two quotes cannot outvote each
other. Depth replaces spread as the ranking key because the wrong pool
wins on spread, so ranking by spread would fill the sample with it.

Depth ranks paths rather than admitting them. A hop with no depth recorded
reads as zero and sorts last instead of being dropped, so a token whose
pools are all thin keeps a rough price rather than losing it.

Token prices now require pool_depths, which reorders the derived schedule
to spot prices, then depths, then token prices.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@tamaralipows

Copy link
Copy Markdown
Contributor Author

Closing in favour of #401. This extended the bespoke path search rather than replacing it, and #401 fixes the same BSC mispricing by solving with the router instead — see Markus's point on this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant