Skip to content

improve(svm): extract code/wasClean from Solana WS close and route log level#3427

Merged
pxrl merged 2 commits into
masterfrom
droplet/T90K0AL22-C09TJ24HWQ4-1779965890-507339
May 28, 2026
Merged

improve(svm): extract code/wasClean from Solana WS close and route log level#3427
pxrl merged 2 commits into
masterfrom
droplet/T90K0AL22-C09TJ24HWQ4-1779965890-507339

Conversation

@droplet-rl
Copy link
Copy Markdown
Contributor

Summary

The reconnect-on-close warn introduced in #3417 logs the raw SolanaError.cause, which is a Node CloseEvent. CloseEvent exposes code, wasClean, and reason as prototype getters, so winston serializes the cause as {} (Cloud Logging) or "[object CloseEvent]" (winston transport) and the only useful fields are dropped. Slack sees them only because its formatter walks the prototype — which is also why we get the giant initEvent/stopImmediatePropagation/etc dump in the Slack message.

This change projects the three scalars (code, wasClean, reason) onto own properties before logging, drops the rest of the CloseEvent, and routes the log level on the extracted signal:

  • code === 1006 && wasClean === falsedebug. This is the documented Chainstack shared-gateway rotation at a wallclock 10-min cadence and is operational noise.
  • Anything else (notably code: 1011, wasClean: true, reason: "Internal Error" from Alchemy, plus unknown codes) → warn, unchanged.

Empirical justification (bots-across-3839, 7 days 2026-05-21 → 2026-05-28, both primary + usdh):

Alchemy Chainstack
code: 1011, wasClean: true 2922 0
code: 1006, wasClean: false 11 305

So 1006-vs-1011 is essentially a clean per-provider split: Chainstack 1006 is the wallclock-rotation noise we already know about; Alchemy 1011 is the current high-volume signal (~417/day/bot) and stays at warn so it remains alertable. The full structured log now looks like:

{
  "at": "RelayerSpokePoolListenerSVM::listen",
  "message": "Caught error on Solana provider; reconnecting.",
  "provider": "wss://solana-mainnet.g.alchemy.com",
  "backoffS": 1,
  "code": 1011,
  "wasClean": true,
  "reason": "Internal Error"
}

…instead of the previous cause: {} (GCP) or full CloseEvent dump (Slack).

Test plan

  • yarn tsc --noEmit clean
  • yarn eslint clean on changed file
  • yarn prettier --check clean on changed file
  • No runtime test — the helper is straightforward field projection of a 3-field shape, and the routing is one ternary. Happy to add a unit test if preferred.
  • After merge: 1006-Chainstack-noise should disappear from #zbot-across-relayer-* Slack channels; 1011-Alchemy warns should continue to appear, now structured rather than dumped.

🤖 Generated with Claude Code

The reconnect path was logging the raw SolanaError cause, which is a
WebSocket CloseEvent. CloseEvent exposes code/wasClean/reason as
prototype getters, so winston serializes the cause as {} (or "[object
CloseEvent]") and the only useful fields are dropped. The Slack
transport's deep walker does pick them up, which is why we could see
1006/1011 in Slack but not in GCP logs.

Project code/wasClean/reason onto own properties before logging, drop
the rest of the CloseEvent, and route by the extracted signal: 1006
with wasClean=false is the documented Chainstack shared-gateway
rotation (https://docs.chainstack.com/docs/error-reference) -- demote
to debug. Everything else (notably 1011 Internal Error from Alchemy,
which dominates current warn volume) stays at warn.
…gling

@solana/kit doesn't expose a CloseEvent helper, but `CloseEvent` is a
global TypeScript type via @types/node + undici-types (the kit
websocket channel uses globalThis.WebSocket, whose close event is the
undici CloseEvent). Replace the manual property-by-property extraction
with a single structural type guard so TS infers code/wasClean/reason
from the CloseEvent interface itself.

No behavioural change beyond the missing-fields edge case, which now
falls through to the non-SolanaError branch (logs err.message) instead
of logging undefined fields.
@pxrl pxrl merged commit 2864baa into master May 28, 2026
5 checks passed
@pxrl pxrl deleted the droplet/T90K0AL22-C09TJ24HWQ4-1779965890-507339 branch May 28, 2026 11:28
@droplet-rl
Copy link
Copy Markdown
Contributor Author

Thanks for the approval. The review (pullrequestreview-4380525321) is a plain approve — empty body and no inline comments — and the PR merged ~30s later (2864baa5), so there's no feedback to address and nothing to push.

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