Skip to content

Support multichain V2 APIs with V1 compatibility - #151

Closed
findolor wants to merge 2 commits into
mainfrom
arda/multichain-api-chain-selection
Closed

findolor wants to merge 2 commits into
mainfrom
arda/multichain-api-chain-selection

Conversation

@findolor

@findolor findolor commented Jul 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Resolve all conflicts with current main and make chain selection registry-driven across swaps, tokens, token details/proofs/wrap ratios, orders, trades, vaults, prices, attribution reporting, analytics, and response caches.
  • Mount canonical /v2 routes while retaining existing /v1 mounts for compatibility.
  • Require chainId for address-, order-, transaction-, and swap-scoped operations when more than one network is configured. When the registry has exactly one network, omission resolves to that network for backwards compatibility.
  • Treat omitted chainId on list endpoints as an all-configured-networks query; validate every explicit chain against the active remote registry.
  • Include chainId in chain-sensitive request/response types and analytics events.
  • Scope token/address lookup maps, order and trade wrap-ratio maps, SDK queries, attribution workers, and cache keys by chain to prevent same-address collisions across networks.
  • Add a migration that makes wrapped exchange-rate snapshots unique by (chain_id, share_token_address, block_number). Historical rows are assigned Base chain 8453 because they predate multi-network operation.
  • Update OpenAPI, integration documentation, and smoke checks to use the chain-aware v2 contract.

Compatibility contract

  • Existing v1 routes remain available.
  • A single-network registry preserves existing clients that omit chainId.
  • Clients should start sending chainId now; omission becomes ambiguous and returns a 400 once multiple networks are configured.
  • Base/8453 references remaining in Rust are schema examples, test fixtures, or the explicit legacy-data migration, not runtime network selection.

Validation

  • nix develop -c cargo fmt -- --check
  • nix develop -c cargo check
  • nix develop -c cargo test — 439 passed
  • Cross-chain wrapped snapshot regression test
  • nix develop -c rainix-rs-static
  • Oracle-enabled v1/v2 calldata attribution integration test
  • nix develop -c mdbook build docs
  • Final diff/conflict-marker checks against current main

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This PR removes fixed chain-id usage, resolves chain IDs from configuration, and threads chain-aware filtering through swap, orders, trades, tokens, and vault routes. It also updates OpenAPI metadata, request/response types, caching, and tests.

Changes

Chain-ID Multi-chain Support

