Skip to content

feat(perps): [Analytics][Core] Consolidate perps-controller analytics contract and transaction pipeline - #9311

Merged
abretonc7s merged 6 commits into
mainfrom
TAT-3463-feat-consolidate-perps-analytics
Jun 30, 2026
Merged

abretonc7s merged 6 commits into
mainfrom
TAT-3463-feat-consolidate-perps-analytics

Conversation

@abretonc7s

@abretonc7s abretonc7s commented Jun 30, 2026 •

Copy link
Copy Markdown
Contributor

Explanation

Perps analytics instrumentation was fragmented: event names, property keys, and value enums
were partly mirrored in clients, and several ticketed properties/events (submitted status,
discovery attribution, UTM context, hl_fee_rate, bulk_action_id, flip metamask_fee,
close leverage) had no home in core. This made client wiring inconsistent and blocked a
number of analytics tickets.

This PR consolidates the analytics contract and the transaction pipeline inside
@metamask/perps-controller so Mobile and Extension can adopt a single source of truth in
follow-up tickets after the package is published.

Contract (constants + types):

  • Adds five new PerpsAnalyticsEvent members — Perp Transaction Considered,
    Perp Trade Quote Received, Perp Search Query, Perp Search Result Tapped,
    Perp Search Abandoned — and no others (nine existing names are unchanged).
  • Adds the ticket-sourced PERPS_EVENT_PROPERTY keys (entry point / discovery, UTM,
    watchlisted, hl_fee_rate, bulk_action_id, environment_type, order-funnel/quote,
    search, sort/filter, time_on_screen_ms) and new PERPS_EVENT_VALUE entries
    (INTERACTION_TYPE, ACTION.abandon_order, BUTTON_CLICKED, SCREEN_TYPE).
  • Adds PerpsAttributionContext and setAttributionContext / getAttributionContext /
    clearAttributionContext / mergeAttributionContext on PerpsController (with messenger
    actions) for transient, in-memory UTM attribution propagation.

Pipeline (TradingService):

  • Emits a status: 'submitted' event before the provider round-trip for trade, close,
    cancel and risk-management operations.
  • Populates metamask_fee on successful flip trades; adds leverage to close events; adds
    hl_fee_rate to trade/close events when available (omitted otherwise); generates a
    bulk_action_id for batch close/cancel and attaches it to per-item and summary events;
    propagates entry_point / discovery_source / perp_discovery_source onto
    trade/close/cancel/risk events (source on TPSLTrackingData is now deprecated).

Package effects: new exports from @metamask/perps-controller (PerpsAttributionContext,
three new messenger action types) and from @metamask/perps-controller/constants and
/types. All changes are additive; no events are renamed or removed.

Validation: eventNames.test.ts (60) and TradingService.test.ts (93, incl. 12 new) pass;
PerpsController.state.test.ts (43, incl. 6 attribution cases), and
PerpsController.trading.test.ts + PerpsController.operations.test.ts (71) pass; full
monorepo yarn build is green (zero TS errors); changed-file ESLint and changelog:validate
are clean. The headless core smoke recipe could not run because the recipe runner's harness
dependency (@farmslot/recipe-harness) is not provisioned in this environment; controller
wiring is instead covered by the clean build and passing controller tests.

References

  • https://consensyssoftware.atlassian.net/browse/TAT-3463
  • Supersedes / unblocks: TAT-3080, TAT-3084, TAT-3133, TAT-3134, TAT-3136, TAT-3140,
    TAT-3142, TAT-3144, TAT-3146, TAT-3147, TAT-3148, TAT-3149, TAT-3150, TAT-3151, TAT-3202,
    TAT-3335 (key only)
  • Follow-up (client wiring, separate PRs): Mobile + Extension event wiring; Segment TAT-2409 / TAT-3121

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 — N/A: all changes are additive (no breaking changes)

Note

Medium Risk
Adds additive analytics surface and changes when/how many MetaMetrics events fire for trades (submitted + terminal), which can affect dashboards; trading logic itself is mostly instrumentation.

Overview
Centralizes Perps analytics in @metamask/perps-controller so Mobile/Extension can stop mirroring event names and property keys locally.

Contract: Adds five PerpsAnalyticsEvent values (transaction considered, trade quote, search query/result/abandon), many new PERPS_EVENT_PROPERTY / PERPS_EVENT_VALUE entries (discovery, UTM, order funnel, search, sort/filter), extends TrackingData / TPSLTrackingData and optional trackingData on CancelOrderParams, and deprecates TPSLTrackingData.source in favor of entry/discovery fields.

Attribution: Introduces in-memory PerpsAttributionContext with set / get / clear / mergeAttributionContext on PerpsController (plus messenger actions) for UTM propagation onto events.

TradingService pipeline: Emits status: submitted before provider calls for place/close/cancel/TP-SL and flip; enriches terminal events with discovery fields, optional hl_fee_rate, close leverage, flip metamask_fee, and bulk_action_id on batch close/cancel; ensures terminal outcomes when flip fails without throw or close runs without a local position.

Tests cover the new constants, attribution APIs, and the consolidated analytics behavior.

Reviewed by Cursor Bugbot for commit 155e0c5. Bugbot is set up for automated code reviews on this repo. Configure here.

@abretonc7s

Copy link
Copy Markdown
Contributor Author

Automated dev run — TAT-3463

Metric Value
Run 156b4d70
Duration ?
Model claude/opus
Nudges 0
Worker report

Report — TAT-3463 Consolidate perps-controller analytics contract + transaction pipeline

Summary

Added the consolidated Perps analytics contract (event names, property keys, value
enums, UTM attribution context API) and the transaction-pipeline behavior (submitted
events, fee/leverage/fee-rate/bulk-id propagation, discovery attribution) inside
@metamask/perps-controller. No UI/UX, no client wiring, no Segment work — clients adopt
this in follow-up tickets after the package is published.

Changed files

  • src/constants/eventNames.ts — 40 new PERPS_EVENT_PROPERTY keys; new PERPS_EVENT_VALUE
    entries under INTERACTION_TYPE, ACTION, BUTTON_CLICKED, SCREEN_TYPE.
  • src/types/index.ts — 5 new PerpsAnalyticsEvent members; new PerpsAttributionContext
    type; extended TrackingData (entryPoint, discoverySource, perpDiscoverySource,
    hlFeeRate); extended TPSLTrackingData (attribution + deprecated source); added
    optional trackingData to CancelOrderParams.
  • src/services/TradingService.ts — #buildAttributionProperties + #trackSubmitted
    helpers; status=submitted emission before provider calls (trade/close/cancel/risk);
    metamask_fee on flip success; leverage on close events; hl_fee_rate on trade/close
    (omitted when absent); bulk_action_id UUID for closePositions/cancelOrders threaded
    to per-item + batch-summary events; discovery attribution propagation.
  • src/PerpsController.ts — #attributionContext field + setAttributionContext /
    getAttributionContext / clearAttributionContext / mergeAttributionContext; registered
    the three mutating methods in MESSENGER_EXPOSED_METHODS.
  • src/PerpsController-method-action-types.ts — 3 new action types + union members.
  • src/index.ts — exported the 3 new action types + PerpsAttributionContext.
  • Tests: tests/src/constants/eventNames.test.ts (+141), tests/src/services/TradingService.test.ts
    (+316, 12 new cases), tests/src/PerpsController.state.test.ts (+58, 6 attribution cases).
  • CHANGELOG.md — [Unreleased] Added (contract) + Changed (pipeline) entries.

Validation commands & results

  • yarn workspace @metamask/perps-controller run jest --no-coverage tests/src/constants/eventNames.test.ts
    → 60 passed.
  • yarn workspace @metamask/perps-controller run jest --no-coverage tests/src/services/TradingService.test.ts
    → 93 passed (81 existing + 12 new: submitted ×4, flip metamask_fee, close leverage,
    hl_fee_rate present/omitted, bulk_action_id ×3, attribution).
  • yarn workspace @metamask/perps-controller run jest --no-coverage tests/src/PerpsController.state.test.ts
    → 43 passed (incl. 6 attribution-context cases).
  • yarn workspace @metamask/perps-controller run jest --no-coverage tests/src/PerpsController.trading.test.ts tests/src/PerpsController.operations.test.ts
    → 71 passed (confirms additive submitted events did not break controller-level analytics).
  • yarn build (full monorepo, dependency order) → exit 0, zero TS errors; needed because
    exported types/exports changed. packages/perps-controller built clean for ESM + CJS.
  • yarn eslint <changed files> → clean.
  • yarn workspace @metamask/perps-controller run changelog:validate → exit 0.

Perps smoke recipe (step 10)

  • The checklist's gated command keys on git diff --quiet main...HEAD; pre-commit there is
    no committed diff, so it self-reported N/A.
  • A manual run against the working-tree checkout failed at the harness layer:
    ERR_MODULE_NOT_FOUND: Cannot find package '@farmslot/recipe-harness' — the recipe runner's
    own dependency is not provisioned in this environment. This is a harness provisioning defect,
    unrelated to the change. Controller wiring + read path are instead proven by the clean
    full-monorepo build and the passing controller unit tests.

Risks

  • Additive analytics only — no event names removed/renamed; exactly the five new event names
    from Jira were added (asserted by Object.keys(PerpsAnalyticsEvent).toHaveLength(14)).
  • submitted events are new emissions fired before each provider round-trip; downstream
    dashboards will see them in addition to existing executed/failed events.
  • Attribution context is in-memory (not persisted) by design.

Client follow-up (separate tickets)

  • Mobile + Extension: bump the published package, wire PERPS_SCREEN_VIEWED,
    PERPS_UI_INTERACTION, and the new funnel/search events using the exported constants;
    supply environment_type value (Extension). Segment work (TAT-2409, TAT-3121) is separate.

@abretonc7s abretonc7s changed the title chore: prepare farmslot publication pkg-156b4d70-mr0f3uuh feat(perps): [Analytics][Core] Consolidate perps-controller analytics contract and transaction pipeline Jun 30, 2026
@abretonc7s
abretonc7s marked this pull request as ready for review June 30, 2026 09:12
@abretonc7s
abretonc7s requested review from a team as code owners June 30, 2026 09:12
Comment thread packages/perps-controller/src/services/TradingService.ts
abretonc7s and others added 3 commits June 30, 2026 12:11
…n pipeline

Add the consolidated Perps analytics contract (five new PerpsAnalyticsEvent
names, ticket-sourced PERPS_EVENT_PROPERTY keys and PERPS_EVENT_VALUE entries,
PerpsAttributionContext API for UTM propagation) and the transaction-pipeline
behavior in TradingService: status=submitted events before provider calls,
metamask_fee on flip success, leverage on close, hl_fee_rate on trade/close
(omitted when absent), bulk_action_id for batch close/cancel, and discovery
attribution propagation. Additive only; no events renamed or removed.

Ref: TAT-3463
…PR 9311

Co-authored-by: Cursor <cursoragent@cursor.com>
@abretonc7s
abretonc7s force-pushed the TAT-3463-feat-consolidate-perps-analytics branch from 4fbaf2a to 3e429f6 Compare June 30, 2026 10:54
Comment thread packages/perps-controller/src/services/TradingService.ts
Comment thread packages/perps-controller/src/services/TradingService.ts

@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 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 1d97624. Configure here.

Comment thread packages/perps-controller/src/services/TradingService.ts
Resolves the lint:misc:check CI failure by bringing the consolidated
analytics/transaction-pipeline files into oxfmt compliance. Formatting
only; no behavior or API changes.
@abretonc7s
abretonc7s enabled auto-merge June 30, 2026 16:39
@abretonc7s
abretonc7s added this pull request to the merge queue Jun 30, 2026
Merged via the queue into main with commit 07ae404 Jun 30, 2026
400 checks passed
@abretonc7s
abretonc7s deleted the TAT-3463-feat-consolidate-perps-analytics branch June 30, 2026 17:32
pull Bot pushed a commit to Reality2byte/core that referenced this pull request Jun 30, 2026
…ask#9322)

