Skip to content

feat: add Launchpad TDP WS service and extract shared Channel Sub manager - #10884

Merged
sahar-fehri merged 9 commits into
mainfrom
feat/core-backend-launchpad-tdp-ws
Oct 28, 2026
Merged

sahar-fehri merged 9 commits into
mainfrom
feat/core-backend-launchpad-tdp-ws

Conversation

@sahar-fehri

@sahar-fehri sahar-fehri commented Oct 19, 2026 •

Copy link
Copy Markdown
Contributor

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.v1 gateway docs, Token Details are delivered per-asset on the asset's own channel (launchpad-tokens.v1.<assetId>) with the full created | stats | migrated lifecycle — 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

  • New ChannelSubscriptionManager (src/ws/ChannelSubscriptionManager.ts) — the channel-agnostic subscription lifecycle extracted out of OHLCVService: 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.
  • OHLCVService refactor (behavior-preserving) — reduced to its domain concerns (channel building, bar forwarding, #chainsUp chain status) and now delegates its lifecycle to a ChannelSubscriptionManager. 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.
  • New LaunchpadTokenDetailsService (src/ws/launchpad/) — a stateless service (mirroring the OHLCV pattern) that owns a ChannelSubscriptionManager, subscribes/unsubscribes per-asset by assetId, parses each delivered batch, and publishes a single tokenUpdated per batch. It keeps system-notifications handling, forwarding upstream status as statusChanged, and publishes subscriptionError.
  • Shared pure module (src/ws/launchpad/types.ts, normalize.ts) — dependency-free event types (raw abbreviated-key shapes + a normalized created | stats | migrated discriminated union), plus normalizeEvent, parseBatch (array-tolerant, skips partial/invalid entries), and buildChannel(assetId). Kept pure so a later Explore phase can import it directly.
  • Exports + generated types — new src/ws/launchpad/index.ts, package src/index.ts exports for the service, the manager, and launchpad types, and the auto-generated LaunchpadTokenDetailsService-method-action-types.ts.

Out of scope (tracked for later phases): Explore / the stateful BaseController (ring buffer, eviction, cross-source merge/reconcile) and all metamask-mobile wiring (Engine init, hooks, StatBar).

One open implementation detail (non-blocking)

Per-asset TDP routing is implemented via Rail A (the subscribe() callback, keyed by subscriptionId), matching OHLCV verbatim. If a real TDP frame turns out to be channel-name-only (no subscriptionId), the swap to addChannelCallback is 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

  • Architecture / phasing: packages/core-backend/src/ws/launchpad/LAUNCHPAD_WS_ARCHITECTURE.md

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

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 LaunchpadTokenDetailsService for per-asset launchpad token detail streaming over the backend WebSocket (subscribe/unsubscribe by assetId), with tokenUpdated, statusChanged, and subscriptionError events plus pure normalize/parseBatch/buildChannel helpers for created | stats | migrated batches.

Extracts ChannelSubscriptionManager from OHLCV (ref counting, grace-period unsubscribe/flush, unsubscribe retry + forceReconnection, resubscribe on reconnect, optional system notifications) and refactors OHLCVService to use it via composition while keeping the same public API and events.

Breaking: OHLCV_SERVICE_ALLOWED_ACTIONS no longer includes unused getConnectionInfo and findSubscriptionsByChannelPrefix; OHLCV and launchpad allowed actions/events now share the minimal CHANNEL_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.

@sahar-fehri sahar-fehri changed the title feat: add Launchpad TDP WebSocket service and extract shared Channel… feat: add Launchpad TDP WS service and extract shared Channel Sub manager Oct 19, 2026
@sahar-fehri

Copy link
Copy Markdown
Contributor Author

@metamaskbot publish-preview

1 similar comment
@sahar-fehri

Copy link
Copy Markdown
Contributor Author

@metamaskbot publish-preview

@sahar-fehri

Copy link
Copy Markdown
Contributor Author

@metamaskbot publish-preview

@github-actions

Copy link
Copy Markdown
Contributor

Preview builds have been published. Learn how to use preview builds in other projects.

Expand for full list of packages and versions.
@metamask-previews/account-tree-controller@11.2.0-preview-0f1d8eed9
@metamask-previews/accounts-controller@40.1.0-preview-0f1d8eed9
@metamask-previews/address-book-controller@8.0.0-preview-0f1d8eed9
@metamask-previews/advanced-chart-core@1.0.0-preview-0f1d8eed9
@metamask-previews/ai-controllers@2.0.0-preview-0f1d8eed9
@metamask-previews/analytics-controller@4.1.0-preview-0f1d8eed9
@metamask-previews/analytics-data-regulation-controller@0.0.0-preview-0f1d8eed9
@metamask-previews/announcement-controller@9.0.0-preview-0f1d8eed9
@metamask-previews/app-metadata-controller@3.0.0-preview-0f1d8eed9
@metamask-previews/approval-controller@10.0.0-preview-0f1d8eed9
@metamask-previews/assets-controller@20.0.0-preview-0f1d8eed9
@metamask-previews/assets-controllers@112.2.0-preview-0f1d8eed9
@metamask-previews/authenticated-user-storage@4.1.0-preview-0f1d8eed9
@metamask-previews/base-controller@10.1.0-preview-0f1d8eed9
@metamask-previews/base-data-service@2.1.0-preview-0f1d8eed9
@metamask-previews/bitcoin-regtest-up@2.0.0-preview-0f1d8eed9
@metamask-previews/bridge-controller@83.1.1-preview-0f1d8eed9
@metamask-previews/bridge-status-controller@78.0.0-preview-0f1d8eed9
@metamask-previews/build-utils@4.0.0-preview-0f1d8eed9
@metamask-previews/byte-utils@0.1.0-preview-0f1d8eed9
@metamask-previews/chain-agnostic-permission@2.1.0-preview-0f1d8eed9
@metamask-previews/chomp-api-service@7.0.0-preview-0f1d8eed9
@metamask-previews/claims-controller@1.1.0-preview-0f1d8eed9
@metamask-previews/client-controller@2.0.0-preview-0f1d8eed9
@metamask-previews/client-utils@3.0.4-preview-0f1d8eed9
@metamask-previews/compliance-controller@3.0.0-preview-0f1d8eed9
@metamask-previews/composable-controller@13.0.0-preview-0f1d8eed9
@metamask-previews/config-registry-controller@5.0.0-preview-0f1d8eed9
@metamask-previews/connectivity-controller@1.0.0-preview-0f1d8eed9
@metamask-previews/controller-utils@13.0.0-preview-0f1d8eed9
@metamask-previews/core-backend@12.3.0-preview-0f1d8eed9
@metamask-previews/cryptography@1.1.2-preview-0f1d8eed9
@metamask-previews/delegation-controller@4.0.0-preview-0f1d8eed9
@metamask-previews/earn-controller@13.0.2-preview-0f1d8eed9
@metamask-previews/eip-5792-middleware@4.0.1-preview-0f1d8eed9
@metamask-previews/eip-7702-internal-rpc-middleware@1.0.0-preview-0f1d8eed9
@metamask-previews/eip1193-permission-middleware@3.0.0-preview-0f1d8eed9
@metamask-previews/error-utils@0.1.0-preview-0f1d8eed9
@metamask-previews/eth-block-tracker@16.0.0-preview-0f1d8eed9
@metamask-previews/eth-json-rpc-middleware@26.0.0-preview-0f1d8eed9
@metamask-previews/eth-json-rpc-provider@7.0.0-preview-0f1d8eed9
@metamask-previews/foundryup@2.0.0-preview-0f1d8eed9
@metamask-previews/gas-fee-controller@27.0.0-preview-0f1d8eed9
@metamask-previews/gator-permissions-controller@6.0.1-preview-0f1d8eed9
@metamask-previews/geolocation-controller@2.0.0-preview-0f1d8eed9
@metamask-previews/java-tron-up@2.0.0-preview-0f1d8eed9
@metamask-previews/json-rpc-engine@11.0.0-preview-0f1d8eed9
@metamask-previews/json-rpc-middleware-stream@9.0.0-preview-0f1d8eed9
@metamask-previews/keyring-controller@28.2.0-preview-0f1d8eed9
@metamask-previews/kyc-controller@0.13.0-preview-0f1d8eed9
@metamask-previews/local-node-utils@2.0.0-preview-0f1d8eed9
@metamask-previews/logging-controller@10.0.0-preview-0f1d8eed9
@metamask-previews/marketing-consent-controller@1.1.0-preview-0f1d8eed9
@metamask-previews/message-manager@15.0.0-preview-0f1d8eed9
@metamask-previews/messenger@3.0.0-preview-0f1d8eed9
@metamask-previews/messenger-cli@1.0.0-preview-0f1d8eed9
@metamask-previews/mfa-recovery-controller@0.0.0-preview-0f1d8eed9
@metamask-previews/money-account-api-data-service@2.3.1-preview-0f1d8eed9
@metamask-previews/money-account-balance-service@3.1.2-preview-0f1d8eed9
@metamask-previews/money-account-controller@3.0.0-preview-0f1d8eed9
@metamask-previews/money-account-mfa-whitelist@0.1.0-preview-0f1d8eed9
@metamask-previews/money-account-upgrade-controller@6.0.0-preview-0f1d8eed9
@metamask-previews/money-account-utils@2.1.0-preview-0f1d8eed9
@metamask-previews/multichain-account-service@14.1.0-preview-0f1d8eed9
@metamask-previews/multichain-api-middleware@5.0.0-preview-0f1d8eed9
@metamask-previews/multichain-network-controller@4.0.0-preview-0f1d8eed9
@metamask-previews/multichain-transactions-controller@8.0.0-preview-0f1d8eed9
@metamask-previews/name-controller@10.0.0-preview-0f1d8eed9
@metamask-previews/network-connection-banner-controller@1.0.0-preview-0f1d8eed9
@metamask-previews/network-controller@37.1.1-preview-0f1d8eed9
@metamask-previews/network-enablement-controller@7.0.2-preview-0f1d8eed9
@metamask-previews/node-utils@0.1.0-preview-0f1d8eed9
@metamask-previews/notification-services-controller@30.1.0-preview-0f1d8eed9
@metamask-previews/passkey-controller@4.1.0-preview-0f1d8eed9
@metamask-previews/permission-controller@14.0.0-preview-0f1d8eed9
@metamask-previews/permission-log-controller@6.0.0-preview-0f1d8eed9
@metamask-previews/perps-controller@22.0.0-preview-0f1d8eed9
@metamask-previews/phishing-controller@18.2.1-preview-0f1d8eed9
@metamask-previews/platform-api-docs@0.2.1-preview-0f1d8eed9
@metamask-previews/polling-controller@17.0.0-preview-0f1d8eed9
@metamask-previews/preferences-controller@24.0.0-preview-0f1d8eed9
@metamask-previews/profile-controller@1.2.0-preview-0f1d8eed9
@metamask-previews/profile-metrics-controller@5.2.0-preview-0f1d8eed9
@metamask-previews/profile-sync-controller@36.0.0-preview-0f1d8eed9
@metamask-previews/ramps-controller@28.0.0-preview-0f1d8eed9
@metamask-previews/rate-limit-controller@8.0.0-preview-0f1d8eed9
@metamask-previews/react-data-query@2.0.0-preview-0f1d8eed9
@metamask-previews/remote-feature-flag-controller@7.0.0-preview-0f1d8eed9
@metamask-previews/sample-controllers@6.0.0-preview-0f1d8eed9
@metamask-previews/seedless-onboarding-controller@12.0.0-preview-0f1d8eed9
@metamask-previews/selected-network-controller@27.0.0-preview-0f1d8eed9
@metamask-previews/sentinel-api-service@2.1.0-preview-0f1d8eed9
@metamask-previews/shield-controller@7.1.0-preview-0f1d8eed9
@metamask-previews/signature-controller@40.0.0-preview-0f1d8eed9
@metamask-previews/smart-transactions-controller@27.2.0-preview-0f1d8eed9
@metamask-previews/snap-account-service@4.0.0-preview-0f1d8eed9
@metamask-previews/social-controllers@3.12.1-preview-0f1d8eed9
@metamask-previews/solana-test-validator-up@2.0.0-preview-0f1d8eed9
@metamask-previews/stellar-quickstart-up@0.0.0-preview-0f1d8eed9
@metamask-previews/storage-service@2.0.0-preview-0f1d8eed9
@metamask-previews/subscription-controller@15.1.1-preview-0f1d8eed9
@metamask-previews/transaction-controller@72.2.1-preview-0f1d8eed9
@metamask-previews/transaction-pay-controller@30.3.1-preview-0f1d8eed9
@metamask-previews/transaction-pay-middleware@2.0.0-preview-0f1d8eed9
@metamask-previews/type-utils@0.1.0-preview-0f1d8eed9
@metamask-previews/user-operation-controller@42.0.1-preview-0f1d8eed9
@metamask-previews/utils@12.0.1-preview-0f1d8eed9
@metamask-previews/wallet@20.0.0-preview-0f1d8eed9
@metamask-previews/watch-only-account-service@0.1.0-preview-0f1d8eed9

@sahar-fehri
sahar-fehri marked this pull request as ready for review October 28, 2026 09:39
@sahar-fehri
sahar-fehri requested review from a team as code owners October 28, 2026 09:39
@sahar-fehri
sahar-fehri deployed to default-branch October 28, 2026 09:39 — with GitHub Actions Active

@cursor cursor 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.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ 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.

Comment thread packages/core-backend/src/ws/launchpad/types.ts Outdated
* @param raw - The raw abbreviated-key event.
* @returns The normalized event.
*/
export function normalizeEvent(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why not keeping the raw data structure from the backend?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

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.

Comment on lines +85 to +95
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;

@Kriys94 Kriys94 Oct 28, 2026 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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.

@sahar-fehri sahar-fehri Oct 28, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

thanks @Kriys94 ! update here 84d3bb3

@Kriys94

Kriys94 commented Oct 28, 2026

Copy link
Copy Markdown
Contributor

Nice refactor ;)

Comment thread packages/core-backend/src/ws/launchpad/LaunchpadTokenDetailsService.test.ts Outdated
Comment thread packages/core-backend/src/ws/launchpad/LaunchpadTokenDetailsService.test.ts Outdated
Comment thread packages/core-backend/src/ws/launchpad/LaunchpadTokenDetailsService.test.ts Outdated
Comment thread packages/core-backend/src/ws/launchpad/normalize.ts
Comment thread packages/core-backend/src/ws/launchpad/normalize.ts
Comment thread packages/core-backend/src/ws/launchpad/normalize.ts Outdated
Comment thread packages/core-backend/src/ws/launchpad/types.ts
Comment thread packages/core-backend/src/ws/ohlcv/OHLCVService.ts
Comment thread packages/core-backend/src/ws/ohlcv/OHLCVService.ts
@sahar-fehri
sahar-fehri added this pull request to the merge queue Oct 28, 2026
Merged via the queue into main with commit 5adb7d8 Oct 28, 2026
262 checks passed
@sahar-fehri
sahar-fehri deleted the feat/core-backend-launchpad-tdp-ws branch October 28, 2026 15:57
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.

3 participants