Layer / File(s) Summary
OpenAPI and route surface
src/main.rs, src/routes/*/mod.rs, src/routes/tokens.rs
Adds the v2 swap quote path, mounts v2 route groups, and exposes v2 route entrypoints alongside updated OpenAPI wiring.
Chain-id helpers and request types
src/routes/mod.rs, src/types/*.rs
Adds chain-id resolution helpers and new/updated request, query, and response fields for chain-aware routing and schemas.
Orders and trades chain filtering
src/routes/orders/*, src/routes/trades/*
Threads optional chain filters through orders and trades handlers, data sources, cache keys, mocks, and tests.
Swap quote and calldata chain flow
src/routes/swap/*
Makes swap quote and calldata processing chain-aware end to end, including validation, caching, responses, and route tests.
Vault chain filtering
src/routes/vaults.rs
Adds optional chain filtering to vault queries and per-chain aggregation for totals, with matching test updates.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Routes as route handler
  participant Helpers as resolve_required_chain_id
  participant YAML as RaindexYaml
  participant DataSource as Raindex data source
  participant Response

  Client->>Routes: request with optional chainId
  Routes->>Helpers: resolve or validate chain_id
  Helpers->>YAML: read configured networks
  YAML-->>Helpers: configured chain ids
  Helpers-->>Routes: resolved chain_id or filter
  Routes->>DataSource: query with chain_ids / chain_id
  DataSource-->>Routes: filtered results
  Routes-->>Response: JSON with chain-aware fields
Loading

Possibly related PRs

Suggested reviewers: 0xgleb, JuaniRios, hardyjosh

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main change: adding multichain V2 APIs while preserving V1 compatibility.
Description check ✅ Passed The description directly explains the multichain API changes, V1 compatibility, chain selection, routing, caching, migrations, and validation.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch arda/multichain-api-chain-selection

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/routes/mod.rs (1)

15-70: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add direct unit tests for the new chain-id resolution helpers.

configured_chain_ids, validate_chain_id, resolve_required_chain_id, and optional_chain_ids_filter are pure functions that gate 400 vs. 500 behavior across every swap and vault endpoint, but no tests target them directly in this file. Existing swap/vault tests hardcode chain_id: Some(8453) and bypass this resolution logic entirely, so edge cases (zero configured networks, multiple configured networks, unsupported requested chain) aren't exercised.

Consider adding a #[cfg(test)] module here covering:

  • resolve_required_chain_id: no chain requested + exactly one configured network → Ok.
  • resolve_required_chain_id: no chain requested + zero configured networks → Internal.
  • resolve_required_chain_id: no chain requested + multiple configured networks → BadRequest.
  • validate_chain_id / optional_chain_ids_filter: unsupported chain id → BadRequest.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/mod.rs` around lines 15 - 70, Add direct unit tests in this module
for the chain-id helper functions `configured_chain_ids`, `validate_chain_id`,
`resolve_required_chain_id`, and `optional_chain_ids_filter`, since they control
the 400 vs 500 behavior. Create a `#[cfg(test)]` module that exercises
`resolve_required_chain_id` with no requested chain and exactly one configured
network, zero configured networks, and multiple configured networks, and verify
the expected `Ok`, `Internal`, and `BadRequest` results. Also add coverage for
`validate_chain_id` and `optional_chain_ids_filter` with an unsupported chain id
to confirm they return `BadRequest`.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/routes/mod.rs`:
- Around line 15-70: Add direct unit tests in this module for the chain-id
helper functions `configured_chain_ids`, `validate_chain_id`,
`resolve_required_chain_id`, and `optional_chain_ids_filter`, since they control
the 400 vs 500 behavior. Create a `#[cfg(test)]` module that exercises
`resolve_required_chain_id` with no requested chain and exactly one configured
network, zero configured networks, and multiple configured networks, and verify
the expected `Ok`, `Internal`, and `BadRequest` results. Also add coverage for
`validate_chain_id` and `optional_chain_ids_filter` with an unsupported chain id
to confirm they return `BadRequest`.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1f2e9184-28f3-423e-a8c7-c2b6d2f425bf

📥 Commits

Reviewing files that changed from the base of the PR and between cecf8e6 and cfbffc3.

📒 Files selected for processing (8)
  • src/main.rs
  • src/routes/mod.rs
  • src/routes/swap/calldata.rs
  • src/routes/swap/mod.rs
  • src/routes/swap/quote.rs
  • src/routes/vaults.rs
  • src/types/swap.rs
  • src/types/vaults.rs
💤 Files with no reviewable changes (1)
  • src/main.rs

@findolor
findolor force-pushed the arda/multichain-api-chain-selection branch from cfbffc3 to e22859b Compare July 6, 2026 09:34
@findolor findolor changed the title Support chain selection in swap and vault APIs Support multichain V2 swap and vault APIs Jul 6, 2026
@findolor
findolor force-pushed the arda/multichain-api-chain-selection branch 2 times, most recently from 7c99f00 to d1d967f Compare July 6, 2026 11:09
@findolor findolor changed the title Support multichain V2 swap and vault APIs Support multichain V2 APIs with V1 compatibility Jul 6, 2026
@findolor findolor self-assigned this Jul 6, 2026
@findolor
findolor force-pushed the arda/multichain-api-chain-selection branch from d1d967f to afd85b9 Compare July 8, 2026 13:24

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
src/routes/swap/calldata.rs (1)

118-118: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

unwrap_or_default() in From impls produces chain_id = 0, always overwritten.

Both From<SwapCalldataRequest> and From<SwapCalldataV2Request> set chain_id: req.chain_id.unwrap_or_default(), which yields 0 when chain_id is None. This value is immediately overridden in process_swap_calldata/process_swap_calldata_v2 (lines 179, 189). While correct today, 0 is not a valid chain ID — if the override were accidentally removed in a future refactor, the bug would be silent. Consider adding a brief comment or using a sentinel like u32::MAX to make the placeholder intent explicit.

Also applies to: 133-133, 150-150

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/swap/calldata.rs` at line 118, The `From<SwapCalldataRequest>` and
`From<SwapCalldataV2Request>` impls currently use `unwrap_or_default()` for
`chain_id`, which silently sets an invalid placeholder value of 0 before
`process_swap_calldata` and `process_swap_calldata_v2` overwrite it. Update
these conversions to make the placeholder intent explicit, either by using a
clear sentinel value or adding a brief comment рядом to the `chain_id`
assignment, and ensure the `process_swap_calldata`/`process_swap_calldata_v2`
flow remains the only place that injects the real chain ID.
src/routes/trades/get_by_tx.rs (1)

273-277: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add one assertion that chain_ids reaches the tx data source.

The tx tests all pass None, so dropping the new filter would still pass. Mirror the taker/order-hash tests by capturing _chain_ids in MockTradesDataSource and asserting Some(vec![8453]) in one process test.

Also applies to: 336-455

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/trades/get_by_tx.rs` around lines 273 - 277, The tx path is not
verifying that the new chain filter is actually forwarded into the data source.
Update the `get_trades_by_tx` flow in `MockTradesDataSource` to capture
`_chain_ids`, then add an assertion in one process test that the tx lookup
receives `Some(vec![8453])`, mirroring the existing taker/order-hash test
pattern so the filter can’t be dropped silently.
src/routes/orders/get_by_token.rs (1)

20-23: 📐 Maintainability & Code Quality | 🔵 Trivial

Growing parameter list on process_get_orders_by_token.

Chain_ids pushed this function to 8 positional args, requiring a new #[allow(clippy::too_many_arguments)]. Consider bundling address/state/side/page/page_size/denomination (and now chain_ids) into a small request struct to keep the signature maintainable as more filters get added.

Also applies to: 59-65

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/orders/get_by_token.rs` around lines 20 - 23, The signature of
process_get_orders_by_token has grown too large, forcing a too_many_arguments
allowance and making it harder to extend. Refactor this handler to accept a
small request/filter struct that groups address, state, side, page, page_size,
denomination, and chain_ids, then update the call sites and any internal
references in process_get_orders_by_token to use that struct instead of many
positional parameters.
src/routes/trades/get_by_order_hashes.rs (1)

51-63: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Read lock held across the whole request despite cloning the client.

raindex (the SharedRaindexProvider read guard) isn't scoped into a block, so it isn't dropped until the end of the async move block — i.e., after process_get_trades_by_order_hashes(...).await completes on line 59 — even though client is already an owned clone. This holds the read lock for the entire query duration (hash parsing, data-source query, DB access), unlike get_by_taker.rs/get_by_token.rs in this same PR, which explicitly scope the guard in a nested block to drop it immediately after cloning the client.

♻️ Suggested fix: scope the guard like sibling handlers
-        let raindex = shared_raindex.read().await;
-        let chain_ids =
-            crate::routes::optional_chain_ids_filter(raindex.raindex_yaml(), request.chain_id)?;
-        let client = raindex.client().clone();
+        let (client, chain_ids) = {
+            let raindex = shared_raindex.read().await;
+            let chain_ids = crate::routes::optional_chain_ids_filter(
+                raindex.raindex_yaml(),
+                request.chain_id,
+            )?;
+            (raindex.client().clone(), chain_ids)
+        };
         let ds = RaindexTradesDataSource {
             client: &client,
             pool: pool.inner(),
         };
         process_get_trades_by_order_hashes(&ds, chain_ids, request).await
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/routes/trades/get_by_order_hashes.rs` around lines 51 - 63, The
SharedRaindexProvider read guard in get_by_order_hashes is held for the full
async request because raindex stays in scope until
process_get_trades_by_order_hashes(...).await finishes. Scope the read lock
inside a nested block, clone the client there, and extract chain_ids before the
block ends so the guard is dropped immediately after the needed data is copied.
Mirror the pattern used in get_by_taker and get_by_token, keeping
RaindexTradesDataSource and process_get_trades_by_order_hashes as the key call
sites to update.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main.rs`:
- Around line 177-187: The v2-mounted route groups in main::mount currently
reuse the v1 route tables, so the OpenAPI generator will not emit distinct /v2
paths for tokens, order, orders, vaults, and trades. Update the relevant route
modules and their routes_v2() handlers to add matching #[utoipa::path(...)]
annotations for the v2 endpoints, and extend the existing path assertions/tests
to verify the /v2 paths are present alongside the v1 ones.

---

Nitpick comments:
In `@src/routes/orders/get_by_token.rs`:
- Around line 20-23: The signature of process_get_orders_by_token has grown too
large, forcing a too_many_arguments allowance and making it harder to extend.
Refactor this handler to accept a small request/filter struct that groups
address, state, side, page, page_size, denomination, and chain_ids, then update
the call sites and any internal references in process_get_orders_by_token to use
that struct instead of many positional parameters.

In `@src/routes/swap/calldata.rs`:
- Line 118: The `From<SwapCalldataRequest>` and `From<SwapCalldataV2Request>`
impls currently use `unwrap_or_default()` for `chain_id`, which silently sets an
invalid placeholder value of 0 before `process_swap_calldata` and
`process_swap_calldata_v2` overwrite it. Update these conversions to make the
placeholder intent explicit, either by using a clear sentinel value or adding a
brief comment рядом to the `chain_id` assignment, and ensure the
`process_swap_calldata`/`process_swap_calldata_v2` flow remains the only place
that injects the real chain ID.

In `@src/routes/trades/get_by_order_hashes.rs`:
- Around line 51-63: The SharedRaindexProvider read guard in get_by_order_hashes
is held for the full async request because raindex stays in scope until
process_get_trades_by_order_hashes(...).await finishes. Scope the read lock
inside a nested block, clone the client there, and extract chain_ids before the
block ends so the guard is dropped immediately after the needed data is copied.
Mirror the pattern used in get_by_taker and get_by_token, keeping
RaindexTradesDataSource and process_get_trades_by_order_hashes as the key call
sites to update.

In `@src/routes/trades/get_by_tx.rs`:
- Around line 273-277: The tx path is not verifying that the new chain filter is
actually forwarded into the data source. Update the `get_trades_by_tx` flow in
`MockTradesDataSource` to capture `_chain_ids`, then add an assertion in one
process test that the tx lookup receives `Some(vec![8453])`, mirroring the
existing taker/order-hash test pattern so the filter can’t be dropped silently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ee1e424f-045e-46d2-986b-dedde235f5b1

📥 Commits

Reviewing files that changed from the base of the PR and between 7c99f00 and afd85b9.

📒 Files selected for processing (21)
  • src/main.rs
  • src/routes/mod.rs
  • src/routes/order/mod.rs
  • src/routes/orders/get_by_owner.rs
  • src/routes/orders/get_by_token.rs
  • src/routes/orders/mod.rs
  • src/routes/swap/calldata.rs
  • src/routes/swap/mod.rs
  • src/routes/swap/quote.rs
  • src/routes/tokens.rs
  • src/routes/trades/get_by_address.rs
  • src/routes/trades/get_by_order_hashes.rs
  • src/routes/trades/get_by_taker.rs
  • src/routes/trades/get_by_token.rs
  • src/routes/trades/get_by_tx.rs
  • src/routes/trades/mod.rs
  • src/routes/vaults.rs
  • src/types/orders.rs
  • src/types/swap.rs
  • src/types/trades.rs
  • src/types/vaults.rs
✅ Files skipped from review due to trivial changes (1)
  • src/types/swap.rs
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/routes/mod.rs
  • src/types/vaults.rs
  • src/routes/vaults.rs
  • src/routes/swap/mod.rs

Comment thread src/main.rs
Comment on lines +177 to +187
.mount("/v2/tokens", routes::tokens::routes_v2())
.mount("/v1/swap", routes::swap::routes())
.mount("/v2/swap", routes::swap::routes_v2())
.mount("/v1/order", routes::order::routes())
.mount("/v2/order", routes::order::routes_v2())
.mount("/v1/orders", routes::orders::routes())
.mount("/v2/orders", routes::orders::routes_v2())
.mount("/v1/vaults", routes::vaults::routes())
.mount("/v2/vaults", routes::vaults::routes_v2())
.mount("/v1/trades", routes::trades::routes())
.mount("/v2/trades", routes::trades::routes_v2())

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify v2 OpenAPI path coverage in source/tests.
rg -n '"/v2/(tokens|order|orders|vaults|trades|swap)' src
rg -n '#\[utoipa::path|paths\(' src/main.rs src/routes

Repository: ST0x-Technology/st0x.rest.api

Length of output: 2959


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the OpenAPI setup and v2 route definitions/annotations.
sed -n '1,140p' src/main.rs
printf '\n--- main OpenAPI tests ---\n'
sed -n '420,520p' src/main.rs

printf '\n--- tokens routes ---\n'
sed -n '720,860p' src/routes/tokens.rs

printf '\n--- order routes ---\n'
sed -n '1,220p' src/routes/order.rs

printf '\n--- orders routes ---\n'
sed -n '1,220p' src/routes/orders.rs

printf '\n--- vaults routes ---\n'
sed -n '280,390p' src/routes/vaults.rs

printf '\n--- trades routes ---\n'
sed -n '1,220p' src/routes/trades.rs

printf '\n--- swap routes ---\n'
sed -n '1,120p' src/routes/swap/quote.rs
sed -n '1,120p' src/routes/swap/calldata.rs

Repository: ST0x-Technology/st0x.rest.api

Length of output: 14228


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the route modules and inspect the v2 OpenAPI-related definitions/tests.
fd -t f 'mod.rs|*.rs' src/routes | sort

printf '\n--- order-related files ---\n'
fd -t f 'order*.rs|*order*/*.rs' src/routes | sort

