Skip to content

fix(perps-controller): repair CommonJS build and add close leverage + order latency - #9471

Merged
gambinish merged 10 commits into
mainfrom
fix/perps-controller-cjs-hyperliquid
Jul 16, 2026
Merged

gambinish merged 10 commits into
mainfrom
fix/perps-controller-cjs-hyperliquid

Conversation

@abretonc7s

@abretonc7s abretonc7s commented Jul 10, 2026 •

Copy link
Copy Markdown
Contributor

Explanation

perps-controller@9.2.1 (#9311) has a broken CommonJS build: dist/services/HyperLiquidClientService.cjs and dist/utils/standaloneInfoClient.cjs emit require("file:///home/runner/work/hyperliquid/hyperliquid/src/mod.ts") — an absolute build-machine path — so any CJS/bundler/Jest consumer throws Cannot find module. Root cause is upstream: @nktkas/hyperliquid (0.33.0+) ships .d.ts with a /// directive (a Deno->npm dnt build artifact); ts-bridge uses that amd-module name as the CJS require() target, leaking the absolute path. The ESM build is unaffected.

This PR:

  1. Fixes the CJS build with a yarn patch on @nktkas/hyperliquid that strips the amd-module directives, applied via the root monorepo resolutions (build-time only). perps-controller's own dependency range stays ^0.33.1 — the patch is deliberately NOT in the package's dependencies, since a published patch: specifier would break every downstream consumer. After rebuild both .cjs files emit require("@nktkas/hyperliquid") with zero file: leaks. Bumping the SDK does not fix this (0.33.1 is latest and still ships the directive; 0.32.2 is the last clean version but predates breaking API changes this package depends on).
  2. — PERPS_POSITION_CLOSE_TRANSACTION.leverage is now the computed effective leverage abs(positionValue)/marginUsed rounded to 1 decimal (guarded for finite values and marginUsed > 0), populated for every close including TP/SL.
  3. — adds optional TrackingData.orderExecutionLatencyMs, emitted as order_execution_latency_ms on the trade terminal event only (scoped to #trackOrderResult, not the shared attribution builder), with a !== undefined guard.

All changes are additive and recorded under ## [Unreleased] in the CHANGELOG (version stays 9.2.1 in this PR per release-cut convention; the next perps-controller release picks them up).

References

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, additive/non-breaking

Note

Medium Risk
Touches live trading analytics classification and provider order results alongside a monorepo-wide dependency patch; changes are additive and well-tested but affect how partial fills and margin failures are reported.

Overview
Repairs the broken CommonJS build for @metamask/perps-controller consumers by adding a Yarn patch on @nktkas/hyperliquid@0.33.1 (wired through root resolutions) that strips /// <amd-module name="file:///..."> lines from shipped .d.ts files, so ts-bridge no longer emits require("file:///home/runner/...") in .cjs artifacts.

Perps analytics and trading telemetry are extended in TradingService and HyperLiquidProvider: close events report effective leverage (abs(positionValue) / marginUsed, 1 dp) instead of configured leverage; successful opens can emit an extra partially_filled PERPS_TRADE_TRANSACTION when filledSize is below the exchange submitted size (new OrderResult.submittedSize from the provider, including post-retry normalization); batch closes add number_positions_closed; updateMargin now emits a failed Risk Management event when the provider returns { success: false } without throwing. TradeAction types now include flip variants.

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

… order latency

The published 9.2.1 CJS build emits require("file:///home/runner/.../mod.ts")
because @nktkas/hyperliquid ships .d.ts with an amd-module directive carrying an
absolute CI path; ts-bridge uses it as the CJS require target, so any CJS consumer
throws Cannot find module. Fixed via a yarn patch on the SDK applied through the
root monorepo resolutions (build-time only); perps-controller's dependency range
stays ^0.33.1. Upstream: nktkas/hyperliquid#165

Also:
- TAT-3147: close events emit computed effective leverage (abs(positionValue)/marginUsed,
  1 dp), incl. TP/SL closes.
- TAT-3084: add optional TrackingData.orderExecutionLatencyMs, emitted as
  order_execution_latency_ms on the trade terminal event only.

Bumps perps-controller to 9.2.2.
…rnal ticket refs

Move the pending analytics and CommonJS-fix entries from a premature [9.2.2]
section to [Unreleased] and revert the package version to 9.2.1 so the release
check passes (package versions are bumped at release-cut, not in feature PRs).
Remove internal ticket references from the changelog, code comments, and test
names, and apply oxfmt formatting.
The check-changelog action requires user-facing Unreleased entries to link to
the current pull request. Add the PR reference to the added, changed, and fixed
entries.
Mirror the close path on the open-trade path in #trackOrderResult: when a
successful order fills for less than the requested size, emit an additional
PERPS_TRADE_TRANSACTION event with status=partially_filled plus amount_filled
and remaining_amount. Full fills are unchanged. Previously partial fills only
emitted status=executed, hiding the fill's partiality.

@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 2 potential issues.

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 82545e5. Configure here.

Comment thread packages/perps-controller/src/services/TradingService.ts Outdated
Comment thread packages/perps-controller/src/services/TradingService.ts
…ing failures

updateMargin only emitted the failed Perp Risk Management analytics event on
the thrown-error path, so a non-throwing { success: false } provider result
silently lost the terminal event. Emit the failed event on that branch too,
with the error message from the result, keeping exactly-once semantics per
operation (success emits executed, non-throwing failure emits failed, thrown
errors emit failed in the catch).
Add flip_long_to_short and flip_short_to_long to the TradeAction union
alongside create_position and increase_exposure. The runtime already forwards
these values verbatim, so consumers no longer need casts when deriving flip
actions.
…mmary

The closePositions batch summary event carried only status, completion_duration
and bulk_action_id, silently dropping the closed count after clients removed
their own close-all summary emission. Add a NUMBER_POSITIONS_CLOSED event
property and emit the successful-close count on the batch summary event.

@geositta geositta left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I am requesting one change before approval: partial-fill classification should use the exact size submitted to the provider rather than the earlier request size. Provider normalization or retry adjustment can otherwise report a complete fill as partial and emit an incorrect remainder.

I also left two nonblocking contract questions concerning how order_execution_latency_ms differs from the existing completion_duration, and whether the supplemental partial-fill event should mirror the close-event schema.

// Emit an additional partially filled trade event when the fill is partial,
// mirroring the close path so the fill's partiality is visible in analytics
// rather than hidden behind a status=executed event.
const requestedSize = parseFloat(params.size);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Could we classify partial fills using the exact size submitted by the provider? This comparison uses params.size, while HyperLiquidProvider.placeOrder can normalize that value to asset precision, recalculate it from usdAmount, or adjust it during the minimum order retry. A complete fill of the normalized size can therefore emit partially_filled with a small incorrect remaining_amount. Returning the submitted size in OrderResult, or having the provider return the partial-fill classification, would keep the analytics status aligned with the executed order.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in 7a0e3f3. HyperLiquidProvider.placeOrder now returns the final normalized size it actually submits to the exchange as OrderResult.submittedSize (post precision rounding, USD recalculation, and the $10-minimum retry), and TradingService classifies partial fills against that value (filledSize < submittedSize) rather than the pre-normalization params.size. When the provider does not report a submitted size we no longer classify at all, so a complete fill of the normalized size is never misreported as partially filled.


// Order execution latency on the terminal trade event
if (params.trackingData?.orderExecutionLatencyMs !== undefined) {
properties[PERPS_EVENT_PROPERTY.ORDER_EXECUTION_LATENCY_MS] =

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What distinction should order_execution_latency_ms have from the existing completion_duration on this terminal event? The new field accepts a completed elapsed duration in the request data, although callers construct that request before the operation completes. If a client specific start boundary is required, accepting a start timestamp and calculating the elapsed value here would make that contract easier to use. If the controller boundary is sufficient, the existing duration already provides it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Agreed — removed entirely. An input tracking param cannot carry a post-completion measurement, and completion_duration already measures submission through terminal result at the controller boundary. Dropped the field, its emission, tests, and the CHANGELOG entry in 7a0e3f3.

filledSize < requestedSize
) {
this.#deps.metrics.trackPerpsEvent(PerpsAnalyticsEvent.TradeTransaction, {
...properties,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit: Could we confirm the intended schema for the supplemental partial-fill event? Spreading properties gives it order_size = filledSize and order_execution_latency_ms, while the close partial-fill path keeps order_size as the requested size and reports the fill separately through amount_filled. If dashboard parity is intended, we should override order_size with the requested size and decide explicitly whether latency belongs on both outcomes.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Aligned with the close-event schema in 7a0e3f3: order_size is now the submitted size, the fill is reported separately via amount_filled, and remaining_amount = submittedSize - filledSize. The open-trade partial event now mirrors the close path exactly.

Base the open-trade partially_filled analytics event on the provider's
final submitted size rather than the caller's pre-normalization size.
HyperLiquidProvider transforms the requested size before submission
(precision rounding, USD recalculation, $10-minimum retry) and never
echoed it back, so a complete fill of the normalized size was reported
as partially_filled with a wrong remaining_amount.

- Add optional OrderResult.submittedSize, returned by HyperLiquidProvider
  as the final normalized main-order size actually sent to the exchange.
- Classify partial fills only when submittedSize is present
  (filledSize < submittedSize); when absent, do not classify.
- Align the partial event schema with the close path: order_size is the
  submitted size, the fill is reported via amount_filled, and
  remaining_amount = submittedSize - filledSize.
- Remove order_execution_latency_ms: an input tracking param cannot carry
  a post-completion measurement, and completion_duration already measures
  submission to terminal result at the controller boundary.
@abretonc7s
abretonc7s requested a review from geositta July 11, 2026 07:11
Classify open-trade partial fills with arbitrary-precision BigNumber
comparison and subtraction instead of parseFloat. Routing the decimal
size strings through parseFloat can collapse distinct values (float
artifacts) into an equal comparison — misclassifying a partial fill —
and leaves binary-float dust in remaining_amount (e.g. 10 - 9.7 =
0.30000000000000071). Values are converted to Number only for the
emitted analytics, after the exact decimal subtraction.

Also tighten the $10-minimum retry test to assert submittedSize equals
the second (post-retry) submission's size, not the first rejected
attempt, and add precision regression tests for exact remaining_amount
and parseFloat-collapse classification.
Add explicit coverage for the guards already enforced by the open-trade
partial-fill classifier: a non-finite filledSize or submittedSize emits
no partially_filled event (and no NaN size on any trade event), and a
failed result that still carries filled/submitted sizes never emits a
partial event since classification is gated on success.

@geositta geositta left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approved. Thanks for addressing the review feedback. I verified that the provider now returns the final normalized size submitted to the exchange, including the minimum order retry path, and that TradingService uses that value for partial fill classification.

The updated implementation also aligns the partial-fill event schema with the close path, uses decimal arithmetic for comparison and remaining_amount, and safely skips classification when either size is unavailable or invalid. Removing the client supplied latency field resolves the contract ambiguity with the existing controller measured completion_duration.

@gambinish
gambinish added this pull request to the merge queue Jul 16, 2026
Merged via the queue into main with commit ae49342 Jul 16, 2026
421 checks passed
@gambinish
gambinish deleted the fix/perps-controller-cjs-hyperliquid branch July 16, 2026 15:24
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/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 -->
Naz-Ovh pushed a commit to 0x-fork/metamask-core that referenced this pull request Sep 30, 2026
…k#10591)

## Explanation

Bumps `@nktkas/hyperliquid` from `^0.33.1` to `^0.33.3` in
`@metamask/perps-controller`
([v0.33.2](https://github.com/nktkas/hyperliquid/releases/tag/v0.33.2),
[v0.33.3](https://github.com/nktkas/hyperliquid/releases/tag/v0.33.3)).

- **Yarn patch dropped.** The patch (MetaMask#9471) stripped `/// <amd-module
name="file:///home/runner/...">` directives from the SDK's `.d.ts`
files. `0.33.3` no longer ships them, so the patch and its root
`resolutions` entry are gone. The built `dist` has no `file:///`
references.
- **`dexAbstraction` migration removed.** `0.33.2` dropped
`dexAbstraction` from `userAbstraction` / `userSetAbstraction` to match
the API: HyperLiquid retired the mode (the live `userAbstraction`
endpoint now returns `"default"` for the SDK's former `dexAbstraction`
fixture users). The `userSetAbstraction` main-wallet migration and its
"don't re-prompt after a rejection" cache were dead, so they are
removed. `default` / `disabled` accounts still migrate through
`agentSetAbstraction`, and deferral with `allowUserSigning: false` is
unchanged. An unexpected `dexAbstraction` response now takes the
existing unknown-mode bail.
- **New order types.** `FrontendOrder.orderType` adds `Twap Slice`,
`Vault Close` and `Spot Dust Conversion`. They are mapped explicitly to
`'market'` in historical orders, which is what the fallback already
returned.
- **Type-only breaks:** `OrderFill.liquidation.liquidatedUser` is now
optional (HyperLiquid omits it on some liquidation fills), and the
re-exported `FrontendOrder.orderType` union gains the three values
above. Mobile (`PerpsFillTag`) and Extension (`perps-fill-tag`) already
guard `liquidatedUser` at runtime; their local `transactionHistory`
types declare it as `string` and may need `?` when adopting this
release.
- **Historical order statuses.** Every `OrderProcessingStatus` from the
SDK now maps explicitly (exhaustive `satisfies` map): the specific
`*Rejected` statuses report `rejected` and `outcomeSettledCanceled` /
`internalCancel` report `canceled`, where they previously fell through
to `queued`.
- **Unified-account migration lock.** Waiters re-check the in-flight
lock after each wait (same pattern as the referral lock), so an attempt
that ends without caching a result (e.g. deferred at init for a hardware
wallet) no longer lets every waiter run `agentSetAbstraction`
concurrently.

## References

- Fixes
[TAT-4044](https://consensyssoftware.atlassian.net/browse/TAT-4044)
- Mobile validation (draft, preview build `18.0.1-preview-2848be6`,
device recipe evidence):
MetaMask/metamask-mobile#37025
- Upstream schema change: nktkas/hyperliquid#167

## 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)
- [ ] 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


[TAT-4044]:
https://consensyssoftware.atlassian.net/browse/TAT-4044?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants