Repository navigation
feat: add Launchpad TDP WS service and extract shared Channel Sub manager - #10884
Conversation
…ubscriptionManager
|
@metamaskbot publish-preview |
1 similar comment
|
@metamaskbot publish-preview |
|
@metamaskbot publish-preview |
|
Preview builds have been published. Learn how to use preview builds in other projects. Expand for full list of packages and versions. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 0f1d8ee. Configure here.
| * @param raw - The raw abbreviated-key event. | ||
| * @returns The normalized event. | ||
| */ | ||
| export function normalizeEvent( |
There was a problem hiding this comment.
Why not keeping the raw data structure from the backend?
There was a problem hiding this comment.
Mainly readability + isolating backend changes. The wire keys are intentionally "unfriendly" (gp, mc, snhp, t10hp, gs…) to keep payloads small, so normalizing to graduationPercent / marketCap / etc. keeps consumers from hard-coding abbreviations. And it means if the backend renames a key or changes shape, only normalize.ts changes instead of every call site.
| export const LAUNCHPAD_TDP_SERVICE_ALLOWED_ACTIONS = [ | ||
| 'BackendWebSocketService:connect', | ||
| 'BackendWebSocketService:forceReconnection', | ||
| 'BackendWebSocketService:subscribe', | ||
| 'BackendWebSocketService:getConnectionInfo', | ||
| 'BackendWebSocketService:channelHasSubscription', | ||
| 'BackendWebSocketService:getSubscriptionsByChannel', | ||
| 'BackendWebSocketService:findSubscriptionsByChannelPrefix', | ||
| 'BackendWebSocketService:addChannelCallback', | ||
| 'BackendWebSocketService:removeChannelCallback', | ||
| ] as const; |
There was a problem hiding this comment.
This list is the same as OHLCV_SERVICE_ALLOWED_ACTIONS, and after the extraction neither service calls BackendWebSocketService itself. Every call is inside ChannelSubscriptionManager.
Define the list once in the manager and spread it from each service constant. Keep OHLCV_SERVICE_ALLOWED_ACTIONS and LAUNCHPAD_TDP_SERVICE_ALLOWED_ACTIONS as the public exports: getRestricted grants these actions to the messenger named OHLCVService / LaunchpadTokenDetailsService, and the manager only borrows that messenger.
export const CHANNEL_SUBSCRIPTION_MANAGER_ALLOWED_ACTIONS = [
'BackendWebSocketService:connect',
'BackendWebSocketService:forceReconnection',
'BackendWebSocketService:subscribe',
'BackendWebSocketService:channelHasSubscription',
'BackendWebSocketService:getSubscriptionsByChannel',
'BackendWebSocketService:addChannelCallback',
'BackendWebSocketService:removeChannelCallback',
] as const;export const LAUNCHPAD_TDP_SERVICE_ALLOWED_ACTIONS = [
...CHANNEL_SUBSCRIPTION_MANAGER_ALLOWED_ACTIONS,
] as const;
Those are the actions the manager actually calls. getConnectionInfo and findSubscriptionsByChannelPrefix are on both lists and are never called (same on main for OHLCV), so leave them off the shared list.
Do the same for the allowed-events arrays (connectionStateChanged is the only one). A service that later calls something else adds that action to its own constant.
|
Nice refactor ;) |
…onManager constant

Explanation
This PR lands Phase 1 of the Launchpad (memecoin) WebSocket work in
@metamask/core-backend, scoped to Token Detail Pages (TDP), and refactors the existing OHLCV lifecycle into a shared, reusable helper first (extract-first).Per the
launchpad-tokens.v1gateway docs, Token Details are delivered per-asset on the asset's own channel (launchpad-tokens.v1.<assetId>) with the fullcreated | stats | migratedlifecycle — i.e. the same "subscribe-channel == delivery-channel" shape as OHLCV, and distinct from Discovery/Explore's top-level fan-in. Because TDP is OHLCV-shaped, the per-channel subscription machinery is identical, so it is extracted once and shared rather than cloned.What changed
ChannelSubscriptionManager(src/ws/ChannelSubscriptionManager.ts) — the channel-agnostic subscription lifecycle extracted out ofOHLCVService: reference counting, grace-period unsubscribe, grace-period flush, unsubscribe retry with backoff +forceReconnection, idempotency, resubscribe-on-reconnect, and optional system-notifications registration. Consumed by composition (not inheritance) via options (messenger,channelType,onMessage,onSubscriptionError, system-notifications channel/handler,onConnectionStateChange); grace/retry are overridable for tests.OHLCVServicerefactor (behavior-preserving) — reduced to its domain concerns (channel building, bar forwarding,#chainsUpchain status) and now delegates its lifecycle to aChannelSubscriptionManager. No change to its public API, messenger, exposed actions, or published events — its existing test suite passes unchanged, which is the proof the extraction preserves behavior.LaunchpadTokenDetailsService(src/ws/launchpad/) — a stateless service (mirroring the OHLCV pattern) that owns aChannelSubscriptionManager, subscribes/unsubscribes per-asset byassetId, parses each delivered batch, and publishes a singletokenUpdatedper batch. It keeps system-notifications handling, forwarding upstream status asstatusChanged, and publishessubscriptionError.src/ws/launchpad/types.ts,normalize.ts) — dependency-free event types (raw abbreviated-key shapes + a normalizedcreated | stats | migrateddiscriminated union), plusnormalizeEvent,parseBatch(array-tolerant, skips partial/invalid entries), andbuildChannel(assetId). Kept pure so a later Explore phase can import it directly.src/ws/launchpad/index.ts, packagesrc/index.tsexports for the service, the manager, and launchpad types, and the auto-generatedLaunchpadTokenDetailsService-method-action-types.ts.Out of scope (tracked for later phases): Explore / the stateful
BaseController(ring buffer, eviction, cross-source merge/reconcile) and allmetamask-mobilewiring (Engine init, hooks, StatBar).One open implementation detail (non-blocking)
Per-asset TDP routing is implemented via Rail A (the
subscribe()callback, keyed bysubscriptionId), matching OHLCV verbatim. If a real TDP frame turns out to be channel-name-only (nosubscriptionId), the swap toaddChannelCallbackis isolated to the manager's subscribe path and does not change the public design. This only needs a quick confirmation against a live frame.References
packages/core-backend/src/ws/launchpad/LAUNCHPAD_WS_ARCHITECTURE.mdChecklist
Note
Medium Risk
Shared WebSocket subscription lifecycle moves behind a new abstraction used by OHLCV and launchpad; behavior should be preserved for OHLCV but reconnect/unsubscribe edge cases affect both services. Breaking messenger allowlists require consumers that relied on the removed OHLCV actions to grant them explicitly.
Overview
Introduces
LaunchpadTokenDetailsServicefor per-asset launchpad token detail streaming over the backend WebSocket (subscribe/unsubscribebyassetId), withtokenUpdated,statusChanged, andsubscriptionErrorevents plus purenormalize/parseBatch/buildChannelhelpers forcreated | stats | migratedbatches.Extracts
ChannelSubscriptionManagerfrom OHLCV (ref counting, grace-period unsubscribe/flush, unsubscribe retry +forceReconnection, resubscribe on reconnect, optional system notifications) and refactorsOHLCVServiceto use it via composition while keeping the same public API and events.Breaking:
OHLCV_SERVICE_ALLOWED_ACTIONSno longer includes unusedgetConnectionInfoandfindSubscriptionsByChannelPrefix; OHLCV and launchpad allowed actions/events now share the minimalCHANNEL_SUBSCRIPTION_MANAGER_*set. Package exports and changelog are updated; new unit tests cover the manager, launchpad service, and normalizers.Reviewed by Cursor Bugbot for commit b388afd. Bugbot is set up for automated code reviews on this repo. Configure here.