printf '\n--- orders-related files ---\n'
fd -t f 'orders*.rs|*orders*/*.rs' src/routes | sort

printf '\n--- vaults/trades/tokens files ---\n'
fd -t f 'vaults*.rs|trades*.rs|tokens*.rs' src/routes | sort

printf '\n--- openapi references in main.rs ---\n'
rg -n 'openapi|ApiDoc|/v2/(tokens|order|orders|vaults|trades|swap)' src/main.rs

printf '\n--- path annotations in route files ---\n'
rg -n '#\[utoipa::path|path = "/v2/|path = "/v1/' src/routes/tokens.rs src/routes/swap src/routes/orders.rs src/routes/vaults.rs src/routes/trades.rs src/routes/order -g '!**/target/**'

Repository: ST0x-Technology/st0x.rest.api

Length of output: 465


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- route files ---'
fd -t f --glob '*.rs' src/routes | sort

printf '\n%s\n' '--- v2 route mounts/usages ---'
rg -n 'routes_v2|/v2/' src/routes src/main.rs

printf '\n%s\n' '--- OpenAPI annotations in candidate files ---'
rg -n '#\[utoipa::path|path = "/v2/|path = "/v1/' src/routes/tokens.rs src/routes/orders.rs src/routes/vaults.rs src/routes/trades.rs src/routes/swap/quote.rs src/routes/swap/calldata.rs src/main.rs

