Skip to content

Network selector on routes + per-request x402 network - #136

Open
ibochivincent-lang wants to merge 1 commit into
Miracle656:mainfrom
ibochivincent-lang:feat/api-network-selector
Open

Network selector on routes + per-request x402 network#136
ibochivincent-lang wants to merge 1 commit into
Miracle656:mainfrom
ibochivincent-lang:feat/api-network-selector

Conversation

@ibochivincent-lang

Copy link
Copy Markdown

Summary

  • Adds src/middleware/network.ts: a Fastify plugin resolving the per-request Stellar network from a ?network= query param or x-network header, validating it (400 for anything not testnet/mainnet, defaults to testnet), and attaching it to req.network. Registered early in src/index.ts (right after cors/compress, ahead of API-key auth/rate-limit/x402) so everything downstream can read it.
  • src/x402/network.ts: shared per-network helpers (X402_NETWORK_LABEL, paymentAddressFor, getX402ResourceServer) used by both REST x402 gating and the WS endpoint, replacing the old module-load-time STELLAR_NETWORK read.
  • src/middleware/x402.ts and src/api/websocket.ts: x402's network/payTo in the 402 requirements (and the actual verify/settle calls) are now resolved from req.network per request instead of a value computed once at plugin init. Supports optional ORACLE_PAYMENT_ADDRESS_TESTNET / ORACLE_PAYMENT_ADDRESS_MAINNET env vars, falling back to the existing ORACLE_PAYMENT_ADDRESS.
  • src/api/rest.ts + src/aggregator/bestRoute.ts: /price/:assetA/:assetB and /price/:assetA/:assetB/route now look up watched pairs (getNetworkConfig(network).pairs) and live SDEX pricing (a real per-network Horizon call) from the resolved network — so ?network=mainnet genuinely returns mainnet SDEX pricing. The price Redis cache key is network-scoped ({network}:{pairKey}) so testnet/mainnet never collide.
  • /ws resolves+validates the network the same way and rejects (400, closes the socket) a network other than the one this instance is actually streaming, since live price events don't carry a network tag yet.

Scope call — what's not covered

Candles, price history, AMM pool pricing, and the GraphQL resolvers read from Postgres, which has no network column yet — that's the deeper aggregation-layer work the issue's suggested execution calls out ("Thread into pricing/aggregation reads (L048)"). Those endpoints still serve from whichever network this instance is currently configured to ingest (STELLAR_NETWORK). Documented explicitly in the README and in code comments at each of these read sites so it's not lost track of.

Side fix

While wiring getBestRoute's new per-network getNetworkConfig() call, I found tests/aggregator.property.test.ts and src/__tests__/bestRoute.test.ts's @stellar/stellar-sdk mocks were missing a Networks export — pre-existing on main, not something this PR introduces (verified by running the suite against a clean main checkout first). It silently crashed the property test at import time (0 iterations ever ran) and would have crashed bestRoute.test.ts too once getBestRoute started calling getNetworkConfig(). Fixed both mocks; also bumped the property test's timeout to 30s since, now that its 10,000 iterations actually execute, they don't reliably finish in the default 5s under full-suite load.

closes #118

Test plan

  • npx tsc --noEmit — clean.
  • npx vitest run197 passed, 1 skipped, run twice for stability. (Ran against clean main first to confirm which failures were pre-existing vs. introduced by this change.)
  • New tests: src/__tests__/middleware/network.test.ts (selector resolution/validation), new cases in src/__tests__/middleware/x402.test.ts (per-network payTo/network label, invalid network short-circuits before x402 runs, mainnet payment verification), new cases in src/__tests__/bestRoute.test.ts (mainnet Horizon client selection + per-network memoisation).

Adds a network-selector Fastify plugin (src/middleware/network.ts) that
resolves a per-request Stellar network from a ?network= query param or
x-network header, validates it (400 on an unrecognised value, default
testnet), and attaches it to req.network — registered early so every
route, x402, and the WebSocket handler can read it.

x402 (both REST and /ws) now resolves its network label and payTo per
request from req.network instead of a fixed value computed once from
STELLAR_NETWORK at plugin init, via new shared helpers in
src/x402/network.ts. Supports optional per-network payment addresses
(ORACLE_PAYMENT_ADDRESS_TESTNET/_MAINNET, falling back to the existing
ORACLE_PAYMENT_ADDRESS).

/price/:assetA/:assetB and /price/:assetA/:assetB/route now look up
watched pairs and live SDEX pricing (a real Horizon call) per the
resolved network, so ?network=mainnet genuinely returns mainnet SDEX
pricing; the price-cache key is network-scoped so testnet/mainnet don't
collide. DB-backed reads (candles, history, pools, AMM pricing, GraphQL
resolvers) have no network column yet and still serve from this
instance's configured STELLAR_NETWORK — documented in the README as
follow-up work.

/ws validates its requested network the same way and rejects (400) a
network other than the one this instance actually streams, since price
events carry no network tag yet either.

Also fixes a pre-existing bug in three test files' @stellar/stellar-sdk
mocks (missing Networks export), which silently broke
tests/aggregator.property.test.ts and made src/__tests__/bestRoute.test.ts
report false results — both surfaced by getBestRoute's new
getNetworkConfig() call.
@drips-wave

drips-wave Bot commented Aug 27, 2026

Copy link
Copy Markdown

@ibochivincent-lang 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

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.

Network selector on routes + per-request x402 network

1 participant