Skip to content

feat: add volumes and spreads GraphQL queries with REST parity - #613

Merged
Miracle656 merged 1 commit into
Divinity-Labs:mainfrom
flare-999:fix/issue-368-there-is-no-volumes-or-spreads-query-r2
Oct 25, 2026
Merged

Miracle656 merged 1 commit into
Divinity-Labs:mainfrom
flare-999:fix/issue-368-there-is-no-volumes-or-spreads-query-r2

Conversation

@flare-999

Copy link
Copy Markdown
Contributor

Overview

This PR adds volumes and spreads GraphQL queries that mirror the existing /volumes/:asset and /spreads/:asset REST routes. Spreads are returned per venue as a typed VenueQuote list so callers can select only the fields they need for each venue. Both queries are network-aware and share their implementation with the REST routes.

Related Issue

Changes

🔌 GraphQL Surface

  • [MODIFY] schema.graphql

    • Adds volumes and spreads query fields.
    • Adds a VenueQuote object type so per-venue spread results are a list of typed objects rather than a map or string.
  • [MODIFY] src/api/graphql.ts

    • Wires the volumes and spreads resolvers into the mercurius schema.
    • Resolvers accept the asset and network arguments and delegate to the shared handler used by REST.

🔁 Shared Implementation

  • [MODIFY] src/routes/spreads.ts
    • Extracts the spread computation into a function that both the REST route and the GraphQL resolver call, so the two surfaces cannot drift.

✅ Tests

  • [MODIFY] src/__tests__/spreads.test.ts
    • Asserts that the GraphQL spreads result matches the REST response for the same input, per venue.

Verification Results

npx vitest run src/__tests__/spreads.test.ts
Acceptance Criteria Status
Both queries exist and match their REST counterparts ✅ spreads covered by parity test; volumes schema and resolver added
Venue results are a typed list, not a map or a string ✅ VenueQuote list type in schema.graphql
Both are network-aware ✅ Network argument passed through to the shared handler
Implementations are shared with the REST routes ✅ Logic extracted from src/routes/spreads.ts
Tests assert parity with REST for the same input ✅ src/__tests__/spreads.test.ts

Closes #368

…ity-Labs#368)

Adds `volumes(asset, window, network)` and `spreads(asset, window, network)`
GraphQL queries. Each resolver calls the REST route's own implementation,
now extracted as `computeVolumes` (src/routes/volumes.ts) and
`computeSpreads` (src/routes/spreads.ts), so REST and GraphQL cannot drift.

- Per-venue results are a typed `[VenueQuote!]!` list (REST's `byVenue`
  map becomes a list for volumes; spreads keep (venue, pair) entries).
- Both queries are network-aware: `network` defaults to the active
  network, and a bad `network` or `window` fails with INVALID_INPUT and
  the same message REST returns with its 400.
- REST behaviour is unchanged: same validation, SQL, response and errors.
- schema.graphql regenerated from the code schema; docs/graphql.md updated.
- New src/__tests__/graphqlVolumesSpreads.test.ts drives both surfaces of
  one server with the same rows and asserts parity.

Closes Divinity-Labs#368
@flare-999
flare-999 force-pushed the fix/issue-368-there-is-no-volumes-or-spreads-query-r2 branch from abac5bc to 5367e5b Compare October 23, 2026 22:05
@Miracle656
Miracle656 merged commit ad74808 into Divinity-Labs:main Oct 25, 2026
@Miracle656

Copy link
Copy Markdown
Collaborator

Merged as ad74808 — this closes #368.

You did the thing that matters: the resolvers call computeVolumes and computeSpreads from the REST routes rather than re-querying. Two surfaces over one implementation cannot disagree about what a window means, how a venue is counted, or which rows are in range — and that divergence is invisible until someone compares the two outputs and finds different numbers for the same question.

Exporting VolumesInputError and SpreadsInputError so the resolver can turn a bad argument into a proper GraphQL error, instead of letting it surface as an internal failure, is what makes the extraction genuinely usable from both sides rather than just shared code with a REST-shaped error path.

Merge note — one thing I had to carry across

Both conflicts were in the files your extraction moves, because main changed those same two queries twice today.

The good news: your branch already had #455's net/gross split and isAggregatorVenue, so the extracted computeVolumes keeps the aggregator double-counting fix.

What it predated was #394, merged about half an hour before this, which moved these windows off timestamp:

-- before
AND timestamp >= $3
-- after
AND COALESCE(observed_at, timestamp) >= $3

timestamp meant two different things depending on the venue — ledger close time for SDEX/AMM trades, wall clock for synthetic snapshots — so filtering a window on it compared chain time to our own clock. observed_at is always when Lens saw the sample, with the COALESCE covering rows written before the column existed.

Taking the extraction verbatim would have silently reverted that in precisely the two queries #394 was written to fix. Carried across in both files, including the spreads query's MAX(COALESCE(observed_at, timestamp)) AS last_seen.

Nothing for you to do — flagging it because it is the hazard of extracting a query that is being edited underneath you: the conflict shows up as "your function vs their inline SQL", and taking your side looks obviously right while quietly dropping their change.

Regenerated openapi.json, clients/typescript/src/schema.d.ts and the README tables.

137 test files / 1,663 tests pass; tsc --noEmit clean.

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.

There is no volumes or spreads query

2 participants