Repository: ST0x-Technology/st0x.rest.api

Length of output: 4035


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- routes_v2 implementations ---'
sed -n '1,240p' src/routes/order/mod.rs
printf '\n--- orders mod ---\n'
sed -n '1,260p' src/routes/orders/mod.rs
printf '\n--- vaults ---\n'
sed -n '1,460p' src/routes/vaults.rs
printf '\n--- trades mod ---\n'
sed -n '1,520p' src/routes/trades/mod.rs
printf '\n--- tokens ---\n'
sed -n '1180,1265p' src/routes/tokens.rs
printf '\n--- swap mod ---\n'
sed -n '1,360p' src/routes/swap/mod.rs

printf '\n%s\n' '--- OpenAPI tests mentioning v2 paths ---'
rg -n '/v2/(tokens|order|orders|vaults|trades|swap)|swap_quote_v2_path|swap_calldata_v2_path' src/main.rs src/routes -g '!**/target/**'

Repository: ST0x-Technology/st0x.rest.api

Length of output: 50385


Add v2 OpenAPI entries for the mounted route groups. routes_v2() here reuses the v1 route tables, so /v2/tokens, /v2/order, /v2/orders, /v2/vaults, and /v2/trades won’t appear as distinct OpenAPI paths unless you add matching #[utoipa::path(...)] entries and path assertions.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main.rs` around lines 177 - 187, The v2-mounted route groups in
main::mount currently reuse the v1 route tables, so the OpenAPI generator will
not emit distinct /v2 paths for tokens, order, orders, vaults, and trades.
Update the relevant route modules and their routes_v2() handlers to add matching
#[utoipa::path(...)] annotations for the v2 endpoints, and extend the existing
path assertions/tests to verify the /v2 paths are present alongside the v1 ones.

