Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions packages/perps-controller/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- Consolidate the Perps analytics contract so clients import a single source of truth from `@metamask/perps-controller` ([#9311](https://github.com/MetaMask/core/pull/9311))
- Add five new `PerpsAnalyticsEvent` members: `TransactionConsidered` (`Perp Transaction Considered`), `TradeQuoteReceived` (`Perp Trade Quote Received`), `SearchQuery` (`Perp Search Query`), `SearchResultTapped` (`Perp Search Result Tapped`), `SearchAbandoned` (`Perp Search Abandoned`)
- Add new `PERPS_EVENT_PROPERTY` keys: `entry_point`, `discovery_source`, `perp_discovery_source`, `utm_source`, `utm_medium`, `utm_campaign`, `utm_content`, `utm_term`, `watchlisted`, `hl_fee_rate`, `bulk_action_id`, `environment_type`, `order_context`, `order_size_percent`, `limit_price_input_type`, `limit_price_input_preset`, `order_has_tp`, `order_has_sl`, `quote_latency_ms`, `error_reason`, `saved_order`, `default_payment_token`, `default_size_amount`, `default_leverage`, `default_auto_close`, `order_execution_latency_ms`, `screen_context`, `from_token`, `from_chain`, `to_token`, `to_chain`, `search_query`, `results_count`, `result_rank`, `mode`, `current_token`, `sort_field`, `sort_direction`, `filter_category`, `time_on_screen_ms`
- Add new `PERPS_EVENT_VALUE` entries: `INTERACTION_TYPE.{SORT_APPLIED, FILTER_APPLIED, SEARCH_RESULT_TAPPED, SEARCH_CHIP_TAPPED, SEARCH_SIGNAL_TILE_TAPPED, PAYMENT_TOKEN_SELECTOR_DISMISSED}`, `ACTION.ABANDON_ORDER`, `BUTTON_CLICKED.{PLACE_ORDER, CLOSE, REDUCE_EXPOSURE}`, `SCREEN_TYPE.{SEARCH_RESULTS_SHOWN, SEARCH_NO_RESULTS}`
- Add `PerpsAttributionContext` type and `setAttributionContext` / `getAttributionContext` / `clearAttributionContext` / `mergeAttributionContext` on `PerpsController` (with matching messenger actions) for transient UTM attribution propagation
- Extend `TrackingData` with `entryPoint`, `discoverySource`, `perpDiscoverySource`, `hlFeeRate`; extend `TPSLTrackingData` with `entryPoint`, `discoverySource`, `perpDiscoverySource`; add optional `trackingData` to `CancelOrderParams`
- Add Perps Advanced Chart analytics constants to `PERPS_EVENT_PROPERTY` and `PERPS_EVENT_VALUE` so mobile can import chart instrumentation keys from `@metamask/perps-controller` instead of maintaining a local mirror ([#9221](https://github.com/MetaMask/core/pull/9221))
- New `PERPS_EVENT_PROPERTY` keys: `CHART_LIBRARY`, `ASSET_TYPE`
- New `PERPS_EVENT_VALUE.CHART_LIBRARY` group: `lightweight`, `advanced`
Expand All @@ -18,6 +24,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Changed

- Consolidate the Perps transaction analytics pipeline in `TradingService` ([#9311](https://github.com/MetaMask/core/pull/9311))
- Emit a `status: 'submitted'` event before the provider round-trip for trade (`placeOrder`), close (`closePosition`), cancel (`cancelOrder`) and risk-management (`updatePositionTPSL`) operations
- Populate `metamask_fee` on successful `flipPosition` trades from `trackingData`
- Add `leverage` to `Perp Position Close Transaction` event properties
- Add `hl_fee_rate` to trade and close events when present in `trackingData`; omit it entirely when unavailable
- Generate a `bulk_action_id` UUID for `closePositions` / `cancelOrders` and attach it to each per-item event and the batch summary event
- Propagate `entry_point`, `discovery_source`, `perp_discovery_source` from `trackingData` onto trade/close/cancel/risk events; the legacy `source` field on `TPSLTrackingData` is now deprecated
- On `subscribeToPrices` calls with `includeMarketData: true` (focused detail/ticket screens), the `price` field in each `PriceUpdate` is now driven by the per-symbol `activeAssetCtx` WebSocket stream (`midPx`, falling back to `markPx`) rather than the main-DEX `allMids` snapshot, which Hyperliquid throttles to a ~5 s push cadence ([#9160](https://github.com/MetaMask/core/pull/9160))
- Price source selection is **per-subscriber**: focused (`includeMarketData: true`) callbacks receive the fast-stream price; list/overview (`includeMarketData: false`) callbacks always receive the raw `allMids` baseline, even when both subscriber types share the same symbol.
- The fast-stream price is preferred only while it is fresh (within a 10 s staleness window); `allMids` takes back over automatically once the `activeAssetCtx` stream goes quiet.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,35 @@ export type PerpsControllerGetWithdrawalRoutesAction = {
handler: PerpsController['getWithdrawalRoutes'];
};

/**
* Set the transient UTM / discovery attribution context (TAT-3133, TAT-3140).
* Replaces any previously set context. Held in-memory only — not persisted.
*
* @param context - The attribution context (UTM fields) to store.
*/
export type PerpsControllerSetAttributionContextAction = {
type: `PerpsController:setAttributionContext`;
handler: PerpsController['setAttributionContext'];
};

/**
* Get a copy of the current attribution context (TAT-3133, TAT-3140).
*
* @returns A shallow copy of the stored attribution context.
*/
export type PerpsControllerGetAttributionContextAction = {
type: `PerpsController:getAttributionContext`;
handler: PerpsController['getAttributionContext'];
};

/**
* Clear the stored attribution context (TAT-3133, TAT-3140).
*/
export type PerpsControllerClearAttributionContextAction = {
type: `PerpsController:clearAttributionContext`;
handler: PerpsController['clearAttributionContext'];
};

/**
* Toggle between testnet and mainnet
*
Expand Down Expand Up @@ -1073,6 +1102,9 @@ export type PerpsControllerMethodActions =
| PerpsControllerValidateClosePositionAction
| PerpsControllerValidateWithdrawalAction
| PerpsControllerGetWithdrawalRoutesAction
| PerpsControllerSetAttributionContextAction
| PerpsControllerGetAttributionContextAction
| PerpsControllerClearAttributionContextAction
| PerpsControllerToggleTestnetAction
| PerpsControllerSwitchProviderAction
| PerpsControllerGetCurrentNetworkAction
Expand Down
71 changes: 71 additions & 0 deletions packages/perps-controller/src/PerpsController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ import type {
PerpsPlatformDependencies,
PerpsLogger,
PerpsActiveProviderMode,
PerpsAnalyticsProperties,
PerpsAttributionContext,
PerpsProviderType,
PerpsSelectedPaymentToken,
PerpsRemoteFeatureFlagState,
Expand Down Expand Up @@ -734,6 +736,7 @@ const MESSENGER_EXPOSED_METHODS = [
'calculateMaintenanceMargin',
'cancelOrder',
'cancelOrders',
'clearAttributionContext',
'clearDepositResult',
'clearPendingTradeConfiguration',
'clearPendingTransactionRequests',
Expand All @@ -750,6 +753,7 @@ const MESSENGER_EXPOSED_METHODS = [
'getAccountState',
'getActiveProvider',
'getActiveProviderOrNull',
'getAttributionContext',
'getAvailableDexs',
'getBlockExplorerUrl',
'getCachedMarketDataForActiveProvider',
Expand Down Expand Up @@ -790,6 +794,7 @@ const MESSENGER_EXPOSED_METHODS = [
'saveOrderBookGrouping',
'savePendingTradeConfiguration',
'saveTradeConfiguration',
'setAttributionContext',
'setLiveDataConfig',
'setSelectedPaymentToken',
'startEligibilityMonitoring',
Expand Down Expand Up @@ -869,6 +874,13 @@ export class PerpsController extends BaseController<
// through to each provider, which applies its own default when this is undefined.
readonly #priceDeviationLimit?: number;

/**
* Transient UTM / discovery attribution context (TAT-3133, TAT-3140).
* Held in-memory only (never persisted in PerpsControllerState) and merged
* into analytics event properties via {@link mergeAttributionContext}.
*/
#attributionContext: PerpsAttributionContext = {};

/**
* Check if MYX provider is enabled via feature flag
* Uses same pattern as other feature flags in FeatureFlagConfigurationService
Expand Down Expand Up @@ -3852,6 +3864,65 @@ export class PerpsController extends BaseController<
}
}

/**
* Set the transient UTM / discovery attribution context (TAT-3133, TAT-3140).
* Replaces any previously set context. Held in-memory only — not persisted.
*
* @param context - The attribution context (UTM fields) to store.
*/
setAttributionContext(context: PerpsAttributionContext): void {
this.#attributionContext = { ...context };
}

/**
* Get a copy of the current attribution context (TAT-3133, TAT-3140).
*
* @returns A shallow copy of the stored attribution context.
*/
getAttributionContext(): PerpsAttributionContext {
return { ...this.#attributionContext };
}

/**
* Clear the stored attribution context (TAT-3133, TAT-3140).
*/
clearAttributionContext(): void {
this.#attributionContext = {};
}

/**
* Merge the stored UTM attribution context into a set of analytics event
* properties (TAT-3133, TAT-3140). Only defined UTM fields are added, mapped
* to their canonical PERPS_EVENT_PROPERTY keys. Provided properties take
* precedence and are never overwritten.
*
* @param properties - Base event properties to merge attribution into.
* @returns A new properties object including any defined UTM keys.
*/
mergeAttributionContext(
properties: PerpsAnalyticsProperties = {},
): PerpsAnalyticsProperties {
const utm: PerpsAnalyticsProperties = {};
const context = this.#attributionContext;
if (context.utmSource !== undefined) {
utm[PERPS_EVENT_PROPERTY.UTM_SOURCE] = context.utmSource;
}
if (context.utmMedium !== undefined) {
utm[PERPS_EVENT_PROPERTY.UTM_MEDIUM] = context.utmMedium;
}
if (context.utmCampaign !== undefined) {
utm[PERPS_EVENT_PROPERTY.UTM_CAMPAIGN] = context.utmCampaign;
}
if (context.utmContent !== undefined) {
utm[PERPS_EVENT_PROPERTY.UTM_CONTENT] = context.utmContent;
}
if (context.utmTerm !== undefined) {
utm[PERPS_EVENT_PROPERTY.UTM_TERM] = context.utmTerm;
}
// Provided properties win over attribution context.
return { ...utm, ...properties };
}

/**
* Toggle between testnet and mainnet
*
Expand Down
78 changes: 78 additions & 0 deletions packages/perps-controller/src/constants/eventNames.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,66 @@ export const PERPS_EVENT_PROPERTY = {
// Account setup / abstraction mode (PERPS_ACCOUNT_SETUP)
ABSTRACTION_MODE: 'abstraction_mode',
PREVIOUS_ABSTRACTION_MODE: 'previous_abstraction_mode',

// Entry point / discovery attribution (TAT-3080)
ENTRY_POINT: 'entry_point',
DISCOVERY_SOURCE: 'discovery_source',
PERP_DISCOVERY_SOURCE: 'perp_discovery_source',

// UTM attribution context (TAT-3133, TAT-3140)
UTM_SOURCE: 'utm_source',
UTM_MEDIUM: 'utm_medium',
UTM_CAMPAIGN: 'utm_campaign',
UTM_CONTENT: 'utm_content',
UTM_TERM: 'utm_term',

// Watchlist membership at event time (TAT-3148)
WATCHLISTED: 'watchlisted',

// HyperLiquid protocol fee rate on trade + close (TAT-3149)
HL_FEE_RATE: 'hl_fee_rate',

// Bulk action correlation id for batch close/cancel (TAT-3150)
BULK_ACTION_ID: 'bulk_action_id',

// Client environment (Extension supplies value) (TAT-3335)
ENVIRONMENT_TYPE: 'environment_type',

// Order funnel / consideration + quote properties (TAT-3084)
ORDER_CONTEXT: 'order_context',
ORDER_SIZE_PERCENT: 'order_size_percent',
LIMIT_PRICE_INPUT_TYPE: 'limit_price_input_type',
LIMIT_PRICE_INPUT_PRESET: 'limit_price_input_preset',
ORDER_HAS_TP: 'order_has_tp',
ORDER_HAS_SL: 'order_has_sl',
QUOTE_LATENCY_MS: 'quote_latency_ms',
ERROR_REASON: 'error_reason',
SAVED_ORDER: 'saved_order',
DEFAULT_PAYMENT_TOKEN: 'default_payment_token',
DEFAULT_SIZE_AMOUNT: 'default_size_amount',
DEFAULT_LEVERAGE: 'default_leverage',
DEFAULT_AUTO_CLOSE: 'default_auto_close',
ORDER_EXECUTION_LATENCY_MS: 'order_execution_latency_ms',
SCREEN_CONTEXT: 'screen_context',
FROM_TOKEN: 'from_token',
FROM_CHAIN: 'from_chain',
TO_TOKEN: 'to_token',
TO_CHAIN: 'to_chain',

// Search / discovery query properties (TAT-3144, TAT-3202, TAT-3151)
SEARCH_QUERY: 'search_query',
RESULTS_COUNT: 'results_count',
RESULT_RANK: 'result_rank',
MODE: 'mode',
CURRENT_TOKEN: 'current_token',

// Sort / filter properties (TAT-3142)
SORT_FIELD: 'sort_field',
SORT_DIRECTION: 'sort_direction',
FILTER_CATEGORY: 'filter_category',

// Time-on-screen for abandon tracking (TAT-3136)
TIME_ON_SCREEN_MS: 'time_on_screen_ms',
} as const;

/**
Expand Down Expand Up @@ -396,6 +456,15 @@ export const PERPS_EVENT_VALUE = {
SLIPPAGE_LIMIT_BLOCKED_ORDER: 'slippage_limit_blocked_order',
// Discovery analytics
MARKET_LIST_FILTER: 'market_list_filter',
// Sort / filter interactions (TAT-3142)
SORT_APPLIED: 'sort_applied',
FILTER_APPLIED: 'filter_applied',
// Search interactions (TAT-3144, TAT-3202)
SEARCH_RESULT_TAPPED: 'search_result_tapped',
SEARCH_CHIP_TAPPED: 'search_chip_tapped',
SEARCH_SIGNAL_TILE_TAPPED: 'search_signal_tile_tapped',
// Pay-with token selector dismissed (TAT-3151)
PAYMENT_TOKEN_SELECTOR_DISMISSED: 'payment_token_selector_dismissed',
},
MAX_SLIPPAGE_SOURCE: {
DEFAULT: 'default',
Expand Down Expand Up @@ -480,6 +549,9 @@ export const PERPS_EVENT_VALUE = {
COMPLIANCE_BLOCK_NOTIF: 'compliance_block_notif',
// Deposit + order (pay-with token) cancel toast
CANCEL_TRADE_WITH_TOKEN_TOAST: 'cancel_trade_with_token_toast',
// Search result screen states (TAT-3144)
SEARCH_RESULTS_SHOWN: 'search_results_shown',
SEARCH_NO_RESULTS: 'search_no_results',
},
SETTING_TYPE: {
LEVERAGE: 'leverage',
Expand Down Expand Up @@ -512,6 +584,8 @@ export const PERPS_EVENT_VALUE = {
// Flip position actions with direction specificity
FLIP_LONG_TO_SHORT: 'flip_long_to_short',
FLIP_SHORT_TO_LONG: 'flip_short_to_long',
// Order funnel abandonment (TAT-3136)
ABANDON_ORDER: 'abandon_order',
},
// Risk management sources
RISK_MANAGEMENT_SOURCE: {
Expand Down Expand Up @@ -555,6 +629,10 @@ export const PERPS_EVENT_VALUE = {
WATCHLIST: 'watchlist',
TOP_MOVERS: 'top_movers',
WHATS_HAPPENING: 'whats_happening',
// Order + position management CTAs (TAT-3135, TAT-3141)
PLACE_ORDER: 'place_order',
CLOSE: 'close',
REDUCE_EXPOSURE: 'reduce_exposure',
},
BUTTON_LOCATION: {
PERPS_HOME: 'perps_home',
Expand Down
4 changes: 4 additions & 0 deletions packages/perps-controller/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export type {
PerpsControllerClearWithdrawResultAction,
PerpsControllerClosePositionAction,
PerpsControllerClosePositionsAction,
PerpsControllerClearAttributionContextAction,
PerpsControllerCompleteWithdrawalFromHistoryAction,
PerpsControllerDepositWithConfirmationAction,
PerpsControllerDepositWithOrderAction,
Expand All @@ -63,6 +64,7 @@ export type {
PerpsControllerGetAccountStateAction,
PerpsControllerGetActiveProviderAction,
PerpsControllerGetActiveProviderOrNullAction,
PerpsControllerGetAttributionContextAction,
PerpsControllerGetAvailableDexsAction,
PerpsControllerGetBlockExplorerUrlAction,
PerpsControllerGetCachedMarketDataForActiveProviderAction,
Expand Down Expand Up @@ -101,6 +103,7 @@ export type {
PerpsControllerSaveOrderBookGroupingAction,
PerpsControllerSavePendingTradeConfigurationAction,
PerpsControllerSaveTradeConfigurationAction,
PerpsControllerSetAttributionContextAction,
PerpsControllerSetLiveDataConfigAction,
PerpsControllerSetSelectedPaymentTokenAction,
PerpsControllerStartEligibilityMonitoringAction,
Expand Down Expand Up @@ -233,6 +236,7 @@ export type {
PerpsTraceName,
PerpsTraceValue,
PerpsAnalyticsProperties,
PerpsAttributionContext,
PerpsMetrics,
PerpsDebugLogger,
PerpsStreamManager,
Expand Down
Loading
Loading