Skip to content

feat(hydromancer)!: add l2Book surface and body-driven /info#126

Closed
jaspersagent wants to merge 7 commits into
sedaprotocol:mainfrom
jaspersagent:feat/hydro-ws
Closed

feat(hydromancer)!: add l2Book surface and body-driven /info#126
jaspersagent wants to merge 7 commits into
sedaprotocol:mainfrom
jaspersagent:feat/hydro-ws

Conversation

@jaspersagent
Copy link
Copy Markdown
Contributor

/info now reads coins from the request body, and the hydromancer module serves order books alongside asset contexts.

The route accepts {"type": "assetContext" | "l2Book", "coins": [...]} and rejects anything else with a 400. The proxy parses the body once and threads it into the module handler.

assetContext stays WS-first with a REST fallback. l2Book is WS-first with a short wait for the first frame and no fallback, because books update many times a second and a fallback fetch would be stale before it returned. An unseeded coin comes back null. Both surfaces multiplex over one WebSocket on one credential.

The WS code then went through a review pass. It was leaking three ways: book-cache waiters stranded on timeout, reconnect listeners that were never detached, and idle-cleanup fibers with no way to stop them. All three are closed, and the module now runs as a scoped layer so every background daemon dies with the layer. The two near-identical WS channels became one channel-keyed registry, and the getOrWaitPrice catch-and-evict block that dxfeed, lo-tech, and pyth-lazer each hand-rolled is now a shared getOrWaitOrEvict primitive.

Two things to know before merging:

  • The hydromancer config fields were renamed (coinsCleanup* to assetCtx* and friends), so existing operator config files break.
  • ModuleService.handleRequest takes a required body parameter now; chainlink-streams and pm-insights use it instead of re-reading the request.

…hapes

Coins now come from the JSON body (`{type: "assetContext", coins}`),
matching Hydromancer's native /info contract. Non-assetContext bodies
are rejected with 400 in the module, since the proxy never consumes
any other Hydromancer endpoint. The dispatcher no longer sniffs
bodies or synthesizes upstream routes for hydromancer, keeping
handle-proxy-request route-type-agnostic.

BREAKING CHANGE: not backwards compatible with the previous URL-param
shape (`/hydromancer/:coin` + `fetchFromModule`). Routes must now use
`POST /info` with an assetContext body. The transparent passthrough
for other Hydromancer endpoints (userState, meta, etc.) is also gone;
those return 400.
The Bun-to-Node migration's elysia adapter consumes the request stream
during the route's parse step, so modules calling request.clone().text()
afterward fail with "Failed to read request body". The dispatcher
already has the parsed body; thread it into ModuleService.handleRequest
as a fourth argument so hydromancer can read its assetContext payload
without touching the request stream.
After rebasing onto main, the new lo-tech and pm-insights arms call
handleRequest with three args. Mark `body` optional in the interface
so only hydromancer (which actually needs it) has to pass it, and
default to "" inside hydromancer so an empty body still hits the
existing 400 reject for non-assetContext shapes.
Adds a second subscription type to the hydromancer WS multiplex. /info
l2Book bodies route through a wait-for-first-frame cache with no REST
fallback; coins not seen on the socket within the wait timeout come
back null. assetContext-specific config fields gain an assetCtx prefix
so the l2Book knobs can sit alongside without ambiguity. The
standalone smoketest is removed now that the production path covers
the same flow.
The l2Book book handler stranded cache waiters when a request timed
out, WS reconnects never detached their event listeners, and the
idle-cleanup daemons could not be interrupted. A tryGetOrWait primitive
now evicts the waiter on timeout, every WS listener is paired with a
removeEventListener in the release path, and the module runs as a
scoped layer whose finalizers stop every daemon on teardown. The two
near-identical WS channels collapse into one channel-keyed registry and
the twin idle-cleanup blocks into one registry-driven loop. The request
body parser is now a single valibot variant schema, the asset cache
moves to a shared freshness-cache, and the ModuleService body parameter
is required so the proxy always threads the parsed body.
…ules

dxfeed, lo-tech, and pyth-lazer each hand-rolled the same getOrWaitPrice
catch-evict-rethrow block; they now call the shared getOrWaitOrEvict
primitive. chainlink-streams and pm-insights re-read the request body
with request.clone().text() instead of the body the proxy already
parsed; they now take the threaded body parameter. Behavior is
unchanged.
@jaspersagent
Copy link
Copy Markdown
Contributor Author

Closing in favor of #127. This branch had drifted 17 commits behind main, and its first commits duplicated work already merged via #116, so #127 rebuilds the l2Book flow and hardening cleanly on current main.

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