Source: Coding guidelines

graphite-app Bot pushed a commit that referenced this pull request Jul 30, 2026
## Motivation

Pyth is being removed as the website pricing source. The REST API needs to own the executable platform price, preserve the latest valid price when markets are closed, and retain enough history for 24-hour movement and future gainers/losers views.

The price service follows the registry-driven multi-chain model from #151. Chain IDs and quote-token addresses are discovered from the registry rather than duplicated in static REST configuration.

## Solution

- Discover every configured registry network containing ST0x tokens on each sampling/request cycle.
- Resolve each network's quote token from the registry token list by the `USDC` symbol, case-insensitively.
- Pin dev, preview, and production to registry commit `208aad93753e6dff4d87c178af7eb8a3a9f7732b`, which includes the compatible subgraph and complete asset set.
- Sample active ST0x/USDC order books every 60 seconds with bounded multi-chain concurrency.
- Compute the exact midpoint of the highest executable bid and lowest executable ask only for complete, positive, two-sided, non-crossed books.
- Exclude explicitly unsuccessful order quotes because those orders are not currently executable. Still fail a sample on transport failures, missing/duplicate quote results, or a successful quote without data so incomplete books cannot silently publish.
- Normalize wrapped, unwrapped, legacy, and mixed-case token aliases into canonical wrapped-share units using ERC-4626 assets-per-share.
- Persist exact decimal bid, ask, and midpoint strings in SQLite with idempotent sample buckets and seven-day retention.
- Add authenticated latest, timestamp, and SQL-downsampled history endpoints with canonical lowercase addresses and camelCase JSON.
- Return all registry markets when `GET /v1/prices` omits `chainId`; validate a requested chain and require `chainId` for history when multiple networks are configured.
- Keep retained history visible across registry canonical-address rotations by querying current, unwrapped, and legacy aliases.
- Expose live, cached, historical, and unavailable source states plus batched 24-hour percentage changes.
- Supervise the sampler with timeout and detailed health reporting, and document the API contract.