## Explanation

Mobile ships Auto Close TP/SL RoE sign toggle analytics using interim
local constants in `perpsEventsLocal.ts` (`tpsl_roe_sign_toggled`,
`roe_sign`) because these keys were not yet in the published
`@metamask/perps-controller` contract. After MetaMask#9311 consolidated the
analytics contract in core, this small gap remained for the RoE sign
toggle interaction.

This PR adds the missing shared constants to
`@metamask/perps-controller` so clients can import typed values instead
of maintaining local mirrors:

- `PERPS_EVENT_PROPERTY.ROE_SIGN` → `roe_sign` (the toggled sign: `'+'`
| `'-'`)
- `PERPS_EVENT_VALUE.INTERACTION_TYPE.TPSL_ROE_SIGN_TOGGLED` →
`tpsl_roe_sign_toggled`

No controller or runtime behavior changes — event emission stays
client-side (`Perp UI Interaction`). Only the contract/constants surface
is extended.

## References

- https://consensyssoftware.atlassian.net/browse/TAT-3429
- Follow-up client wiring:
https://consensyssoftware.atlassian.net/browse/TAT-3430 (Mobile +
Extension replace `perpsEventsLocal.ts` after package release)
- Builds on analytics consolidation: MetaMask#9311

## Checklist

- [x] I've updated the test suite for new or updated code as appropriate
- [x] I've updated documentation (JSDoc, Markdown, etc.) for new or
updated code as appropriate
- [x] I've communicated my changes to consumers by [updating changelogs
for packages I've
changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md)
- [x] I've introduced [breaking
changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md)
in this PR and have prepared draft pull requests for clients and
consumer packages to resolve them — N/A: additive constants only

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Additive analytics constants only; no runtime, auth, or trading
behavior changes.
> 
> **Overview**
> Extends the shared Perps analytics contract in
`@metamask/perps-controller` with constants for **Auto Close TP/SL RoE
sign toggle** events, so mobile and extension can drop local mirrors
(`perpsEventsLocal.ts`) and import typed keys from core.
> 
> Adds **`PERPS_EVENT_PROPERTY.ROE_SIGN`** (`roe_sign`) for the toggled
sign (`'+'` | `'-'`) and
**`PERPS_EVENT_VALUE.INTERACTION_TYPE.TPSL_ROE_SIGN_TOGGLED`**
(`tpsl_roe_sign_toggled`). Changelog and `eventNames` tests assert the
new exports; **no controller or emission logic** changes in this
package.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
f933d88. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
pull Bot pushed a commit to Reality2byte/metamask-mobile that referenced this pull request Jul 16, 2026
…Mobile (MetaMask#33095)

## **Description**

Adopts the controller-owned perps analytics contract and completes the
assigned tickets.

Beyond the initial contract adoption (register the five controller
MetaMetrics events, entry/discovery/UTM/`hlFeeRate`/`metamaskFee`
trackingData on the order lifecycle, controller search events from the
market list, removal of duplicate client-side `PERPS_TRADE_TRANSACTION`
emissions so submitted/terminal come only from the controller), this now
also wires:
- UTM merged onto all client-emitted `PERPS_SCREEN_VIEWED` ()
- search query / result-tapped / abandoned with full prop sets, 500ms
debounce ()
- `sort_applied` / `filter_applied` incl. the watchlist filter
- `place_order` button for ALL users incl. the deposit-required path
- `abandon_order` on genuine exit — back/swipe/hardware-back and
tab-switch-away, suppressed on internal TP/SL / payment-selector nav and
after placement
- `screen_type=add_margin`/`remove_margin` on margin modal open
- `watchlisted` on asset_detail only
- `PERPS_TRANSACTION_CONSIDERED` + `PERPS_TRADE_QUOTE_RECEIVED` now
emitted

No SDK bump or patch required on mobile — Metro uses the correct ESM
build. The published 9.2.1 CommonJS build bug is core-side and fixed in
perps-controller 9.2.2 (MetaMask/core#9471; upstream
nktkas/hyperliquid#165).

### Deferred
- `order_execution_latency_ms` wiring lands with the 9.2.2 bump (new
`TrackingData.orderExecutionLatencyMs`).

## **Changelog**

CHANGELOG entry: null

## **Related issues**

Refs: MetaMask/core#9311

## **Manual testing steps**

```gherkin
Feature: perps controller analytics contract consumption

  Scenario: order and search analytics use controller contract
    Given the app is running with @metamask/perps-controller 9.2.1
    And MetaMetrics is enabled

    When the user opens Perps markets and searches for a symbol
    Then Perp Search Query / Result Tapped / Abandoned events can fire from the market list

    When the user places, closes, cancels, flips, or updates TP/SL
    Then trackingData includes entryPoint/discovery fields and fee fields where available
    And the client does not emit a second Perp Trade Transaction for the same controller event
```

N/A for visual UI walkthrough — analytics-only change; validated via
recipe state assertions + unit tests.

## **Screenshots/Recordings**

Analytics-only change; no screenshots or video. Proof is recipe state
assertions and unit tests in Validation Logs.

## **Validation Recipe**
<details><summary>recipe.json (17 steps — state asserts for AC1–AC5
analytics contract consumption)</summary>

```json
{
  "schema_version": 1,
  "title": "Consume perps controller analytics contract (MANUAL-000002)",
  "description": "Prove Mobile consumes perps-controller analytics contract: package version, MetaMetrics event registration, attribution wiring, search events, and no duplicate client trade-transaction emissions.",
  "validate": {
    "workflow": {
      "entry": "ac1-package-version",
      "nodes": {
        "ac1-package-version": {
          "action": "command",
          "intent": "AC1: confirm installed @metamask/perps-controller includes core#9311 contract",
          "proofTarget": "ac1",
          "cmd": "node -e \"const fs=require('fs'); const pkg=JSON.parse(fs.readFileSync('node_modules/@metamask/perps-controller/package.json','utf8')); const cl=fs.readFileSync('node_modules/@metamask/perps-controller/CHANGELOG.md','utf8'); if (!pkg.version.startsWith('9.')) { console.error('unexpected version', pkg.version); process.exit(1);} if (!cl.includes('MetaMask#9311') && !cl.includes('PerpsAttributionContext')) { console.error('changelog missing contract markers'); process.exit(1);} console.log('ok', pkg.version);\"",
          "next": "ac1-assert-output"
        },
        "ac1-assert-output": {
          "action": "assert_output",
          "intent": "AC1: package version command printed ok",
          "proofTarget": "ac1",
          "source": "ac1-package-version",
          "stream": "stdout",
          "contains": "ok",
          "next": "ac2-search-query"
        },
        "ac2-search-query": {
          "action": "assert_file",
          "intent": "AC2: MetaMetrics registers Perp Search Query",
          "proofTarget": "ac2",
          "path": "app/core/Analytics/MetaMetrics.events.ts",
          "contains": "Perp Search Query",
          "next": "ac2-search-result"
        },
        "ac2-search-result": {
          "action": "assert_file",
          "intent": "AC2: MetaMetrics registers Perp Search Result Tapped",
          "proofTarget": "ac2",
          "path": "app/core/Analytics/MetaMetrics.events.ts",
          "contains": "Perp Search Result Tapped",
          "next": "ac2-search-abandoned"
        },
        "ac2-search-abandoned": {
          "action": "assert_file",
          "intent": "AC2: MetaMetrics registers Perp Search Abandoned",
          "proofTarget": "ac2",
          "path": "app/core/Analytics/MetaMetrics.events.ts",
          "contains": "Perp Search Abandoned",
          "next": "ac2-considered"
        },
        "ac2-considered": {
          "action": "assert_file",
          "intent": "AC2: MetaMetrics registers Perp Transaction Considered",
          "proofTarget": "ac2",
          "path": "app/core/Analytics/MetaMetrics.events.ts",
          "contains": "Perp Transaction Considered",
          "next": "ac2-quote"
        },
        "ac2-quote": {
          "action": "assert_file",
          "intent": "AC2: MetaMetrics registers Perp Trade Quote Received",
          "proofTarget": "ac2",
          "path": "app/core/Analytics/MetaMetrics.events.ts",
          "contains": "Perp Trade Quote Received",
          "next": "ac3-attribution-helper"
        },
        "ac3-attribution-helper": {
          "action": "assert_file",
          "intent": "AC3: attribution helper maps entryPoint",
          "proofTarget": "ac3",
          "path": "app/components/UI/Perps/utils/perpsAnalyticsAttribution.ts",
          "contains": "entryPoint",
          "next": "ac3-deeplink-utm"
        },
        "ac3-deeplink-utm": {
          "action": "assert_file",
          "intent": "AC3: deeplink handler applies UTM attribution context",
          "proofTarget": "ac3",
          "path": "app/core/DeeplinkManager/handlers/legacy/handlePerpsUrl.ts",
          "contains": "setPerpsUtmAttribution",
          "next": "ac3-order-entrypoint"
        },
        "ac3-order-entrypoint": {
          "action": "assert_file",
          "intent": "AC3: order ticket wires entry attribution helper into trackingData",
          "proofTarget": "ac3",
          "path": "app/components/UI/Perps/Views/PerpsOrderView/PerpsOrderView.tsx",
          "contains": "toPerpsEntryAttribution",
          "next": "ac3-order-hl-fee"
        },
        "ac3-order-hl-fee": {
          "action": "assert_file",
          "intent": "AC3: order ticket trackingData includes hlFeeRate",
          "proofTarget": "ac3",
          "path": "app/components/UI/Perps/Views/PerpsOrderView/PerpsOrderView.tsx",
          "contains": "hlFeeRate",
          "next": "ac3-close-entrypoint"
        },
        "ac3-close-entrypoint": {
          "action": "assert_file",
          "intent": "AC3: close position wires entry attribution helper into trackingData",
          "proofTarget": "ac3",
          "path": "app/components/UI/Perps/Views/PerpsClosePositionView/PerpsClosePositionView.tsx",
          "contains": "toPerpsEntryAttribution",
          "next": "ac4-search-event"
        },
        "ac4-search-event": {
          "action": "assert_file",
          "intent": "AC4: market list emits controller Search Query event",
          "proofTarget": "ac4",
          "path": "app/components/UI/Perps/Views/PerpsMarketListView/PerpsMarketListView.tsx",
          "contains": "PERPS_SEARCH_QUERY",
          "next": "ac5-no-client-trade-dup"
        },
        "ac5-no-client-trade-dup": {
          "action": "command",
          "intent": "AC5: usePerpsOrderExecution must not emit client-side PERPS_TRADE_TRANSACTION",
          "proofTarget": "ac5",
          "cmd": "node -e \"const fs=require('fs'); const src=fs.readFileSync('app/components/UI/Perps/hooks/usePerpsOrderExecution.ts','utf8'); if (/track\\\\(\\\\s*MetaMetricsEvents\\\\.PERPS_TRADE_TRANSACTION/.test(src)) { console.error('duplicate client PERPS_TRADE_TRANSACTION still present'); process.exit(1);} console.log('no-client-trade-dup-ok');\"",
          "next": "ac5-assert-output"
        },
        "ac5-assert-output": {
          "action": "assert_output",
          "intent": "AC5: confirm no client trade-transaction duplicate emission",
          "proofTarget": "ac5",
          "source": "ac5-no-client-trade-dup",
          "stream": "stdout",
          "contains": "no-client-trade-dup-ok",
          "next": "ac3-flip-metamask-fee"
        },
        "ac3-flip-metamask-fee": {
          "action": "assert_file",
          "intent": "AC3: flip sheet passes metamaskFee for controller flip analytics",
          "proofTarget": "ac3",
          "path": "app/components/UI/Perps/components/PerpsFlipPositionConfirmSheet/PerpsFlipPositionConfirmSheet.tsx",
          "contains": "metamaskFee",
          "next": "done"
        },
        "done": {
          "action": "end",
          "status": "pass",
          "intent": "All analytics contract consumption assertions passed"
        }
      }
    }
  }
}
```
</details>

## **Validation Logs**
Command:
```bash

```

<details><summary>Full output (17/17 passed)</summary>

```
# MetaMask Recipe Run

Status: pass
Duration: 7.5s
Nodes: 17/17 passed

## Steps
- PASS ac1-package-version (command, 201ms): exitCode=0, stdout=ok 9.2.1

- PASS ac1-assert-output (assert_output, 93ms): source=ac1-package-version, stream=stdout, contains=ok
- PASS ac2-search-query (assert_file, 100ms): path=app/core/Analytics/MetaMetrics.events.ts
- PASS ac2-search-result (assert_file, 83ms): path=app/core/Analytics/MetaMetrics.events.ts
- PASS ac2-search-abandoned (assert_file, 69ms): path=app/core/Analytics/MetaMetrics.events.ts
- PASS ac2-considered (assert_file, 93ms): path=app/core/Analytics/MetaMetrics.events.ts
- PASS ac2-quote (assert_file, 116ms): path=app/core/Analytics/MetaMetrics.events.ts
- PASS ac3-attribution-helper (assert_file, 105ms): path=app/components/UI/Perps/utils/perpsAnalyticsAttribution.ts
- PASS ac3-deeplink-utm (assert_file, 143ms): path=app/core/DeeplinkManager/handlers/legacy/handlePerpsUrl.ts
- PASS ac3-order-entrypoint (assert_file, 310ms): path=app/components/UI/Perps/Views/PerpsOrderView/PerpsOrderView.tsx
- PASS ac3-order-hl-fee (assert_file, 219ms): path=app/components/UI/Perps/Views/PerpsOrderView/PerpsOrderView.tsx
- PASS ac3-close-entrypoint (assert_file, 170ms): path=app/components/UI/Perps/Views/PerpsClosePositionView/PerpsClosePositionView.tsx
- PASS ac4-search-event (assert_file, 286ms): path=app/components/UI/Perps/Views/PerpsMarketListView/PerpsMarketListView.tsx
- PASS ac5-no-client-trade-dup (command, 592ms): exitCode=0, stdout=no-client-trade-dup-ok

- PASS ac5-assert-output (assert_output, 366ms): source=ac5-no-client-trade-dup, stream=stdout, contains=no-client-trade-dup-ok
- PASS ac3-flip-metamask-fee (assert_file, 300ms): path=app/components/UI/Perps/components/PerpsFlipPositionConfirmSheet/PerpsFlipPositionConfirmSheet.tsx
- PASS done (end, 307ms)

---
mm-harness: dist/ is OLDER than src/ — this run uses the stale compiled code.
  Next: npm run build (or: trash dist/ to run straight from src via tsx)
Recipe libraries: metamask=/Users/deeeed/dev/metamask/metamask-harness/library (1 flows)
{
  "schemaVersion": 1,
  "command": "run",
  "adapter": "mobile",
  "status": "pass",
  "exitCode": 0,
  "recovered": [],
  "mutations": [],
  "reportPath": "/Users/deeeed/dev/metamask/metamask-mobile-1/temp/tasks/feat/manual-000002-0709-124928/artifacts/recipe-run/report.md",
  "result": {
    "status": "pass",
    "summaryPath": "/Users/deeeed/dev/metamask/metamask-mobile-1/temp/tasks/feat/manual-000002-0709-124928/artifacts/recipe-run/summary.json",
    "tracePath": "/Users/deeeed/dev/metamask/metamask-mobile-1/temp/tasks/feat/manual-000002-0709-124928/artifacts/recipe-run/trace.json",
    "artifactManifestPath": "/Users/deeeed/dev/metamask/metamask-mobile-1/temp/tasks/feat/manual-000002-0709-124928/artifacts/recipe-run/artifact-manifest.json",
    "recipePath": "/Users/deeeed/dev/metamask/metamask-mobile-1/temp/tasks/feat/manual-000002-0709-124928/artifacts/recipe-run/recipe.json"
  }
}
```
</details>

## **Pre-merge author checklist**

- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I've included tests if applicable
- [x] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I've applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

#### Performance checks (if applicable)

- [x] I've tested on Android
- N/A for this analytics-only change (validated via recipe/unit on iOS
slot)
- [x] I've tested with a power user scenario
  - N/A — no UI/performance path change
- [x] I've instrumented key operations with Sentry traces for production
performance metrics
  - N/A — analytics wiring only; existing Sentry paths unchanged

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.

## Self-review fix note
Forwarded market-list `source_section` through market details into order
`trackingData` discovery fields; added unit coverage for the handoff.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Large analytics surface across trade, search, and navigation with
abandon/suppression logic; wrong events could skew product metrics but
does not change order execution or funds handling.
> 
> **Overview**
> Aligns MetaMask Mobile Perps with the **controller-owned analytics
contract**: trade lifecycle events (`PERPS_TRADE_TRANSACTION`, partial
fills) are no longer emitted from `usePerpsOrderExecution`—only
`trackingData` is forwarded so the controller owns submitted/terminal
events.
> 
> **Attribution & trackingData** — Adds `toPerpsEntryAttribution` / UTM
helpers and threads `entryPoint`, discovery (`source_section`),
`hlFeeRate`, and fee fields through order, close, cancel, flip, TP/SL,
and margin flows. UTM is merged into client `PERPS_SCREEN_VIEWED` via
`usePerpsEventTracking`.
> 
> **New client events & funnels** — Market list: debounced
`PERPS_SEARCH_QUERY`, result taps, abandonment, sort/filter applied, and
search screen views. Order screen: debounced
`PERPS_TRANSACTION_CONSIDERED`, pay-token `PERPS_TRADE_QUOTE_RECEIVED`,
place-order tap (all paths), and `usePerpsAbandonOrderTracking` for
`abandon_order` on real exits (not child routes or after commit). Close
flow gets the same abandon hook plus route `buttonClicked` /
`buttonLocation` on screen views. Adjust margin screen views by mode;
asset details adds `watchlisted` and forwards `source_section` into
orders.
> 
> **Supporting pieces** — `derivePerpsTradeAction` for considered vs
executed action parity; payment-token selector dismissed tracking;
navigation params extended for close entry CTAs and `source_section`.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
6c19369. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Nicholas Gambino <nicholas.gambino@consensys.net>
georgewrmarshall pushed a commit to MetaMask/metamask-mobile that referenced this pull request Jul 16, 2026
…Mobile (#33095)

## **Description**

Adopts the controller-owned perps analytics contract and completes the
assigned tickets.

Beyond the initial contract adoption (register the five controller
MetaMetrics events, entry/discovery/UTM/`hlFeeRate`/`metamaskFee`
trackingData on the order lifecycle, controller search events from the
market list, removal of duplicate client-side `PERPS_TRADE_TRANSACTION`
emissions so submitted/terminal come only from the controller), this now
also wires:
- UTM merged onto all client-emitted `PERPS_SCREEN_VIEWED` ()
- search query / result-tapped / abandoned with full prop sets, 500ms
debounce ()
- `sort_applied` / `filter_applied` incl. the watchlist filter
- `place_order` button for ALL users incl. the deposit-required path
- `abandon_order` on genuine exit — back/swipe/hardware-back and
tab-switch-away, suppressed on internal TP/SL / payment-selector nav and
after placement
- `screen_type=add_margin`/`remove_margin` on margin modal open
- `watchlisted` on asset_detail only
- `PERPS_TRANSACTION_CONSIDERED` + `PERPS_TRADE_QUOTE_RECEIVED` now
emitted

No SDK bump or patch required on mobile — Metro uses the correct ESM
build. The published 9.2.1 CommonJS build bug is core-side and fixed in
perps-controller 9.2.2 (MetaMask/core#9471; upstream
nktkas/hyperliquid#165).

### Deferred
- `order_execution_latency_ms` wiring lands with the 9.2.2 bump (new
`TrackingData.orderExecutionLatencyMs`).

## **Changelog**

CHANGELOG entry: null

## **Related issues**

Refs: MetaMask/core#9311

## **Manual testing steps**

```gherkin
Feature: perps controller analytics contract consumption

  Scenario: order and search analytics use controller contract
    Given the app is running with @metamask/perps-controller 9.2.1
    And MetaMetrics is enabled

    When the user opens Perps markets and searches for a symbol
    Then Perp Search Query / Result Tapped / Abandoned events can fire from the market list

    When the user places, closes, cancels, flips, or updates TP/SL
    Then trackingData includes entryPoint/discovery fields and fee fields where available
    And the client does not emit a second Perp Trade Transaction for the same controller event
```

N/A for visual UI walkthrough — analytics-only change; validated via
recipe state assertions + unit tests.

## **Screenshots/Recordings**

Analytics-only change; no screenshots or video. Proof is recipe state
assertions and unit tests in Validation Logs.

## **Validation Recipe**
<details><summary>recipe.json (17 steps — state asserts for AC1–AC5
analytics contract consumption)</summary>

```json
{
  "schema_version": 1,
  "title": "Consume perps controller analytics contract (MANUAL-000002)",
  "description": "Prove Mobile consumes perps-controller analytics contract: package version, MetaMetrics event registration, attribution wiring, search events, and no duplicate client trade-transaction emissions.",
  "validate": {
    "workflow": {
      "entry": "ac1-package-version",
      "nodes": {
        "ac1-package-version": {
          "action": "command",
          "intent": "AC1: confirm installed @metamask/perps-controller includes core#9311 contract",
          "proofTarget": "ac1",
          "cmd": "node -e \"const fs=require('fs'); const pkg=JSON.parse(fs.readFileSync('node_modules/@metamask/perps-controller/package.json','utf8')); const cl=fs.readFileSync('node_modules/@metamask/perps-controller/CHANGELOG.md','utf8'); if (!pkg.version.startsWith('9.')) { console.error('unexpected version', pkg.version); process.exit(1);} if (!cl.includes('#9311') && !cl.includes('PerpsAttributionContext')) { console.error('changelog missing contract markers'); process.exit(1);} console.log('ok', pkg.version);\"",
          "next": "ac1-assert-output"
        },
        "ac1-assert-output": {
          "action": "assert_output",
          "intent": "AC1: package version command printed ok",
          "proofTarget": "ac1",
          "source": "ac1-package-version",
          "stream": "stdout",
          "contains": "ok",
          "next": "ac2-search-query"
        },
        "ac2-search-query": {
          "action": "assert_file",
          "intent": "AC2: MetaMetrics registers Perp Search Query",
          "proofTarget": "ac2",
          "path": "app/core/Analytics/MetaMetrics.events.ts",
          "contains": "Perp Search Query",
          "next": "ac2-search-result"
        },
        "ac2-search-result": {
          "action": "assert_file",
          "intent": "AC2: MetaMetrics registers Perp Search Result Tapped",
          "proofTarget": "ac2",
          "path": "app/core/Analytics/MetaMetrics.events.ts",
          "contains": "Perp Search Result Tapped",
          "next": "ac2-search-abandoned"
        },
        "ac2-search-abandoned": {
          "action": "assert_file",
          "intent": "AC2: MetaMetrics registers Perp Search Abandoned",
          "proofTarget": "ac2",
          "path": "app/core/Analytics/MetaMetrics.events.ts",
          "contains": "Perp Search Abandoned",
          "next": "ac2-considered"
        },
        "ac2-considered": {
          "action": "assert_file",
          "intent": "AC2: MetaMetrics registers Perp Transaction Considered",
          "proofTarget": "ac2",
          "path": "app/core/Analytics/MetaMetrics.events.ts",
          "contains": "Perp Transaction Considered",
          "next": "ac2-quote"
        },
        "ac2-quote": {
          "action": "assert_file",
          "intent": "AC2: MetaMetrics registers Perp Trade Quote Received",
          "proofTarget": "ac2",
          "path": "app/core/Analytics/MetaMetrics.events.ts",
          "contains": "Perp Trade Quote Received",
          "next": "ac3-attribution-helper"
        },
        "ac3-attribution-helper": {
          "action": "assert_file",
          "intent": "AC3: attribution helper maps entryPoint",
          "proofTarget": "ac3",
          "path": "app/components/UI/Perps/utils/perpsAnalyticsAttribution.ts",
          "contains": "entryPoint",
          "next": "ac3-deeplink-utm"
        },
        "ac3-deeplink-utm": {
          "action": "assert_file",
          "intent": "AC3: deeplink handler applies UTM attribution context",
          "proofTarget": "ac3",
          "path": "app/core/DeeplinkManager/handlers/legacy/handlePerpsUrl.ts",
          "contains": "setPerpsUtmAttribution",
          "next": "ac3-order-entrypoint"
        },
        "ac3-order-entrypoint": {
          "action": "assert_file",
          "intent": "AC3: order ticket wires entry attribution helper into trackingData",
          "proofTarget": "ac3",
          "path": "app/components/UI/Perps/Views/PerpsOrderView/PerpsOrderView.tsx",
          "contains": "toPerpsEntryAttribution",
          "next": "ac3-order-hl-fee"
        },
        "ac3-order-hl-fee": {
          "action": "assert_file",
          "intent": "AC3: order ticket trackingData includes hlFeeRate",
          "proofTarget": "ac3",
          "path": "app/components/UI/Perps/Views/PerpsOrderView/PerpsOrderView.tsx",
          "contains": "hlFeeRate",
          "next": "ac3-close-entrypoint"
        },
        "ac3-close-entrypoint": {
          "action": "assert_file",
          "intent": "AC3: close position wires entry attribution helper into trackingData",
          "proofTarget": "ac3",
          "path": "app/components/UI/Perps/Views/PerpsClosePositionView/PerpsClosePositionView.tsx",
          "contains": "toPerpsEntryAttribution",
          "next": "ac4-search-event"
        },
        "ac4-search-event": {
          "action": "assert_file",
          "intent": "AC4: market list emits controller Search Query event",
          "proofTarget": "ac4",
          "path": "app/components/UI/Perps/Views/PerpsMarketListView/PerpsMarketListView.tsx",
          "contains": "PERPS_SEARCH_QUERY",
          "next": "ac5-no-client-trade-dup"
        },
        "ac5-no-client-trade-dup": {
          "action": "command",
          "intent": "AC5: usePerpsOrderExecution must not emit client-side PERPS_TRADE_TRANSACTION",
          "proofTarget": "ac5",
          "cmd": "node -e \"const fs=require('fs'); const src=fs.readFileSync('app/components/UI/Perps/hooks/usePerpsOrderExecution.ts','utf8'); if (/track\\\\(\\\\s*MetaMetricsEvents\\\\.PERPS_TRADE_TRANSACTION/.test(src)) { console.error('duplicate client PERPS_TRADE_TRANSACTION still present'); process.exit(1);} console.log('no-client-trade-dup-ok');\"",
          "next": "ac5-assert-output"
        },
        "ac5-assert-output": {
          "action": "assert_output",
          "intent": "AC5: confirm no client trade-transaction duplicate emission",
          "proofTarget": "ac5",
          "source": "ac5-no-client-trade-dup",
          "stream": "stdout",
          "contains": "no-client-trade-dup-ok",
          "next": "ac3-flip-metamask-fee"
        },
        "ac3-flip-metamask-fee": {
          "action": "assert_file",
          "intent": "AC3: flip sheet passes metamaskFee for controller flip analytics",
          "proofTarget": "ac3",
          "path": "app/components/UI/Perps/components/PerpsFlipPositionConfirmSheet/PerpsFlipPositionConfirmSheet.tsx",
          "contains": "metamaskFee",
          "next": "done"
        },
        "done": {
          "action": "end",
          "status": "pass",
          "intent": "All analytics contract consumption assertions passed"
        }
      }
    }
  }
}
```
</details>

## **Validation Logs**
Command:
```bash

```

<details><summary>Full output (17/17 passed)</summary>

```
# MetaMask Recipe Run

Status: pass
Duration: 7.5s
Nodes: 17/17 passed

## Steps
- PASS ac1-package-version (command, 201ms): exitCode=0, stdout=ok 9.2.1

- PASS ac1-assert-output (assert_output, 93ms): source=ac1-package-version, stream=stdout, contains=ok
- PASS ac2-search-query (assert_file, 100ms): path=app/core/Analytics/MetaMetrics.events.ts
- PASS ac2-search-result (assert_file, 83ms): path=app/core/Analytics/MetaMetrics.events.ts
- PASS ac2-search-abandoned (assert_file, 69ms): path=app/core/Analytics/MetaMetrics.events.ts
- PASS ac2-considered (assert_file, 93ms): path=app/core/Analytics/MetaMetrics.events.ts
- PASS ac2-quote (assert_file, 116ms): path=app/core/Analytics/MetaMetrics.events.ts
- PASS ac3-attribution-helper (assert_file, 105ms): path=app/components/UI/Perps/utils/perpsAnalyticsAttribution.ts
- PASS ac3-deeplink-utm (assert_file, 143ms): path=app/core/DeeplinkManager/handlers/legacy/handlePerpsUrl.ts
- PASS ac3-order-entrypoint (assert_file, 310ms): path=app/components/UI/Perps/Views/PerpsOrderView/PerpsOrderView.tsx
- PASS ac3-order-hl-fee (assert_file, 219ms): path=app/components/UI/Perps/Views/PerpsOrderView/PerpsOrderView.tsx
- PASS ac3-close-entrypoint (assert_file, 170ms): path=app/components/UI/Perps/Views/PerpsClosePositionView/PerpsClosePositionView.tsx
- PASS ac4-search-event (assert_file, 286ms): path=app/components/UI/Perps/Views/PerpsMarketListView/PerpsMarketListView.tsx
- PASS ac5-no-client-trade-dup (command, 592ms): exitCode=0, stdout=no-client-trade-dup-ok

- PASS ac5-assert-output (assert_output, 366ms): source=ac5-no-client-trade-dup, stream=stdout, contains=no-client-trade-dup-ok
- PASS ac3-flip-metamask-fee (assert_file, 300ms): path=app/components/UI/Perps/components/PerpsFlipPositionConfirmSheet/PerpsFlipPositionConfirmSheet.tsx
- PASS done (end, 307ms)

---
mm-harness: dist/ is OLDER than src/ — this run uses the stale compiled code.
  Next: npm run build (or: trash dist/ to run straight from src via tsx)
Recipe libraries: metamask=/Users/deeeed/dev/metamask/metamask-harness/library (1 flows)
{
  "schemaVersion": 1,
  "command": "run",
  "adapter": "mobile",
  "status": "pass",
  "exitCode": 0,
  "recovered": [],
  "mutations": [],
  "reportPath": "/Users/deeeed/dev/metamask/metamask-mobile-1/temp/tasks/feat/manual-000002-0709-124928/artifacts/recipe-run/report.md",
  "result": {
    "status": "pass",
    "summaryPath": "/Users/deeeed/dev/metamask/metamask-mobile-1/temp/tasks/feat/manual-000002-0709-124928/artifacts/recipe-run/summary.json",
    "tracePath": "/Users/deeeed/dev/metamask/metamask-mobile-1/temp/tasks/feat/manual-000002-0709-124928/artifacts/recipe-run/trace.json",
    "artifactManifestPath": "/Users/deeeed/dev/metamask/metamask-mobile-1/temp/tasks/feat/manual-000002-0709-124928/artifacts/recipe-run/artifact-manifest.json",
    "recipePath": "/Users/deeeed/dev/metamask/metamask-mobile-1/temp/tasks/feat/manual-000002-0709-124928/artifacts/recipe-run/recipe.json"
  }
}
```
</details>

## **Pre-merge author checklist**

- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile
Coding
Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I've included tests if applicable
- [x] I've documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I've applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

#### Performance checks (if applicable)

- [x] I've tested on Android
- N/A for this analytics-only change (validated via recipe/unit on iOS
slot)
- [x] I've tested with a power user scenario
  - N/A — no UI/performance path change
- [x] I've instrumented key operations with Sentry traces for production
performance metrics
  - N/A — analytics wiring only; existing Sentry paths unchanged

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.

## Self-review fix note
Forwarded market-list `source_section` through market details into order
`trackingData` discovery fields; added unit coverage for the handoff.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Large analytics surface across trade, search, and navigation with
abandon/suppression logic; wrong events could skew product metrics but
does not change order execution or funds handling.
> 
> **Overview**
> Aligns MetaMask Mobile Perps with the **controller-owned analytics
contract**: trade lifecycle events (`PERPS_TRADE_TRANSACTION`, partial
fills) are no longer emitted from `usePerpsOrderExecution`—only
`trackingData` is forwarded so the controller owns submitted/terminal
events.
> 
> **Attribution & trackingData** — Adds `toPerpsEntryAttribution` / UTM
helpers and threads `entryPoint`, discovery (`source_section`),
`hlFeeRate`, and fee fields through order, close, cancel, flip, TP/SL,
and margin flows. UTM is merged into client `PERPS_SCREEN_VIEWED` via
`usePerpsEventTracking`.
> 
> **New client events & funnels** — Market list: debounced
`PERPS_SEARCH_QUERY`, result taps, abandonment, sort/filter applied, and
search screen views. Order screen: debounced
`PERPS_TRANSACTION_CONSIDERED`, pay-token `PERPS_TRADE_QUOTE_RECEIVED`,
place-order tap (all paths), and `usePerpsAbandonOrderTracking` for
`abandon_order` on real exits (not child routes or after commit). Close
flow gets the same abandon hook plus route `buttonClicked` /
`buttonLocation` on screen views. Adjust margin screen views by mode;
asset details adds `watchlisted` and forwards `source_section` into
orders.
> 
> **Supporting pieces** — `derivePerpsTradeAction` for considered vs
executed action parity; payment-token selector dismissed tracking;
navigation params extended for close entry CTAs and `source_section`.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
6c19369. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Nicholas Gambino <nicholas.gambino@consensys.net>
pull Bot pushed a commit to firas9941/metamask-extension that referenced this pull request Jul 31, 2026
…Extension (MetaMask#44324)

## **Description**

Adopts the controller-owned perps analytics contract
(`@metamask/perps-controller`) and completes the assigned analytics
tickets.

Beyond the initial contract adoption (re-export controller
event/property/value defs, attribution APIs incl.
`mergeAttributionContext`, `trackingData`/`hlFeeRate` on
place/close/cancel/flip, removal of duplicate client-side transaction
emissions so the controller TradingService owns submitted+terminal
analytics, LavaMoat policy updates), this now also wires:
- UTM merged onto all client-emitted `PERPS_SCREEN_VIEWED` events, with
`source=deeplink` for deep links
- `watchlisted` on the asset_detail screen only
- `screen_type=error` + `error_type` + `screen_name` on every
displayed-error path
- `button_clicked` (close/reduce_exposure) + `button_location` on
position_close
- `sort_applied` / `filter_applied` with
`sort_field`/`sort_direction`/`filter_category`
- `PERPS_TRANSACTION_CONSIDERED` (debounced, gated to genuine user size
edits) + trading-screen defaults
- `screen_type=add_margin`/`remove_margin` on margin modal open
- no-swallowed-exception cleanup (captureException instead of empty
catch / log-and-continue)

### Perps controller dependency
The latest `main` sync supplies `@metamask/perps-controller@10.0.0`,
which includes the upstream Hyperliquid packaging fix. The temporary
9.2.1 yarn patch was removed during the sync.

### Deferred / N/A
- N/A in the extension: `PERPS_TRADE_QUOTE_RECEIVED` (no async
pay-with-token quote — fee calculation is synchronous),
payment-token-selector-dismissed (no such selector).

## **Changelog**

CHANGELOG entry: null

## **Related issues**

Refs: MetaMask/core#9311, MetaMask/core#9471

## **Manual testing steps**

1. Build/start the Extension with the bumped
`@metamask/perps-controller`.
2. Open Perps (home → market list → market detail → order entry) and
confirm screens still load and trade/close/cancel/TP-SL flows still
work.
3. With analytics enabled, confirm Segment receives controller-emitted
Perp Trade / Close / Cancel / Risk events (including `status: submitted`
then terminal) and that the UI no longer double-fires the same
transaction event names for those flows.
4. Open a Perps deep link / URL with `utm_*` or `source=` query params
and confirm subsequent order lifecycle events include attribution fields
when present.

## **Screenshots/Recordings**

Live Recipe Protocol v1 proof passed **177/177 nodes** in **143
seconds** at exact PR head `a2eb9dcaeae90f61edf22c4b8b9f9b353bf3fe8a`
with `@metamask/perps-controller@10.0.0`.

- Runtime: one production LavaMoat build; the real MetaMetrics client
sent Segment batches to the harness collector.
- Published proof stack: `@deeeed/metamask-harness@0.26.5` +
`@farmslot/recipe-harness@0.10.4`.
- Focused regression: 21 changed Perps/analytics suites, 762 tests, no
console-baseline violations; locale validation clean.
- Coverage: attributed screen views; search result and abandonment;
filter/sort/watchlist; trade and close; limit-order cancel; TP/SL;
add/remove margin screens; position flip; considered/abandoned order;
unknown-market error screen.
- Assertions: run-bracketed live payloads with event
order/count/property checks, including submitted + terminal controller
events and duplicate-emission guards.
- Run integrity: no recovery and no product mutation; the artifact
package retains the executed root, 11 digest-pinned child recipes,
summary, trace, diagnostics, and manifest.
- Diagnostics: eight non-blocking warning/error entries were retained in
`diagnostics.json`; none invalidated UI postconditions or analytics
assertions.

Final artifact paths in the PR slot:

-
`temp/fixes/44324/perps-analytics-exact-head-a2eb9dcaea-0265/report.md`
-
`temp/fixes/44324/perps-analytics-exact-head-a2eb9dcaea-0265/summary.json`
-
`temp/fixes/44324/perps-analytics-exact-head-a2eb9dcaea-0265/trace.json`
-
`temp/fixes/44324/perps-analytics-exact-head-a2eb9dcaea-0265/provenance.json`

## **Pre-merge author checklist**

- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I’ve included tests if applicable
- [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.

## **Validation Recipe**

<details>
<summary>Composable live Recipe Protocol v1 proof (177/177
nodes)</summary>

The root composes independent flows so a failed acceptance criterion is
isolated and rerunnable. The artifact package retains the exact child
documents listed by digest below.

```json
{
  "$schema": "https://farmslot.io/schemas/recipe-v1.schema.json",
  "title": "MetaMask Extension Perps analytics live proof",
  "description": "Compose independent live-app flows that prove Perps analytics from the built Extension and its real MetaMetrics client.",
  "workflow": {
    "entry": "ready",
    "nodes": {
      "ready": {
        "action": "call",
        "ref": "perps-analytics.runtime-ready",
        "intent": "Prepare the live Extension once",
        "next": "attribution"
      },
      "attribution": {
        "action": "call",
        "ref": "perps-analytics.attributed-screen",
        "intent": "Prove attributed asset-details analytics",
        "next": "search-result"
      },
      "search-result": {
        "action": "call",
        "ref": "perps-analytics.search-result",
        "intent": "Prove the successful search funnel",
        "next": "search-abandon"
      },
      "search-abandon": {
        "action": "call",
        "ref": "perps-analytics.search-abandon",
        "intent": "Prove the abandoned search funnel",
        "next": "discovery-controls"
      },
      "discovery-controls": {
        "action": "call",
        "ref": "perps-analytics.discovery-controls",
        "intent": "Prove market filter, sort, and watchlist analytics",
        "next": "trade-close"
      },
      "trade-close": {
        "action": "call",
        "ref": "perps-analytics.trade-close",
        "intent": "Prove the committed trade and close lifecycles",
        "next": "cancel-order"
      },
      "cancel-order": {
        "action": "call",
        "ref": "perps-analytics.cancel-order",
        "intent": "Prove the committed order-cancel lifecycle",
        "next": "risk-management"
      },
      "risk-management": {
        "action": "call",
        "ref": "perps-analytics.risk-management",
        "intent": "Prove the committed TP/SL lifecycle",
        "next": "margin-flip"
      },
      "margin-flip": {
        "action": "call",
        "ref": "perps-analytics.margin-flip",
        "intent": "Prove margin screens and the committed flip lifecycle",
        "next": "considered-abandon"
      },
      "considered-abandon": {
        "action": "call",
        "ref": "perps-analytics.considered-abandon",
        "intent": "Prove considered and abandoned order analytics",
        "next": "error-screen"
      },
      "error-screen": {
        "action": "call",
        "ref": "perps-analytics.error-screen",
        "intent": "Prove unknown-market error analytics",
        "next": "done"
      },
      "done": {
        "action": "end",
        "status": "pass"
      }
    }
  }
}
```

<details>
<summary>Resolved child recipe digests</summary>

- `perps-analytics.attributed-screen` —
`sha256:366ab0fa91b93d528c0b1e3567f050f37504f955d90d900757b2519d505e37ee`
- `perps-analytics.cancel-order` —
`sha256:08e49f8b3753b79b8058f32a50f04ed0d747576f1292bbeea92168ff9b81afa0`
- `perps-analytics.considered-abandon` —
`sha256:f4cad429c6209a1c8842daba3a081169d30d451bc025ba05f7c9dfbf874e4307`
- `perps-analytics.discovery-controls` —
`sha256:63259a262ffff22a301231e748195d67c3a40168b24ae754ed32c87368bdbd91`
- `perps-analytics.error-screen` —
`sha256:fe098d8b928d1228d0b36cf5cef57ea0c60bae42e6e66710301ef328b1ff902a`
- `perps-analytics.margin-flip` —
`sha256:ffe7eb7fe44a0c4e41009876a37856bec72cab162b3f410bfdf9bfa7277d5fa5`
- `perps-analytics.risk-management` —
`sha256:a198670ce27037ab93ac3f3fcd2c806cc8e554ad67f3ebda8c1d960afde13792`
- `perps-analytics.runtime-ready` —
`sha256:9f562eae962e306c28ab39ef588a193bdc2424f73168ee7d6300b9aa960b89a8`
- `perps-analytics.search-abandon` —
`sha256:94bfaf573cd1f6bba01fa0627e21858b6ff14374664b390f97e80a0cc6f7fdd0`
- `perps-analytics.search-result` —
`sha256:1de24f3a300f91faf9ecf7a09536cc0637ee5285866cefa59c4208d810e98b29`
- `perps-analytics.trade-close` —
`sha256:46935ae8b573b4ccd2e83ca462eeb657eb01879d7c01ff4cb285490fffba5598`

</details>

Run command:

```sh
npx --yes --package=@deeeed/metamask-harness@0.26.5 mm-harness run temp/fixes/44324/perps-analytics-live.recipe.json --adapter extension --target "$PWD" --cdp-port 6662 --library live=temp/fixes/44324/analytics-live-library --artifacts-dir temp/fixes/44324/perps-analytics-exact-head-a2eb9dcaea-0265 --json-stream
```

</details>

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Wide Perps UI and background analytics changes alter which events fire
and which properties they carry; regressions would affect metrics rather
than funds, but incorrect deduplication or status values could skew
funnel reporting.
> 
> **Overview**
> Aligns Extension Perps analytics with
**`@metamask/perps-controller`**: `shared/constants/perps-events` now
re-exports the controller contract (with Extension aliases), and
background **`trackPerpsEvent`** merges stored UTM via
**`mergeAttributionContext`** / **`perpsSetAttributionContext`**.
> 
> **Attribution end-to-end:** Perps deeplinks set **`source=deeplink`**
and forward **`utm_*`** (including unsigned params on signed links via
**`handlerSearchParams: 'original'`**). **`PerpsAttributionProvider`**
on the wallet Perps tab and **`usePerpsAttribution`** replace
**`buildPerpsVipTrackingData`**, attaching entry/discovery,
**`hlFeeRate`**, and trade action on background **`trackingData`**.
> 
> **Event ownership:** UI stops double-emitting trade/close/cancel/risk
success paths the controller already reports; transport/`{ success:
false }` gaps keep **`PerpsError`**, error **screen views**, and
targeted client events (e.g. margin **`{ success: false }`**, batch
close **position count** with **EXECUTED** status). Adds abandon-order
tracking, geo-block screen views, close-modal CTA props, and
**`input_method`** on size controls.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
a2eb9dc. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
pull Bot pushed a commit to Reality2byte/core that referenced this pull request Aug 3, 2026
…sk#9320)

## Summary

- Removes internal Jira ticket IDs (`TAT-XXXX`) from perps-controller
comments, JSDoc, and test names added in MetaMask#9311
- Keeps the descriptive comment text unchanged; no runtime or API
behavior changes

Follow-up to MetaMask#9311 — the cleanup commit that was left off the merged
branch.

## Test plan

- [x] `yarn eslint` on the 8 changed files — clean
- [x] Comment-only diff; no logic changes

Made with [Cursor](https://cursor.com)

Co-authored-by: Cursor <cursoragent@cursor.com>
pull Bot pushed a commit to Reality2byte/metamask-extension that referenced this pull request Aug 4, 2026
## **Description**

Re-lands the analytics-contract consumption from MetaMask#44324 after its revert
in MetaMask#45087, without the deeplink-attribution route changes.

This keeps the controller-owned analytics contract, event ownership and
deduplication, UI funnel/error/abandonment tracking, and attribution
consumption. It does not change any deeplink route handler, add
`handlerSearchParams: original`, or forward original/unsigned
parameters. The signed-only attribution follow-up is proposed separately
in MetaMask#45123 for focused Security review.

## **Changelog**

CHANGELOG entry: null

## **Related issues**

Refs: MetaMask#44324, MetaMask#45087, MetaMask/core#9311, MetaMask/core#9471

Follow-up: [TAT-3646 — restore signed Perps deeplink
attribution](https://consensyssoftware.atlassian.net/browse/TAT-3646)

## **Manual testing steps**

1. Build and start the Extension with Perps enabled.
2. Open Perps and navigate through the market list, market detail, and
order entry screens.
3. With analytics consent enabled, complete trade, close, cancel, TP/SL,
and margin flows.
4. Confirm each submitted and terminal transaction event is emitted
once, and confirm search, abandonment, filter, sort, watchlist, and
displayed-error events include their expected properties.

<!--
## **Screenshots/Recordings**

Fresh exact-head live analytics evidence will be attached after the
production build and composable recipe run complete. This evidence
validates the analytics contract only; it is not deeplink-security
evidence.
-->

## **Live recipe evidence**

The reusable recipe is merged at
[MetaMask/experimental-metamask-recipe-perps@c7cd115](https://github.com/MetaMask/experimental-metamask-recipe-perps/blob/c7cd11526ba85c2601463ede7f8cdf9697e8bde0/recipes/perps/analytics-lifecycle.extension.recipe.json).
The scrubbed, checksummed evidence is published at
[abretonc7s/mm-extension-farm-artifacts@461d55a](https://github.com/abretonc7s/mm-extension-farm-artifacts/tree/461d55a9e1406dfd57695bf5b6ff84d25ff991bb/fixes/45119/analytics-lifecycle).

<details>
<summary>201/201-node live HyperLiquid testnet proof</summary>

```sh
RECIPE_LIBRARY_PATH="perps=/path/to/experimental-metamask-recipe-perps" \
  mm-harness run perps.analytics-lifecycle \
  --adapter extension \

  market=BTC market_query=btc
```

- Product head: `d5e5a0184f01dd5fb099313060f99399327d9208`
- Recipe source: merged commit
`c7cd11526ba85c2601463ede7f8cdf9697e8bde0`
- Runner: `@metamask/recipe-runner` source commit
`fea899ce2716fd54cced5b58652025930d42fbf9`
- Runtime: `@farmslot/recipe-harness` 0.11.0
- Result: 201 passed, 0 failed, 136,200 ms; no recovery or runtime
mutation
- Safety: all ten mutating setup/cleanup executions reported `requested:
testnet`, `isTestnet: true`, and finished with zero matching orders and
positions
- Coverage: attributed screen views; search query, result, and
abandonment; discovery controls; trade/open and close; single and batch
cancellation; TP/SL; margin dialogs; position reversal; order
consideration and abandonment; displayed error screens
- Assertions use exact event totals plus required-property matches, so
duplicate emissions fail rather than passing on presence alone
- Review:
[report](https://github.com/abretonc7s/mm-extension-farm-artifacts/blob/461d55a9e1406dfd57695bf5b6ff84d25ff991bb/fixes/45119/analytics-lifecycle/report.md),
[summary](https://github.com/abretonc7s/mm-extension-farm-artifacts/blob/461d55a9e1406dfd57695bf5b6ff84d25ff991bb/fixes/45119/analytics-lifecycle/summary.json),
[trace](https://github.com/abretonc7s/mm-extension-farm-artifacts/blob/461d55a9e1406dfd57695bf5b6ff84d25ff991bb/fixes/45119/analytics-lifecycle/trace.json),
and [SHA-256
checksums](https://github.com/abretonc7s/mm-extension-farm-artifacts/blob/461d55a9e1406dfd57695bf5b6ff84d25ff991bb/fixes/45119/analytics-lifecycle/SHA256SUMS)

The trace proves analytics consumption after routing inside the running
Extension. It deliberately does not claim external signed-deeplink
parsing, canonicalization, or security behavior.

</details>

## **Validation**

- 18 affected Perps suites / 730 tests passed with no console-baseline
violations.
- `yarn lint:changed:fix` passed with zero errors.
- `yarn lint:tsc` passed.
- Final diff audit found no files under `shared/lib/deep-links`, no
`handlerSearchParams: original`, no `withDeeplinkAttribution`, and no
original/unsigned handler parameter access.
- The composed live recipe passed 201/201 nodes against this exact
product head.

## **Pre-merge author checklist**

- [x] I have followed MetaMask Contributor Docs and MetaMask Extension
Coding Standards.
- [x] I have completed the PR template to the best of my ability.
- [x] I have included tests where applicable.
- [x] I have documented code using JSDoc where applicable.
- [x] I have applied the right labels. Not required for external
contributors.

## **Pre-merge reviewer checklist**

- [ ] I have manually tested the PR.
- [ ] I confirm that this PR addresses the applicable acceptance
criteria and includes the necessary testing evidence.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Touches MetaMetrics event shapes and ownership across many Perps
flows; mis-deduping or wrong status/attribution would skew analytics but
does not change trading or auth logic.
> 
> **Overview**
> Re-lands Perps analytics alignment with `@metamask/perps-controller`:
**`shared/constants/perps-events`** now imports the controller contract
(with Extension aliases), new MetaMetrics funnel events are registered,
and the unused **`tutorial`** locale key is removed.
> 
> **Background:** `perpsSetAttributionContext` is exposed and
`mergeAttributionContext` is wired through `createPerpsInfrastructure`
so controller-emitted transaction events pick up stored UTM fields
before MetaMetrics. Tests consolidate on
**`test/mocks/metamask-perps-controller.js`**.
> 
> **UI:** `PerpsAttributionProvider` / **`usePerpsAttribution`** replace
`buildPerpsVipTrackingData` and attach **`trackingData`** (fees, VIP,
`hlFeeRate`, flow attribution) on cancel, close, flip, and TP/SL.
Duplicate client **transaction** analytics are removed where the
controller owns submitted/terminal events; gaps remain for transport
throws, margin `{ success: false }`, batch close counts,
**`trackPerpsErrorScreenViewed`**, geo-block screen views,
**`usePerpsAbandonOrderTracking`**, and order-size **`input_method`**
plumbing. **`derivePerpsTradeAction`** keeps considered vs executed
`action` consistent (including omitting reduce-only opposite-side
orders).
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
b5e316c. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
n3ps pushed a commit to MetaMask/metamask-extension that referenced this pull request Aug 5, 2026
## **Description**

Re-lands the analytics-contract consumption from #44324 after its revert
in #45087, without the deeplink-attribution route changes.

This keeps the controller-owned analytics contract, event ownership and
deduplication, UI funnel/error/abandonment tracking, and attribution
consumption. It does not change any deeplink route handler, add
`handlerSearchParams: original`, or forward original/unsigned
parameters. The signed-only attribution follow-up is proposed separately
in #45123 for focused Security review.

## **Changelog**

CHANGELOG entry: null

## **Related issues**

Refs: #44324, #45087, MetaMask/core#9311, MetaMask/core#9471

Follow-up: [TAT-3646 — restore signed Perps deeplink
attribution](https://consensyssoftware.atlassian.net/browse/TAT-3646)

## **Manual testing steps**

1. Build and start the Extension with Perps enabled.
2. Open Perps and navigate through the market list, market detail, and
order entry screens.
3. With analytics consent enabled, complete trade, close, cancel, TP/SL,
and margin flows.
4. Confirm each submitted and terminal transaction event is emitted
once, and confirm search, abandonment, filter, sort, watchlist, and
displayed-error events include their expected properties.

<!--
## **Screenshots/Recordings**

Fresh exact-head live analytics evidence will be attached after the
production build and composable recipe run complete. This evidence
validates the analytics contract only; it is not deeplink-security
evidence.
-->

## **Live recipe evidence**

The reusable recipe is merged at
[MetaMask/experimental-metamask-recipe-perps@c7cd115](https://github.com/MetaMask/experimental-metamask-recipe-perps/blob/c7cd11526ba85c2601463ede7f8cdf9697e8bde0/recipes/perps/analytics-lifecycle.extension.recipe.json).
The scrubbed, checksummed evidence is published at
[abretonc7s/mm-extension-farm-artifacts@461d55a](https://github.com/abretonc7s/mm-extension-farm-artifacts/tree/461d55a9e1406dfd57695bf5b6ff84d25ff991bb/fixes/45119/analytics-lifecycle).

<details>
<summary>201/201-node live HyperLiquid testnet proof</summary>

```sh
RECIPE_LIBRARY_PATH="perps=/path/to/experimental-metamask-recipe-perps" \
  mm-harness run perps.analytics-lifecycle \
  --adapter extension \

  market=BTC market_query=btc
```

- Product head: `d5e5a0184f01dd5fb099313060f99399327d9208`
- Recipe source: merged commit
`c7cd11526ba85c2601463ede7f8cdf9697e8bde0`
- Runner: `@metamask/recipe-runner` source commit
`fea899ce2716fd54cced5b58652025930d42fbf9`
- Runtime: `@farmslot/recipe-harness` 0.11.0
- Result: 201 passed, 0 failed, 136,200 ms; no recovery or runtime
mutation
- Safety: all ten mutating setup/cleanup executions reported `requested:
testnet`, `isTestnet: true`, and finished with zero matching orders and
positions
- Coverage: attributed screen views; search query, result, and
abandonment; discovery controls; trade/open and close; single and batch
cancellation; TP/SL; margin dialogs; position reversal; order
consideration and abandonment; displayed error screens
- Assertions use exact event totals plus required-property matches, so
duplicate emissions fail rather than passing on presence alone
- Review:
[report](https://github.com/abretonc7s/mm-extension-farm-artifacts/blob/461d55a9e1406dfd57695bf5b6ff84d25ff991bb/fixes/45119/analytics-lifecycle/report.md),
[summary](https://github.com/abretonc7s/mm-extension-farm-artifacts/blob/461d55a9e1406dfd57695bf5b6ff84d25ff991bb/fixes/45119/analytics-lifecycle/summary.json),
[trace](https://github.com/abretonc7s/mm-extension-farm-artifacts/blob/461d55a9e1406dfd57695bf5b6ff84d25ff991bb/fixes/45119/analytics-lifecycle/trace.json),
and [SHA-256
checksums](https://github.com/abretonc7s/mm-extension-farm-artifacts/blob/461d55a9e1406dfd57695bf5b6ff84d25ff991bb/fixes/45119/analytics-lifecycle/SHA256SUMS)

The trace proves analytics consumption after routing inside the running
Extension. It deliberately does not claim external signed-deeplink
parsing, canonicalization, or security behavior.

</details>

## **Validation**

- 18 affected Perps suites / 730 tests passed with no console-baseline
violations.
- `yarn lint:changed:fix` passed with zero errors.
- `yarn lint:tsc` passed.
- Final diff audit found no files under `shared/lib/deep-links`, no
`handlerSearchParams: original`, no `withDeeplinkAttribution`, and no
original/unsigned handler parameter access.
- The composed live recipe passed 201/201 nodes against this exact
product head.

## **Pre-merge author checklist**

- [x] I have followed MetaMask Contributor Docs and MetaMask Extension
Coding Standards.
- [x] I have completed the PR template to the best of my ability.
- [x] I have included tests where applicable.
- [x] I have documented code using JSDoc where applicable.
- [x] I have applied the right labels. Not required for external
contributors.

## **Pre-merge reviewer checklist**

- [ ] I have manually tested the PR.
- [ ] I confirm that this PR addresses the applicable acceptance
criteria and includes the necessary testing evidence.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Touches MetaMetrics event shapes and ownership across many Perps
flows; mis-deduping or wrong status/attribution would skew analytics but
does not change trading or auth logic.
> 
> **Overview**
> Re-lands Perps analytics alignment with `@metamask/perps-controller`:
**`shared/constants/perps-events`** now imports the controller contract
(with Extension aliases), new MetaMetrics funnel events are registered,
and the unused **`tutorial`** locale key is removed.
> 
> **Background:** `perpsSetAttributionContext` is exposed and
`mergeAttributionContext` is wired through `createPerpsInfrastructure`
so controller-emitted transaction events pick up stored UTM fields
before MetaMetrics. Tests consolidate on
**`test/mocks/metamask-perps-controller.js`**.
> 
> **UI:** `PerpsAttributionProvider` / **`usePerpsAttribution`** replace
`buildPerpsVipTrackingData` and attach **`trackingData`** (fees, VIP,
`hlFeeRate`, flow attribution) on cancel, close, flip, and TP/SL.
Duplicate client **transaction** analytics are removed where the
controller owns submitted/terminal events; gaps remain for transport
throws, margin `{ success: false }`, batch close counts,
**`trackPerpsErrorScreenViewed`**, geo-block screen views,
**`usePerpsAbandonOrderTracking`**, and order-size **`input_method`**
plumbing. **`derivePerpsTradeAction`** keeps considered vs executed
`action` consistent (including omitting reduce-only opposite-side
orders).
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
b5e316c. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Bigshmow pushed a commit to MetaMask/metamask-extension that referenced this pull request Aug 5, 2026
…Extension (#44324)

## **Description**

Adopts the controller-owned perps analytics contract
(`@metamask/perps-controller`) and completes the assigned analytics
tickets.

Beyond the initial contract adoption (re-export controller
event/property/value defs, attribution APIs incl.
`mergeAttributionContext`, `trackingData`/`hlFeeRate` on
place/close/cancel/flip, removal of duplicate client-side transaction
emissions so the controller TradingService owns submitted+terminal
analytics, LavaMoat policy updates), this now also wires:
- UTM merged onto all client-emitted `PERPS_SCREEN_VIEWED` events, with
`source=deeplink` for deep links
- `watchlisted` on the asset_detail screen only
- `screen_type=error` + `error_type` + `screen_name` on every
displayed-error path
- `button_clicked` (close/reduce_exposure) + `button_location` on
position_close
- `sort_applied` / `filter_applied` with
`sort_field`/`sort_direction`/`filter_category`
- `PERPS_TRANSACTION_CONSIDERED` (debounced, gated to genuine user size
edits) + trading-screen defaults
- `screen_type=add_margin`/`remove_margin` on margin modal open
- no-swallowed-exception cleanup (captureException instead of empty
catch / log-and-continue)

### Perps controller dependency
The latest `main` sync supplies `@metamask/perps-controller@10.0.0`,
which includes the upstream Hyperliquid packaging fix. The temporary
9.2.1 yarn patch was removed during the sync.

### Deferred / N/A
- N/A in the extension: `PERPS_TRADE_QUOTE_RECEIVED` (no async
pay-with-token quote — fee calculation is synchronous),
payment-token-selector-dismissed (no such selector).

## **Changelog**

CHANGELOG entry: null

## **Related issues**

Refs: MetaMask/core#9311, MetaMask/core#9471

## **Manual testing steps**

1. Build/start the Extension with the bumped
`@metamask/perps-controller`.
2. Open Perps (home → market list → market detail → order entry) and
confirm screens still load and trade/close/cancel/TP-SL flows still
work.
3. With analytics enabled, confirm Segment receives controller-emitted
Perp Trade / Close / Cancel / Risk events (including `status: submitted`
then terminal) and that the UI no longer double-fires the same
transaction event names for those flows.
4. Open a Perps deep link / URL with `utm_*` or `source=` query params
and confirm subsequent order lifecycle events include attribution fields
when present.

## **Screenshots/Recordings**

Live Recipe Protocol v1 proof passed **177/177 nodes** in **143
seconds** at exact PR head `a2eb9dcaeae90f61edf22c4b8b9f9b353bf3fe8a`
with `@metamask/perps-controller@10.0.0`.

- Runtime: one production LavaMoat build; the real MetaMetrics client
sent Segment batches to the harness collector.
- Published proof stack: `@deeeed/metamask-harness@0.26.5` +
`@farmslot/recipe-harness@0.10.4`.
- Focused regression: 21 changed Perps/analytics suites, 762 tests, no
console-baseline violations; locale validation clean.
- Coverage: attributed screen views; search result and abandonment;
filter/sort/watchlist; trade and close; limit-order cancel; TP/SL;
add/remove margin screens; position flip; considered/abandoned order;
unknown-market error screen.
- Assertions: run-bracketed live payloads with event
order/count/property checks, including submitted + terminal controller
events and duplicate-emission guards.
- Run integrity: no recovery and no product mutation; the artifact
package retains the executed root, 11 digest-pinned child recipes,
summary, trace, diagnostics, and manifest.
- Diagnostics: eight non-blocking warning/error entries were retained in
`diagnostics.json`; none invalidated UI postconditions or analytics
assertions.

Final artifact paths in the PR slot:

-
`temp/fixes/44324/perps-analytics-exact-head-a2eb9dcaea-0265/report.md`
-
`temp/fixes/44324/perps-analytics-exact-head-a2eb9dcaea-0265/summary.json`
-
`temp/fixes/44324/perps-analytics-exact-head-a2eb9dcaea-0265/trace.json`
-
`temp/fixes/44324/perps-analytics-exact-head-a2eb9dcaea-0265/provenance.json`

## **Pre-merge author checklist**

- [x] I've followed [MetaMask Contributor
Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask
Extension Coding
Standards](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/CODING_GUIDELINES.md).
- [x] I've completed the PR template to the best of my ability
- [x] I’ve included tests if applicable
- [x] I’ve documented my code using [JSDoc](https://jsdoc.app/) format
if applicable
- [x] I’ve applied the right labels on the PR (see [labeling
guidelines](https://github.com/MetaMask/metamask-extension/blob/main/.github/guidelines/LABELING_GUIDELINES.md)).
Not required for external contributors.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR (e.g. pull and build branch, run the
app, test code being changed).
- [ ] I confirm that this PR addresses all acceptance criteria described
in the ticket it closes and includes the necessary testing evidence such
as recordings and or screenshots.

## **Validation Recipe**

<details>
<summary>Composable live Recipe Protocol v1 proof (177/177
nodes)</summary>

The root composes independent flows so a failed acceptance criterion is
isolated and rerunnable. The artifact package retains the exact child
documents listed by digest below.

```json
{
  "$schema": "https://farmslot.io/schemas/recipe-v1.schema.json",
  "title": "MetaMask Extension Perps analytics live proof",
  "description": "Compose independent live-app flows that prove Perps analytics from the built Extension and its real MetaMetrics client.",
  "workflow": {
    "entry": "ready",
    "nodes": {
      "ready": {
        "action": "call",
        "ref": "perps-analytics.runtime-ready",
        "intent": "Prepare the live Extension once",
        "next": "attribution"
      },
      "attribution": {
        "action": "call",
        "ref": "perps-analytics.attributed-screen",
        "intent": "Prove attributed asset-details analytics",
        "next": "search-result"
      },
      "search-result": {
        "action": "call",
        "ref": "perps-analytics.search-result",
        "intent": "Prove the successful search funnel",
        "next": "search-abandon"
      },
      "search-abandon": {
        "action": "call",
        "ref": "perps-analytics.search-abandon",
        "intent": "Prove the abandoned search funnel",
        "next": "discovery-controls"
      },
      "discovery-controls": {
        "action": "call",
        "ref": "perps-analytics.discovery-controls",
        "intent": "Prove market filter, sort, and watchlist analytics",
        "next": "trade-close"
      },
      "trade-close": {
        "action": "call",
        "ref": "perps-analytics.trade-close",
        "intent": "Prove the committed trade and close lifecycles",
        "next": "cancel-order"
      },
      "cancel-order": {
        "action": "call",
        "ref": "perps-analytics.cancel-order",
        "intent": "Prove the committed order-cancel lifecycle",
        "next": "risk-management"
      },
      "risk-management": {
        "action": "call",
        "ref": "perps-analytics.risk-management",
        "intent": "Prove the committed TP/SL lifecycle",
        "next": "margin-flip"
      },
      "margin-flip": {
        "action": "call",
        "ref": "perps-analytics.margin-flip",
        "intent": "Prove margin screens and the committed flip lifecycle",
        "next": "considered-abandon"
      },
      "considered-abandon": {
        "action": "call",
        "ref": "perps-analytics.considered-abandon",
        "intent": "Prove considered and abandoned order analytics",
        "next": "error-screen"
      },
      "error-screen": {
        "action": "call",
        "ref": "perps-analytics.error-screen",
        "intent": "Prove unknown-market error analytics",
        "next": "done"
      },
      "done": {
        "action": "end",
        "status": "pass"
      }
    }
  }
}
```

<details>
<summary>Resolved child recipe digests</summary>

- `perps-analytics.attributed-screen` —
`sha256:366ab0fa91b93d528c0b1e3567f050f37504f955d90d900757b2519d505e37ee`
- `perps-analytics.cancel-order` —
`sha256:08e49f8b3753b79b8058f32a50f04ed0d747576f1292bbeea92168ff9b81afa0`
- `perps-analytics.considered-abandon` —
`sha256:f4cad429c6209a1c8842daba3a081169d30d451bc025ba05f7c9dfbf874e4307`
- `perps-analytics.discovery-controls` —
`sha256:63259a262ffff22a301231e748195d67c3a40168b24ae754ed32c87368bdbd91`
- `perps-analytics.error-screen` —
`sha256:fe098d8b928d1228d0b36cf5cef57ea0c60bae42e6e66710301ef328b1ff902a`
- `perps-analytics.margin-flip` —
`sha256:ffe7eb7fe44a0c4e41009876a37856bec72cab162b3f410bfdf9bfa7277d5fa5`
- `perps-analytics.risk-management` —
`sha256:a198670ce27037ab93ac3f3fcd2c806cc8e554ad67f3ebda8c1d960afde13792`
- `perps-analytics.runtime-ready` —
`sha256:9f562eae962e306c28ab39ef588a193bdc2424f73168ee7d6300b9aa960b89a8`
- `perps-analytics.search-abandon` —
`sha256:94bfaf573cd1f6bba01fa0627e21858b6ff14374664b390f97e80a0cc6f7fdd0`
- `perps-analytics.search-result` —
`sha256:1de24f3a300f91faf9ecf7a09536cc0637ee5285866cefa59c4208d810e98b29`
- `perps-analytics.trade-close` —
`sha256:46935ae8b573b4ccd2e83ca462eeb657eb01879d7c01ff4cb285490fffba5598`

</details>

Run command:

```sh
npx --yes --package=@deeeed/metamask-harness@0.26.5 mm-harness run temp/fixes/44324/perps-analytics-live.recipe.json --adapter extension --target "$PWD" --cdp-port 6662 --library live=temp/fixes/44324/analytics-live-library --artifacts-dir temp/fixes/44324/perps-analytics-exact-head-a2eb9dcaea-0265 --json-stream
```

</details>

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Wide Perps UI and background analytics changes alter which events fire
and which properties they carry; regressions would affect metrics rather
than funds, but incorrect deduplication or status values could skew
funnel reporting.
> 
> **Overview**
> Aligns Extension Perps analytics with
**`@metamask/perps-controller`**: `shared/constants/perps-events` now
re-exports the controller contract (with Extension aliases), and
background **`trackPerpsEvent`** merges stored UTM via
**`mergeAttributionContext`** / **`perpsSetAttributionContext`**.
> 
> **Attribution end-to-end:** Perps deeplinks set **`source=deeplink`**
and forward **`utm_*`** (including unsigned params on signed links via
**`handlerSearchParams: 'original'`**). **`PerpsAttributionProvider`**
on the wallet Perps tab and **`usePerpsAttribution`** replace
**`buildPerpsVipTrackingData`**, attaching entry/discovery,
**`hlFeeRate`**, and trade action on background **`trackingData`**.
> 
> **Event ownership:** UI stops double-emitting trade/close/cancel/risk
success paths the controller already reports; transport/`{ success:
false }` gaps keep **`PerpsError`**, error **screen views**, and
targeted client events (e.g. margin **`{ success: false }`**, batch
close **position count** with **EXECUTED** status). Adds abandon-order
tracking, geo-block screen views, close-modal CTA props, and
**`input_method`** on size controls.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
a2eb9dc. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Bigshmow pushed a commit to MetaMask/metamask-extension that referenced this pull request Aug 5, 2026
## **Description**

Re-lands the analytics-contract consumption from #44324 after its revert
in #45087, without the deeplink-attribution route changes.

This keeps the controller-owned analytics contract, event ownership and
deduplication, UI funnel/error/abandonment tracking, and attribution
consumption. It does not change any deeplink route handler, add
`handlerSearchParams: original`, or forward original/unsigned
parameters. The signed-only attribution follow-up is proposed separately
in #45123 for focused Security review.

## **Changelog**

CHANGELOG entry: null

## **Related issues**

Refs: #44324, #45087, MetaMask/core#9311, MetaMask/core#9471

Follow-up: [TAT-3646 — restore signed Perps deeplink
attribution](https://consensyssoftware.atlassian.net/browse/TAT-3646)

## **Manual testing steps**

1. Build and start the Extension with Perps enabled.
2. Open Perps and navigate through the market list, market detail, and
order entry screens.
3. With analytics consent enabled, complete trade, close, cancel, TP/SL,
and margin flows.
4. Confirm each submitted and terminal transaction event is emitted
once, and confirm search, abandonment, filter, sort, watchlist, and
displayed-error events include their expected properties.

<!--
## **Screenshots/Recordings**

Fresh exact-head live analytics evidence will be attached after the
production build and composable recipe run complete. This evidence
validates the analytics contract only; it is not deeplink-security
evidence.
-->

## **Live recipe evidence**

The reusable recipe is merged at
[MetaMask/experimental-metamask-recipe-perps@c7cd115](https://github.com/MetaMask/experimental-metamask-recipe-perps/blob/c7cd11526ba85c2601463ede7f8cdf9697e8bde0/recipes/perps/analytics-lifecycle.extension.recipe.json).
The scrubbed, checksummed evidence is published at
[abretonc7s/mm-extension-farm-artifacts@461d55a](https://github.com/abretonc7s/mm-extension-farm-artifacts/tree/461d55a9e1406dfd57695bf5b6ff84d25ff991bb/fixes/45119/analytics-lifecycle).

<details>
<summary>201/201-node live HyperLiquid testnet proof</summary>

```sh
RECIPE_LIBRARY_PATH="perps=/path/to/experimental-metamask-recipe-perps" \
  mm-harness run perps.analytics-lifecycle \
  --adapter extension \

  market=BTC market_query=btc
```

- Product head: `d5e5a0184f01dd5fb099313060f99399327d9208`
- Recipe source: merged commit
`c7cd11526ba85c2601463ede7f8cdf9697e8bde0`
- Runner: `@metamask/recipe-runner` source commit
`fea899ce2716fd54cced5b58652025930d42fbf9`
- Runtime: `@farmslot/recipe-harness` 0.11.0
- Result: 201 passed, 0 failed, 136,200 ms; no recovery or runtime
mutation
- Safety: all ten mutating setup/cleanup executions reported `requested:
testnet`, `isTestnet: true`, and finished with zero matching orders and
positions
- Coverage: attributed screen views; search query, result, and
abandonment; discovery controls; trade/open and close; single and batch
cancellation; TP/SL; margin dialogs; position reversal; order
consideration and abandonment; displayed error screens
- Assertions use exact event totals plus required-property matches, so
duplicate emissions fail rather than passing on presence alone
- Review:
[report](https://github.com/abretonc7s/mm-extension-farm-artifacts/blob/461d55a9e1406dfd57695bf5b6ff84d25ff991bb/fixes/45119/analytics-lifecycle/report.md),
[summary](https://github.com/abretonc7s/mm-extension-farm-artifacts/blob/461d55a9e1406dfd57695bf5b6ff84d25ff991bb/fixes/45119/analytics-lifecycle/summary.json),
[trace](https://github.com/abretonc7s/mm-extension-farm-artifacts/blob/461d55a9e1406dfd57695bf5b6ff84d25ff991bb/fixes/45119/analytics-lifecycle/trace.json),
and [SHA-256
checksums](https://github.com/abretonc7s/mm-extension-farm-artifacts/blob/461d55a9e1406dfd57695bf5b6ff84d25ff991bb/fixes/45119/analytics-lifecycle/SHA256SUMS)

The trace proves analytics consumption after routing inside the running
Extension. It deliberately does not claim external signed-deeplink
parsing, canonicalization, or security behavior.

</details>

## **Validation**

- 18 affected Perps suites / 730 tests passed with no console-baseline
violations.
- `yarn lint:changed:fix` passed with zero errors.
- `yarn lint:tsc` passed.
- Final diff audit found no files under `shared/lib/deep-links`, no
`handlerSearchParams: original`, no `withDeeplinkAttribution`, and no
original/unsigned handler parameter access.
- The composed live recipe passed 201/201 nodes against this exact
product head.

## **Pre-merge author checklist**

- [x] I have followed MetaMask Contributor Docs and MetaMask Extension
Coding Standards.
- [x] I have completed the PR template to the best of my ability.
- [x] I have included tests where applicable.
- [x] I have documented code using JSDoc where applicable.
- [x] I have applied the right labels. Not required for external
contributors.

## **Pre-merge reviewer checklist**

- [ ] I have manually tested the PR.
- [ ] I confirm that this PR addresses the applicable acceptance
criteria and includes the necessary testing evidence.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Touches MetaMetrics event shapes and ownership across many Perps
flows; mis-deduping or wrong status/attribution would skew analytics but
does not change trading or auth logic.
> 
> **Overview**
> Re-lands Perps analytics alignment with `@metamask/perps-controller`:
**`shared/constants/perps-events`** now imports the controller contract
(with Extension aliases), new MetaMetrics funnel events are registered,
and the unused **`tutorial`** locale key is removed.
> 
> **Background:** `perpsSetAttributionContext` is exposed and
`mergeAttributionContext` is wired through `createPerpsInfrastructure`
so controller-emitted transaction events pick up stored UTM fields
before MetaMetrics. Tests consolidate on
**`test/mocks/metamask-perps-controller.js`**.
> 
> **UI:** `PerpsAttributionProvider` / **`usePerpsAttribution`** replace
`buildPerpsVipTrackingData` and attach **`trackingData`** (fees, VIP,
`hlFeeRate`, flow attribution) on cancel, close, flip, and TP/SL.
Duplicate client **transaction** analytics are removed where the
controller owns submitted/terminal events; gaps remain for transport
throws, margin `{ success: false }`, batch close counts,
**`trackPerpsErrorScreenViewed`**, geo-block screen views,
**`usePerpsAbandonOrderTracking`**, and order-size **`input_method`**
plumbing. **`derivePerpsTradeAction`** keeps considered vs executed
`action` consistent (including omitting reduce-only opposite-side
orders).
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
b5e316c. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
n3ps pushed a commit to MetaMask/metamask-extension that referenced this pull request Aug 6, 2026
## **Description**

Re-lands the analytics-contract consumption from #44324 after its revert
in #45087, without the deeplink-attribution route changes.

This keeps the controller-owned analytics contract, event ownership and
deduplication, UI funnel/error/abandonment tracking, and attribution
consumption. It does not change any deeplink route handler, add
`handlerSearchParams: original`, or forward original/unsigned
parameters. The signed-only attribution follow-up is proposed separately
in #45123 for focused Security review.

## **Changelog**

CHANGELOG entry: null

## **Related issues**

Refs: #44324, #45087, MetaMask/core#9311, MetaMask/core#9471

Follow-up: [TAT-3646 — restore signed Perps deeplink
attribution](https://consensyssoftware.atlassian.net/browse/TAT-3646)

## **Manual testing steps**

1. Build and start the Extension with Perps enabled.
2. Open Perps and navigate through the market list, market detail, and
order entry screens.
3. With analytics consent enabled, complete trade, close, cancel, TP/SL,
and margin flows.
4. Confirm each submitted and terminal transaction event is emitted
once, and confirm search, abandonment, filter, sort, watchlist, and
displayed-error events include their expected properties.

<!--
## **Screenshots/Recordings**

Fresh exact-head live analytics evidence will be attached after the
production build and composable recipe run complete. This evidence
validates the analytics contract only; it is not deeplink-security
evidence.
-->

## **Live recipe evidence**

The reusable recipe is merged at
[MetaMask/experimental-metamask-recipe-perps@c7cd115](https://github.com/MetaMask/experimental-metamask-recipe-perps/blob/c7cd11526ba85c2601463ede7f8cdf9697e8bde0/recipes/perps/analytics-lifecycle.extension.recipe.json).
The scrubbed, checksummed evidence is published at
[abretonc7s/mm-extension-farm-artifacts@461d55a](https://github.com/abretonc7s/mm-extension-farm-artifacts/tree/461d55a9e1406dfd57695bf5b6ff84d25ff991bb/fixes/45119/analytics-lifecycle).

<details>
<summary>201/201-node live HyperLiquid testnet proof</summary>

```sh
RECIPE_LIBRARY_PATH="perps=/path/to/experimental-metamask-recipe-perps" \
  mm-harness run perps.analytics-lifecycle \
  --adapter extension \

  market=BTC market_query=btc
```

- Product head: `d5e5a0184f01dd5fb099313060f99399327d9208`
- Recipe source: merged commit
`c7cd11526ba85c2601463ede7f8cdf9697e8bde0`
- Runner: `@metamask/recipe-runner` source commit
`fea899ce2716fd54cced5b58652025930d42fbf9`
- Runtime: `@farmslot/recipe-harness` 0.11.0
- Result: 201 passed, 0 failed, 136,200 ms; no recovery or runtime
mutation
- Safety: all ten mutating setup/cleanup executions reported `requested:
testnet`, `isTestnet: true`, and finished with zero matching orders and
positions
- Coverage: attributed screen views; search query, result, and
abandonment; discovery controls; trade/open and close; single and batch
cancellation; TP/SL; margin dialogs; position reversal; order
consideration and abandonment; displayed error screens
- Assertions use exact event totals plus required-property matches, so
duplicate emissions fail rather than passing on presence alone
- Review:
[report](https://github.com/abretonc7s/mm-extension-farm-artifacts/blob/461d55a9e1406dfd57695bf5b6ff84d25ff991bb/fixes/45119/analytics-lifecycle/report.md),
[summary](https://github.com/abretonc7s/mm-extension-farm-artifacts/blob/461d55a9e1406dfd57695bf5b6ff84d25ff991bb/fixes/45119/analytics-lifecycle/summary.json),
[trace](https://github.com/abretonc7s/mm-extension-farm-artifacts/blob/461d55a9e1406dfd57695bf5b6ff84d25ff991bb/fixes/45119/analytics-lifecycle/trace.json),
and [SHA-256
checksums](https://github.com/abretonc7s/mm-extension-farm-artifacts/blob/461d55a9e1406dfd57695bf5b6ff84d25ff991bb/fixes/45119/analytics-lifecycle/SHA256SUMS)

The trace proves analytics consumption after routing inside the running
Extension. It deliberately does not claim external signed-deeplink
parsing, canonicalization, or security behavior.

</details>

## **Validation**

- 18 affected Perps suites / 730 tests passed with no console-baseline
violations.
- `yarn lint:changed:fix` passed with zero errors.
- `yarn lint:tsc` passed.
- Final diff audit found no files under `shared/lib/deep-links`, no
`handlerSearchParams: original`, no `withDeeplinkAttribution`, and no
original/unsigned handler parameter access.
- The composed live recipe passed 201/201 nodes against this exact
product head.

## **Pre-merge author checklist**

- [x] I have followed MetaMask Contributor Docs and MetaMask Extension
Coding Standards.
- [x] I have completed the PR template to the best of my ability.
- [x] I have included tests where applicable.
- [x] I have documented code using JSDoc where applicable.
- [x] I have applied the right labels. Not required for external
contributors.

## **Pre-merge reviewer checklist**

- [ ] I have manually tested the PR.
- [ ] I confirm that this PR addresses the applicable acceptance
criteria and includes the necessary testing evidence.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Medium Risk**
> Touches MetaMetrics event shapes and ownership across many Perps
flows; mis-deduping or wrong status/attribution would skew analytics but
does not change trading or auth logic.
> 
> **Overview**
> Re-lands Perps analytics alignment with `@metamask/perps-controller`:
**`shared/constants/perps-events`** now imports the controller contract
(with Extension aliases), new MetaMetrics funnel events are registered,
and the unused **`tutorial`** locale key is removed.
> 
> **Background:** `perpsSetAttributionContext` is exposed and
`mergeAttributionContext` is wired through `createPerpsInfrastructure`
so controller-emitted transaction events pick up stored UTM fields
before MetaMetrics. Tests consolidate on
**`test/mocks/metamask-perps-controller.js`**.
> 
> **UI:** `PerpsAttributionProvider` / **`usePerpsAttribution`** replace
`buildPerpsVipTrackingData` and attach **`trackingData`** (fees, VIP,
`hlFeeRate`, flow attribution) on cancel, close, flip, and TP/SL.
Duplicate client **transaction** analytics are removed where the
controller owns submitted/terminal events; gaps remain for transport
throws, margin `{ success: false }`, batch close counts,
**`trackPerpsErrorScreenViewed`**, geo-block screen views,
**`usePerpsAbandonOrderTracking`**, and order-size **`input_method`**
plumbing. **`derivePerpsTradeAction`** keeps considered vs executed
`action` consistent (including omitting reduce-only opposite-side
orders).
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
b5e316c. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
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