Skip to content

feat: GET /premium/:assetA/:assetB with staleness enforcement - #567

Merged
Miracle656 merged 1 commit into
Divinity-Labs:mainfrom
mansur-codes:feat/issue-217-premium-endpoint
Oct 26, 2026
Merged

Miracle656 merged 1 commit into
Divinity-Labs:mainfrom
mansur-codes:feat/issue-217-premium-endpoint

Conversation

@mansur-codes

Copy link
Copy Markdown
Contributor

Summary

Adds GET /premium/:assetA/:assetB — returns the most recent on-chain price and best CEX price for a pair, the spread in basis points, and both sources and timestamps. Refuses to answer when either side is older than a configurable staleness bound (PREMIUM_STALENESS_SECONDS), naming which side was stale. Network is required via ?network= and validated on every request. OpenAPI spec updated and env drift guard satisfied.

Related issue

Closes #217

Type of change

  • Bug fix
  • New feature
  • Refactor
  • Docs
  • Tests
  • CI / tooling

Checklist

  • I have read CONTRIBUTING.md
  • npx tsc --noEmit passes
  • npm run build passes
  • I added / updated tests where relevant
  • I updated docs where relevant

@drips-wave

drips-wave Bot commented Oct 19, 2026

Copy link
Copy Markdown

@mansur-codes Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@mansur-codes
mansur-codes force-pushed the feat/issue-217-premium-endpoint branch from 525bad5 to 9ed01ac Compare October 25, 2026 14:50
@Miracle656
Miracle656 merged commit 7a39a44 into Divinity-Labs:main Oct 26, 2026
3 checks passed
@Miracle656

Copy link
Copy Markdown
Collaborator

Merged into main (f9f3ac0). tsc clean, 1,825 tests pass, no conflicts.

The staleness design is the good part. Three decisions worth naming:

  • It refuses rather than answers. A 503 with STALE_DATA beats returning a number with a quiet "this might be old" field, because the field gets ignored and the number gets traded on.
  • staleSide names which leg went quiet — onChain, cex or both. That is the difference between "a venue stopped publishing" and "our indexer stalled", and it is the first thing you want to know when this fires at 3am.
  • The bound is configurable rather than a magic 60 in the middle of a handler.

One thing I added: the API-key scope. The route registered as:

}>('/premium/:assetA/:assetB', async (req, reply) => {

with no config, so it answered any key regardless of what that key was scoped for. Every other price read in src/routes declares config: { scope: 'read:prices' } — basket.ts, benchmark.ts, book.ts, oracle.ts — so this one was the exception. It now matches them.

Worth knowing why this keeps happening: scope lives in the route's options object, which is easy to omit entirely when a handler is written straight after the path string. There is no test that fails when a route forgets one — #623 built exactly that guard for the GraphQL side (every field must declare a scope, enforced against the schema), and the REST side would benefit from the same thing. If you want a follow-up with real value, that is it: boot the app, enumerate registered routes, and fail when a non-internal one has no scope. It would have caught this, and it would have caught the five that #429 and #437 dropped earlier this week.

A note on timing: Lens's Render service is suspended at the moment, so /premium will not be reachable until it is brought back up.

https://claude.ai/code/session_01USgemLt4Rnz4SGB1Srf3GB

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.

CEX prices are ingested and nothing compares them to the chain

2 participants