## Intentionally unchanged

- No historical backfill or regeneration outside the retention window.
- No top-gainers UI in this PR.
- No website changes in this PR.
- No TradingView or Pyth pricing dependency.
- This PR remains based directly on `main`; it mirrors #151's chain-resolution helpers and registry semantics without becoming its child.

## Verification

- `nix develop -c rainix-rs-static`
- `nix develop -c cargo test` — 323 passed
- Focused market-price and route tests passed.
- Two-stage read-only local review; all actionable correctness findings were fixed and retested.

## Runtime smoke test

Started the REST API locally with the updated dev registry and a temporary API key:

- The registry exposed all 27 configured ST0x assets.
- `GET /v1/prices?chainId=8453` produced live bid, ask, and midpoint snapshots for 26 assets.
- wtSPYM returned an executable midpoint around `$87.23`.
- wtMSTR was correctly unavailable because its current book was crossed; no invalid midpoint was persisted.
- The website's public pricing proxy and homepage consumed these snapshots successfully.

The supplied registry commit removes the earlier subgraph-schema compatibility caveat. The temporary API key was deleted after testing.

## Review focus

Please pay particular attention to registry-driven market discovery, ERC-4626 denomination normalization, quote-side interpretation, explicit failed-quote exclusion versus structural quote failures, alias continuity across registry rotations, and sampler health behavior.

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

* **New Features**
  * Enabled configurable market-price sampling with periodic persistence and seven-day history retention.
  * Added authenticated pricing APIs for latest/historical market prices and per-token intervalized price history.
* **Bug Fixes**
  * Improved reliability by capping concurrency for per-order quote fallback calls.
* **Documentation**
  * Added Market Prices documentation describing pricing methodology and API responses.
* **Tests**
  * Extended smoke checks to validate the new pricing endpoints.
* **Health**
  * Enhanced `/health/detailed` to report market-price sampler status and freshness/failure signals.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

@Siddharth2207 Siddharth2207 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Website multi-network notes (pairs with SARKEX/st0x#263)

Staging /v2/tokens is Base-only today, which is fine. Two payload gaps will block a real second-network UI:

  1. No block explorer on network. The website currently hardcodes https://blockscan.com because TokenNetworkResponseSchema has no explorer URL (key, rpcs, chainId, label, networkId, currency only). Please expose a per-network explorer (registry field or explicit map) so the UI can fail catalog build when it is missing.
  2. rpcs are intentionally stripped in sanitize_network (network.rpcs.clear()). That is correct for a public token list, but SARKEX/st0x#263 then builds EIP-1271 clients from public registry YAML RPCs and dropped the production BASE_RPC_URL fail-closed. A second chain needs a documented paid/primary RPC channel (env or authenticated registry), not an empty rpcs array.

Those two schema/sanitize sites are outside this PR’s diff hunks, so this is a top-level note rather than inline on those lines.

Comment thread src/routes/tokens.rs
#[serde(rename_all = "camelCase")]
pub struct TokenListParams {
#[field(name = "chainId")]
#[param(example = 8453)]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GET /v2/tokens is what the website catalog is built from.

Two gaps on the token network object will block SARKEX/st0x#263 from being actually multi-network:

  • No explorer URL → UI hardcodes https://blockscan.com (already wrong for Base; should be https://basescan.org).
  • rpcs is always [] after sanitize_network → UI falls back to public registry RPCs for EIP-1271.

Please add a per-network explorer to this payload (or /registry), and document a paid/primary RPC channel for authenticated website use. Empty public rpcs can stay.

Copy link
Copy Markdown
Collaborator Author

How to use the Graphite Merge Queue

Add the label add-to-gt-merge-queue to this PR to add it to the merge queue.

You must have a Graphite account in order to use the merge queue. Sign up using this link.

An organization admin has enabled the Graphite Merge Queue in this repository.

Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue.

This stack of pull requests is managed by Graphite. Learn more about stacking.

@findolor

Copy link
Copy Markdown
Collaborator Author

Superseded by #193, rebuilt from current main without the obsolete block-explorer submodule bump.

@findolor findolor closed this Sep 21, 2026
findolor added a commit that referenced this pull request Sep 22, 2026
## Supersedes

- Replaces #151 with a clean branch based on current `main`.
- Excludes the obsolete block-explorer Raindex submodule bump from #174.

## Dependency

- [Registry #79](ST0x-Technology/st0x.registry#79)

## Motivation

Enable registry-driven multi-network behavior without breaking existing V1 clients or the currently deployed website, which uses V2 swaps without an explicit `chainId`.

## Version contract

- V1 non-swap endpoints preserve the legacy Base (8453) default when `chainId` is omitted.
- V2 data endpoints provide multi-network behavior; omitted `chainId` means all networks for list endpoints and remains an error where a single network is required.
- V1 and V2 swap endpoints remain Base-compatible: omitted `chainId` selects Base, and explicit non-Base swaps are directed to V3.
- New V3 quote and calldata endpoints require an explicit `chainId` and never infer a network.

## Solution

- Mount canonical V2 data routes while retaining V1 compatibility.
- Add strict `/v3/swap/quote` and `/v3/swap/calldata` routes using the existing mode-based swap contract.
- Document V3 with dedicated OpenAPI schemas where `chainId` is required.
- Add chain-aware selection across swaps, tokens, details and proofs, orders, trades, vaults, prices, attribution, analytics, and caches.
- Scope token identity, SDK queries, wrap ratios, attribution workers, and cache keys by chain.
- Restrict order, trade, and swap queries to networks with configured Raindexes.
- Add the chain-keyed wrapped exchange-rate snapshot migration.
- Pin dev, preview, and production configs to Registry #79's multi-network registry commit.
- Select each network's market quote token through the registry `marketQuote` extension, with USDC fallback for existing registries.

## Deployment safety

The REST API can be deployed before the website. Existing V1 clients continue to resolve omitted network selection to Base, and the current website's V2 swap requests continue to work unchanged. The website can migrate non-Base swaps to V3 independently.

## Validation

- [x] `cargo fmt --check`
- [x] `cargo check --all-targets`
- [x] `cargo test` — 499 passed
- [x] `cargo clippy --all-targets -- -D warnings`
- [x] `git diff --check`
- [x] No Raindex submodule delta
- [x] Clean local DB bootstrap reached ready/healthy sync for Base (8453) and Robinhood (4663)
- [x] Token, order, trade, vault-total, and price endpoints stayed isolated for both chain IDs
- [x] Unsupported `chainId` returns HTTP 400
- [x] Robinhood prices use the configured USDG quote token and return live chain-4663 samples
- [x] Website PR #263 validated end-to-end against the local server
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.

2 participants