Skip to content

feat(perps): update to perps-controller 22.3.0 and use its shared calculations instead of Extension copies - #47777

Open
abretonc7s wants to merge 4 commits into
mainfrom
TAT-4534-feat-update-perps-controller-22-3-0
Open

abretonc7s wants to merge 4 commits into
mainfrom
TAT-4534-feat-update-perps-controller-22-3-0

Conversation

@abretonc7s

@abretonc7s abretonc7s commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Description

Extension computed its own Perps close and TP/SL figures, so the same position showed different numbers than Mobile and Terminal. This bumps @metamask/perps-controller to ^22.3.0 and uses Core's shared calculations instead:

  • Close and close-all use estimateCloseReceive. Cross margin keeps its PnL (it used to be subtracted), PnL is priced at the live or limit close price, cents round half away from zero (1.005 → 1.01), and close-all shows a signed receive instead of clamping at 0. Amounts keep their cents ($2,114.49, not $2,114.5). A partial close is quoted at the size it submits, rounded to the market's size step. While the fee rate is unquoted, fee and receive show -- and Close (and Close all) stays disabled, so no fee is reported as 0.
  • Activity, recent activity and transaction details use calculateNetClosedPnl. A fill with an unknown fee keeps its net PnL unknown, including when split fills are aggregated. Details show the same amount as the activity row (cents rounded half up, thousands grouped), zero included, and do not subtract the fee again.
  • TP/SL (order entry auto close, edit TP/SL) uses Core's RoE ↔ trigger price, validation, validateTPSLPrices and formatRoePercent. A stop loss is capped at 99% loss per 1x, trigger prices keep Core's precision after the field loses focus, and an unknown leverage shows no RoE instead of assuming 1x.
  • Extension has no local account-summary derivation, so calculateAccountSummary (TAT-4480) has nothing to replace. Add/remove margin and new-order calculations are unchanged, per the ticket.

The dependency bump hoists @metamask/abi-utils for the controller, so the LavaMoat policies change only that edge. The Jest stub now re-exports Core's real calculation modules, so the tests check Core's figures.

Changelog

CHANGELOG entry: Fixed Perps close amounts, closed PnL and TP/SL prices so they match the MetaMask mobile app

Related issues

Fixes: https://consensyssoftware.atlassian.net/browse/TAT-4534

Manual testing steps

  1. Build and run the extension, open Perps on Hyperliquid testnet with a funded account.
  2. Open a small long (for example $11 SOL at 1x), open its market page and press Close long.
  3. Check that Fees and You'll receive show -- until the fee loads, Close is disabled until then, and Margin − Fees = You'll receive to the cent.
  4. On Perps home, press Close all, check that the rows add up the same way, then press Keep positions.
  5. Close the position. Check that the market's Recent activity row, Perps home Recent activity and the transaction details show the same P&L.
  6. Open a new order, turn on Auto close and type -5000 in the stop loss %. Check that a stop loss price appears (about 1% of the price at 1x) instead of staying empty.

Screenshots/Recordings

AC-2: Close review rows
Close review rows

AC-3: Market activity net PnL
Market activity net PnL

AC-1: Stop loss at -5000% RoE gets Core's capped price
Stop loss at -5000% RoE gets Core's capped price

Full evidence gallery and publication receipt
Close review rows
Before
before
After
after
Close all rows
Before
before
After
after
Market activity net PnL
Before
before
After
after
Recent activity net PnL
Before
before
After
after
Transaction details net PnL
Before
before
After
after
Stop loss at -5000% RoE gets Core's capped price
Stop loss at -5000% RoE gets Core's capped price
Cross close review: margin includes PnL, fee, receive
Cross close review: margin includes PnL, fee, receive
caption confidence: LOW — generic filename — no state-specific suffix
Partial (50%) close review at the submitted size
Partial (50%) close review at the submitted size
caption confidence: LOW — generic filename — no state-specific suffix
Limit close review priced at the limit
Limit close review priced at the limit
caption confidence: LOW — generic filename — no state-specific suffix
The Perps tutorial is not covering the page
The Perps tutorial is not covering the page
Unassigned capture.
The close review shows a signed amount you will receive
The close review shows a signed amount you will receive
Unassigned capture.
The close review shows the quoted fee
The close review shows the quoted fee
Unassigned capture.
The close review is dismissed
The close review is dismissed
Unassigned capture.
Close all shows a signed total you will receive
Close all shows a signed total you will receive
Unassigned capture.
Close all shows the total margin with PnL
Close all shows the total margin with PnL
Unassigned capture.
Close all is dismissed and nothing was closed
Close all is dismissed and nothing was closed
Unassigned capture.
Close is enabled once the fee is quoted
Close is enabled once the fee is quoted
Unassigned capture.
The owned close shows in the market activity
The owned close shows in the market activity
Unassigned capture.
The owned close row is visible before the market activity capture
The owned close row is visible before the market activity capture
Unassigned capture.
The owned close appears in recent activity
The owned close appears in recent activity
Unassigned capture.
The owned close row is visible before the recent activity capture
The owned close row is visible before the recent activity capture
Unassigned capture.
The owned close transaction details are showing
The owned close transaction details are showing
Unassigned capture.
The Perps tutorial is not covering the page
The Perps tutorial is not covering the page
Unassigned capture.
The order entry page is showing
The order entry page is showing
Unassigned capture.
The stop-loss RoE field is showing
The stop-loss RoE field is showing
Unassigned capture.
The stop-loss price is visible before the capture
The stop-loss price is visible before the capture
Unassigned capture.
The cross close review shows what you will receive
The cross close review shows what you will receive
Unassigned capture.
The cross close review shows the quoted fee
The cross close review shows the quoted fee
Unassigned capture.
The slider reflects a 50% close
The slider reflects a 50% close
Unassigned capture.
Close is enabled once the partial fee is quoted
Close is enabled once the partial fee is quoted
Unassigned capture.
The limit close review shows the quoted fee
The limit close review shows the quoted fee
Unassigned capture.
Close is enabled once the limit fee is quoted
Close is enabled once the limit fee is quoted
Unassigned capture.

Publication approval

Arthur · person · arthur · 2026-11-10T01:04:17.445Z
Package: c100f61b428837415bcd830fa6e8dc5ef9c2f9873476c5f2ef02e50a02729253. Visual receipts: none.

Evidence policy attestation

Approved by Arthur (arthur) at 2026-11-10T01:04:17.444Z.
Package: c100f61b428837415bcd830fa6e8dc5ef9c2f9873476c5f2ef02e50a02729253. Decision: 73294e2c-77db-4981-8fea-5db6ee865705.

Reason:

Recipe run 3e1bf21c, 123/123. AC-1: depends on perps-controller 22.3; capped stop-loss trigger from Core. AC-2: close review isolated margin $11.22 - fee $0.02 = receive $11.20; close-all $11.21 - $0.02 = $11.19; cross $25.04 - $0.04 = $25; reconciliation assertions pass for close, close-all, cross, partial and limit. AC-3: Recent activity shows the close at -$0.02 and details show P&L -$0.02 with fees $0.02 listed separately.

Waived claim-policy findings:

  • Recorded claim verdicts do not satisfy publication policy.

Original evidence and verdicts are retained. Integrity, source, CI and review checks remain required.

Validation Recipe

recipe.json (116 nodes — Perps close figures and TP/SL from Core 22.3.0 on Extension)
{
  "$schema": "https://farmslot.io/schemas/recipe-v1.schema.json",
  "title": "Perps close figures and TP/SL from Core 22.3.0 on Extension",
  "description": "On Hyperliquid testnet, open one smallest owned position, show the close and close-all reviews with Core's margin-with-PnL, fee and signed receive, close it, show the same net closed PnL in market activity, recent activity and transaction details, then show Core's capped stop-loss price in the order entry. Other runs' positions on the shared account are only read.",
  "proofTargets": [
    {
      "id": "AC-1",
      "claim": "Extension depends on `@metamask/perps-controller` 22.3.0; no local copy of a calculation Core exports remains in scope.",
      "proofMode": "mixed"
    },
    {
      "id": "AC-2",
      "claim": "Close modal and close-all show Core's receive, margin-with-PnL and fee for Hyperliquid isolated and cross, gain and loss, partial and limit close, with discounts; values match Core's reference rows to the cent; signed receive shown.",
      "proofMode": "mixed"
    },
    {
      "id": "AC-3",
      "claim": "Activity, recent activity and transaction details agree on net closed PnL from Core for split fills, zero, rebates and unknown fees.",
      "proofMode": "mixed"
    },
    {
      "id": "AC-4",
      "claim": "Recipe proof in the browser for each user-visible change (testnet, smallest size), unit tests fail before and pass after, perps-review-pr passes.",
      "proofMode": "review"
    },
    {
      "id": "AC-5",
      "claim": "Research with exact file paths and callers: client migration inventory drafted for Core #11378 (Extension section).",
      "proofMode": "review"
    }
  ],
  "paramsSchema": {
    "type": "object",
    "additionalProperties": false,
    "properties": {
      "market": {
        "type": "string",
        "default": "SOL",
        "description": "Market this run owns on the shared testnet account; must start flat."
      },
      "amount": {
        "type": "string",
        "default": "11",
        "description": "Smallest USD order amount above the venue minimum."
      },
      "leverage": {
        "type": "integer",
        "default": 1,
        "description": "Order leverage."
      },
      "stop_loss_roe": {
        "type": "string",
        "default": "-5000",
        "description": "Stop-loss RoE beyond Core's 99% per 1x cap at any market leverage."
      },
      "network": {
        "type": "string",
        "enum": [
          "testnet"
        ],
        "default": "testnet",
        "description": "Perps network; this proof trades, so testnet only."
      },
      "provider": {
        "type": "string",
        "enum": [
          "hyperliquid"
        ],
        "default": "hyperliquid",
        "description": "Provider holding the owned position."
      },
      "side": {
        "type": "string",
        "enum": [
          "long",
          "short"
        ],
        "default": "long",
        "description": "Side of the owned position."
      },
      "cross_amount": {
        "type": "string",
        "default": "24",
        "description": "USD size of the owned cross position: half of it still clears the venue minimum."
      }
    }
  },
  "workflow": {
    "entry": "setup-unlock",
    "nodes": {
      "setup-unlock": {
        "action": "metamask.wallet.ensure_unlocked",
        "intent": "Start from the unlocked slot wallet",
        "next": "setup-perps"
      },
      "setup-perps": {
        "action": "ui.navigate",
        "page": "perps",
        "intent": "Start from Perps home",
        "next": "setup-tutorial-clear"
      },
      "setup-tutorial-clear": {
        "action": "ui.wait_for",
        "test_id": "perps-tutorial-skip-button",
        "expected": "absent",
        "timeout_ms": 5000,
        "intent": "The Perps tutorial is not covering the page",
        "next": "setup-testnet"
      },
      "setup-testnet": {
        "action": "metamask.perps.start_state",
        "network": "{{params.network}}",
        "page": false,
        "positions": false,
        "orders": false,
        "intent": "Select Hyperliquid testnet and verify it before trading",
        "next": "setup-flat-positions"
      },
      "setup-flat-positions": {
        "action": "metamask.perps.assert_positions",
        "market": "{{params.market}}",
        "state": "none",
        "intent": "The owned market starts without a position on either side",
        "next": "setup-flat-orders"
      },
      "setup-flat-orders": {
        "action": "metamask.perps.assert_orders",
        "market": "{{params.market}}",
        "state": "none",
        "intent": "The owned market starts without open orders",
        "next": "setup-run-start"
      },
      "setup-open": {
        "action": "metamask.perps.place_order",
        "market": "{{params.market}}",
        "side": "{{params.side}}",
        "amount": "{{params.amount}}",
        "leverage": "{{params.leverage}}",
        "order_type": "market",
        "provider": "{{params.provider}}",
        "network": "{{params.network}}",
        "confirm_live": true,
        "intent": "Open one smallest owned long on testnet",
        "next": "setup-position-open"
      },
      "setup-position-open": {
        "action": "metamask.perps.assert_positions",
        "market": "{{params.market}}",
        "side": "{{params.side}}",
        "state": "open",
        "intent": "The owned long is open",
        "next": "ac2-open-market"
      },
      "ac2-open-market": {
        "action": "ui.navigate",
        "page": "perps-market",
        "market": "{{params.market}}",
        "intent": "Show the owned position on its market page",
        "next": "ac2-venue-position"
      },
      "ac2-open-close-modal": {
        "action": "ui.press",
        "test_id": "perps-close-cta-button",
        "intent": "Review closing the owned position",
        "next": "ac2-close-modal-receive"
      },
      "ac2-close-modal-receive": {
        "action": "ui.wait_for",
        "test_id": "perps-close-summary-receive-value",
        "expected": "visible",
        "timeout_ms": 15000,
        "intent": "The close review shows a signed amount you will receive",
        "next": "ac2-close-modal-fees",
        "proves": [
          "AC-2"
        ]
      },
      "ac2-close-modal-fees": {
        "action": "ui.wait_for",
        "test_id": "perps-close-summary-fees-value",
        "expected": "visible",
        "timeout_ms": 15000,
        "intent": "The close review shows the quoted fee",
        "next": "ac2-close-modal-figures",
        "proves": [
          "AC-2"
        ]
      },
      "ac2-close-modal-capture": {
        "action": "ui.screenshot",
        "label": "Close review: margin with PnL, fee, receive",
        "intent": "Keep the close review rows for visual review",
        "next": "ac2-close-reconciles"
      },
      "ac2-close-modal-dismiss": {
        "action": "ui.key_press",
        "key": "Escape",
        "intent": "Leave the close review without closing",
        "next": "ac2-close-modal-gone"
      },
      "ac2-close-modal-gone": {
        "action": "ui.wait_for",
        "test_id": "perps-close-position-modal",
        "expected": "absent",
        "timeout_ms": 5000,
        "intent": "The close review is dismissed",
        "next": "ac2-close-all-home"
      },
      "ac2-close-all-home": {
        "action": "ui.navigate",
        "page": "perps",
        "intent": "Show every open position on Perps home",
        "next": "ac2-close-all-open"
      },
      "ac2-close-all-open": {
        "action": "ui.press",
        "test_id": "perps-close-all-positions",
        "intent": "Review closing all positions without confirming",
        "next": "ac2-close-all-receive"
      },
      "ac2-close-all-receive": {
        "action": "ui.wait_for",
        "test_id": "perps-close-all-receive-value",
        "expected": "visible",
        "timeout_ms": 15000,
        "intent": "Close all shows a signed total you will receive",
        "next": "ac2-close-all-margin"
      },
      "ac2-close-all-margin": {
        "action": "ui.wait_for",
        "test_id": "perps-close-all-total-margin-value",
        "expected": "visible",
        "timeout_ms": 5000,
        "intent": "Close all shows the total margin with PnL",
        "next": "ac2-close-all-figures",
        "proves": [
          "AC-2"
        ]
      },
      "ac2-close-all-capture": {
        "action": "ui.screenshot",
        "label": "Close all review: margin with PnL, fees, receive",
        "intent": "Keep the close-all review rows for visual review",
        "next": "ac2-close-all-reconciles"
      },
      "ac2-close-all-keep": {
        "action": "ui.press",
        "test_id": "perps-close-all-positions-modal-cancel",
        "intent": "Keep every position open",
        "next": "ac2-close-all-gone"
      },
      "ac2-close-all-gone": {
        "action": "ui.wait_for",
        "test_id": "perps-close-all-positions-modal",
        "expected": "absent",
        "timeout_ms": 5000,
        "intent": "Close all is dismissed and nothing was closed",
        "next": "ac2-reopen-market"
      },
      "ac2-reopen-market": {
        "action": "ui.navigate",
        "page": "perps-market",
        "market": "{{params.market}}",
        "intent": "Return to the owned position",
        "next": "ac2-close-review"
      },
      "ac2-venue-position": {
        "action": "metamask.perps.read_snapshot",
        "include": [
          "positions"
        ],
        "market": "{{params.market}}",
        "intent": "Read the venue margin and PnL of the owned position before reviewing its close",
        "next": "ac2-open-close-modal"
      },
      "ac2-close-reconciles": {
        "action": "assert_output",
        "source": "ac2-close-modal-figures",
        "stream": "stdout",
        "contains": "RECONCILED=true",
        "intent": "Margin with PnL minus fee equals what you receive, to the cent",
        "next": "ac2-close-modal-dismiss",
        "proves": [
          "AC-2"
        ]
      },
      "ac3-market-activity": {
        "action": "ui.navigate",
        "page": "perps-market",
        "market": "{{params.market}}",
        "intent": "Show the market's trade activity after the close",
        "next": "ac3-owned-close"
      },
      "ac3-amount": {
        "action": "command",
        "intent": "Take the owned close's net PnL as shown in market activity",
        "next": "ac3-market-capture"
      },
      "ac3-market-capture": {
        "action": "ui.screenshot",
        "label": "Market activity: owned close net PnL",
        "intent": "Keep the market activity row for visual review",
        "next": "ac3-recent-home"
      },
      "ac3-recent-home": {
        "action": "ui.navigate",
        "page": "perps",
        "intent": "Show recent activity on Perps home",
        "next": "ac3-recent-row"
      },
      "ac3-recent-row": {
        "action": "ui.wait_for",
        "selector": "[data-testid^=\"transaction-card-{{outputs.ac3-owned-close.stdout}}-\"]",
        "expected": "present",
        "timeout_ms": 30000,
        "intent": "The owned close appears in recent activity",
        "next": "ac3-recent-scroll"
      },
      "ac3-recent-scroll": {
        "action": "ui.scroll",
        "selector": "[data-testid^=\"transaction-card-{{outputs.ac3-owned-close.stdout}}-\"]",
        "scroll_into_view": true,
        "intent": "The owned close row is on screen in recent activity",
        "next": "ac3-recent-visible"
      },
      "ac3-recent-capture": {
        "action": "ui.screenshot",
        "label": "Recent activity: owned close net PnL",
        "intent": "Keep the recent activity row for visual review",
        "next": "ac3-recent-matches"
      },
      "ac3-open-details": {
        "action": "ui.press",
        "selector": "[data-testid^=\"transaction-card-{{outputs.ac3-owned-close.stdout}}-\"]",
        "intent": "Open the owned close's transaction details",
        "next": "ac3-details-pnl"
      },
      "ac3-details-pnl": {
        "action": "ui.wait_for",
        "selector": "[data-testid=\"perps-transaction-details-page\"]",
        "expected": "visible",
        "timeout_ms": 15000,
        "intent": "The owned close transaction details are showing",
        "next": "ac3-details-figures",
        "proves": [
          "AC-3"
        ]
      },
      "ac3-details-capture": {
        "action": "ui.screenshot",
        "label": "Transaction details: owned close net PnL",
        "intent": "Keep the transaction details for visual review",
        "next": "ac3-details-matches"
      },
      "ac1-order-entry": {
        "action": "call",
        "ref": "perps.open-order-entry",
        "params": {
          "market": "{{params.market}}",
          "side": "{{params.side}}"
        },
        "intent": "Open a new long order entry on the now flat market",
        "next": "ac1-auto-close-on"
      },
      "ac1-auto-close-on": {
        "action": "ui.press",
        "selector": "label:has([data-testid=\"auto-close-toggle\"])",
        "intent": "Turn auto close on",
        "next": "ac1-sl-percent-ready"
      },
      "ac1-sl-percent-ready": {
        "action": "ui.wait_for",
        "selector": "[data-testid=\"sl-percent-input\"] input",
        "expected": "visible",
        "timeout_ms": 5000,
        "intent": "The stop-loss RoE field is showing",
        "next": "ac1-sl-roe"
      },
      "ac1-sl-roe": {
        "action": "ui.set_input",
        "selector": "[data-testid=\"sl-percent-input\"] input",
        "value": "{{params.stop_loss_roe}}",
        "intent": "Ask for a stop loss beyond the largest loss the venue allows",
        "next": "ac1-read-sl"
      },
      "ac1-read-sl": {
        "action": "metamask.perps.read_visible_state",
        "surface": "order-entry",
        "market": "{{params.market}}",
        "auto_close": true,
        "max_items": 150,
        "intent": "Read the stop-loss price the form derived from the RoE",
        "next": "ac1-sl-scroll"
      },
      "ac1-sl-capture": {
        "action": "ui.screenshot",
        "label": "Order entry: capped stop-loss price",
        "intent": "Keep the derived stop-loss price for visual review",
        "next": "ac1-auto-close-off"
      },
      "ac1-auto-close-off": {
        "action": "call",
        "ref": "perps.disable-auto-close",
        "params": {
          "market": "{{params.market}}"
        },
        "intent": "Leave the order entry without auto close prices in its draft",
        "next": "ac1-sl-roe-cap"
      },
      "ac1-sl-roe-cap": {
        "action": "assert_output",
        "source": "ac1-read-sl",
        "assert": {
          "path": "$.orderEntry.stopLossPrice",
          "operator": "matches",
          "value": "^\\$?([1-9][0-9,]*(\\.[0-9]+)?|0\\.[0-9]*[1-9][0-9]*)$"
        },
        "intent": "A stop loss beyond the cap gets Core's capped trigger price instead of none",
        "next": "ac2-cross-margin-includes-pnl",
        "proves": [
          "AC-1"
        ]
      },
      "ac2-cross-margin-includes-pnl": {
        "action": "assert_output",
        "source": "ac2-close-modal-figures",
        "stream": "stdout",
        "contains": "MARGIN_INCLUDES_PNL=true",
        "intent": "The close review margin matches the venue margin with its PnL for this margin mode",
        "next": "ac1-core-version-read",
        "proves": [
          "AC-2"
        ]
      },
      "ac1-core-version-read": {
        "action": "command",
        "cmd": "node -p \"require('@metamask/perps-controller/package.json').version\"",
        "intent": "Read the perps-controller version the build resolves",
        "next": "ac1-core-version"
      },
      "ac1-core-version": {
        "action": "assert_output",
        "source": "ac1-core-version-read",
        "stream": "stdout",
        "match": "^22\\.3\\.",
        "intent": "The build depends on perps-controller 22.3",
        "next": "x-open",
        "proves": [
          "AC-1"
        ]
      },
      "done": {
        "action": "end",
        "status": "pass"
      },
      "teardown-dismiss": {
        "action": "ui.key_press",
        "key": "Escape",
        "intent": "Dismiss any open review before cleanup",
        "next": "teardown-owned"
      },
      "teardown-read": {
        "action": "metamask.perps.read_positions",
        "market": "{{params.market}}",
        "network": "{{params.network}}",
        "intent": "Inspect the owned market even after a failure",
        "next": "teardown-choose"
      },
      "teardown-choose": {
        "action": "switch",
        "value": "positions:{{outputs.teardown-read.matchingCount}}",
        "equals": "positions:0",
        "cases": {
          "match": "teardown-flat"
        },
        "default": "teardown-market",
        "intent": "Close only when the owned position is still open"
      },
      "teardown-market": {
        "action": "ui.navigate",
        "page": "perps-market",
        "market": "{{params.market}}",
        "intent": "Show the owned position for cleanup",
        "next": "teardown-review"
      },
      "teardown-flat": {
        "action": "metamask.perps.assert_positions",
        "market": "{{params.market}}",
        "state": "none",
        "intent": "The owned market ends flat",
        "next": "teardown-done",
        "timeout_ms": 45000
      },
      "teardown-done": {
        "action": "end",
        "status": "pass"
      },
      "ac2-close-modal-figures": {
        "action": "command",
        "intent": "Read the close review rows once the fee quote has loaded",
        "next": "ac2-close-modal-capture"
      },
      "ac2-close-all-figures": {
        "action": "command",
        "intent": "Read the close-all rows once every fee quote has loaded",
        "next": "ac2-close-all-capture"
      },
      "ac2-close-all-reconciles": {
        "action": "assert_output",
        "source": "ac2-close-all-figures",
        "stream": "stdout",
        "contains": "RECONCILED=true",
        "intent": "Close all margin with PnL minus fees equals what you receive, to the cent",
        "next": "ac2-close-all-keep",
        "proves": [
          "AC-2"
        ]
      },
      "ac3-recent-figures": {
        "action": "command",
        "intent": "Read the owned close row in recent activity",
        "next": "ac3-recent-capture"
      },
      "ac3-recent-matches": {
        "action": "assert_output",
        "source": "ac3-recent-figures",
        "stream": "stdout",
        "contains": "RECENT_MATCHES=true",
        "intent": "Recent activity shows the same net PnL as market activity",
        "next": "ac3-open-details",
        "proves": [
          "AC-3"
        ]
      },
      "ac3-details-figures": {
        "action": "command",
        "intent": "Read the net PnL the transaction details show",
        "next": "ac3-details-capture"
      },
      "ac3-details-matches": {
        "action": "assert_output",
        "source": "ac3-details-figures",
        "stream": "stdout",
        "contains": "DETAILS_MATCHES=true",
        "intent": "Transaction details show the same net PnL as the activity rows",
        "next": "ac1-order-entry",
        "proves": [
          "AC-3"
        ]
      },
      "ac1-sl-scroll": {
        "action": "ui.scroll",
        "selector": "[data-testid=\"sl-price-input\"]",
        "scroll_into_view": true,
        "intent": "The stop-loss price and RoE fields are on screen for review",
        "next": "ac1-sl-visible"
      },
      "setup-run-start": {
        "action": "command",
        "cmd": "node -e \"process.stdout.write(String(Date.now()))\"",
        "intent": "Stamp the run start so only this run's close counts as owned",
        "next": "setup-open"
      },
      "ac2-close-review": {
        "action": "ui.press",
        "test_id": "perps-close-cta-button",
        "intent": "Review closing the owned position",
        "next": "ac2-close-ready"
      },
      "ac2-close-ready": {
        "action": "ui.wait_for",
        "selector": "[data-testid=\"perps-close-position-modal-submit\"]:not([disabled])",
        "expected": "visible",
        "timeout_ms": 15000,
        "intent": "Close is enabled once the fee is quoted",
        "next": "ac2-close-submit"
      },
      "ac2-close-submit": {
        "action": "ui.press",
        "test_id": "perps-close-position-modal-submit",
        "intent": "Close the owned position at market",
        "next": "ac2-closed"
      },
      "ac2-closed": {
        "action": "metamask.perps.assert_positions",
        "market": "{{params.market}}",
        "state": "none",
        "timeout_ms": 45000,
        "intent": "The owned market is flat after the close",
        "next": "ac3-market-activity"
      },
      "ac3-owned-close": {
        "action": "command",
        "intent": "Find this run's close at the top of the market activity",
        "next": "ac3-close-row"
      },
      "ac3-close-row": {
        "action": "ui.wait_for",
        "selector": "[data-testid^=\"transaction-card-{{outputs.ac3-owned-close.stdout}}-\"]",
        "expected": "present",
        "timeout_ms": 10000,
        "intent": "The owned close shows in the market activity",
        "next": "ac3-close-row-scroll"
      },
      "teardown-review": {
        "action": "ui.press",
        "test_id": "perps-close-cta-button",
        "intent": "Review closing the position this run opened",
        "next": "teardown-ready"
      },
      "teardown-ready": {
        "action": "ui.wait_for",
        "selector": "[data-testid=\"perps-close-position-modal-submit\"]:not([disabled])",
        "expected": "visible",
        "timeout_ms": 15000,
        "intent": "Close is enabled once the fee is quoted",
        "next": "teardown-submit"
      },
      "teardown-submit": {
        "action": "ui.press",
        "test_id": "perps-close-position-modal-submit",
        "intent": "Close only the position this run opened",
        "next": "teardown-flat"
      },
      "teardown-owned": {
        "action": "switch",
        "value": "opened:{{outputs.setup-open.action}}",
        "equals": "opened:metamask.perps.place_order",
        "cases": {
          "match": "teardown-read"
        },
        "default": "teardown-done",
        "intent": "Clean up only when this run placed its own order"
      },
      "ac3-close-row-scroll": {
        "action": "ui.scroll",
        "selector": "[data-testid^=\"transaction-card-{{outputs.ac3-owned-close.stdout}}-\"]",
        "scroll_into_view": true,
        "intent": "The owned close row is on screen for review",
        "next": "ac3-close-row-visible"
      },
      "ac3-close-row-visible": {
        "action": "ui.wait_for",
        "selector": "[data-testid^=\"transaction-card-{{outputs.ac3-owned-close.stdout}}-\"]",
        "visible": true,
        "timeout_ms": 5000,
        "intent": "The owned close row is visible before the market activity capture",
        "next": "ac3-amount",
        "proves": [
          "AC-3"
        ]
      },
      "ac3-recent-visible": {
        "action": "ui.wait_for",
        "selector": "[data-testid^=\"transaction-card-{{outputs.ac3-owned-close.stdout}}-\"]",
        "visible": true,
        "timeout_ms": 5000,
        "intent": "The owned close row is visible before the recent activity capture",
        "next": "ac3-recent-figures"
      },
      "ac1-sl-visible": {
        "action": "ui.wait_for",
        "selector": "[data-testid=\"sl-price-input\"]",
        "visible": true,
        "timeout_ms": 5000,
        "intent": "The stop-loss price is visible before the capture",
        "next": "ac1-sl-capture"
      },
      "x-open": {
        "action": "command",
        "intent": "Open one smallest owned cross long on testnet (the order form has no margin-mode control)",
        "next": "x-opened"
      },
      "x-opened": {
        "action": "assert_output",
        "source": "x-open",
        "stream": "stdout",
        "contains": "OPENED=true",
        "intent": "The owned cross long was placed",
        "next": "x-position-open"
      },
      "x-position-open": {
        "action": "metamask.perps.assert_positions",
        "market": "{{params.market}}",
        "side": "long",
        "state": "open",
        "timeout_ms": 30000,
        "intent": "The owned cross long is open",
        "next": "x-market"
      },
      "x-market": {
        "action": "ui.navigate",
        "page": "perps-market",
        "market": "{{params.market}}",
        "intent": "Show the owned cross position on its market page",
        "next": "x-venue"
      },
      "x-venue": {
        "action": "metamask.perps.read_snapshot",
        "include": [
          "positions"
        ],
        "market": "{{params.market}}",
        "intent": "Read the venue margin, PnL and margin mode of the owned position",
        "next": "x-is-cross"
      },
      "x-is-cross": {
        "action": "assert_output",
        "source": "x-venue",
        "assert": {
          "path": "$.sections.positions.items.0.leverage.type",
          "operator": "matches",
          "value": "^cross$"
        },
        "intent": "The owned position is on cross margin",
        "proves": [
          "AC-2"
        ],
        "next": "x-review"
      },
      "x-review": {
        "action": "ui.press",
        "test_id": "perps-close-cta-button",
        "intent": "Review closing the cross position",
        "next": "x-receive"
      },
      "x-receive": {
        "action": "ui.wait_for",
        "test_id": "perps-close-summary-receive-value",
        "expected": "visible",
        "timeout_ms": 15000,
        "intent": "The cross close review shows what you will receive",
        "proves": [
          "AC-2"
        ],
        "next": "x-fees"
      },
      "x-fees": {
        "action": "ui.wait_for",
        "test_id": "perps-close-summary-fees-value",
        "expected": "visible",
        "timeout_ms": 15000,
        "intent": "The cross close review shows the quoted fee",
        "next": "x-figures"
      },
      "x-figures": {
        "action": "command",
        "intent": "Read the cross close review rows once the fee quote has loaded",
        "next": "x-capture"
      },
      "x-capture": {
        "action": "ui.screenshot",
        "label": "Cross close review: margin with PnL, fee, receive",
        "intent": "Keep the cross close review for visual review",
        "next": "x-reconciles"
      },
      "x-reconciles": {
        "action": "assert_output",
        "source": "x-figures",
        "stream": "stdout",
        "contains": "RECONCILED=true",
        "intent": "Cross: margin with PnL minus fee equals what you receive, to the cent",
        "proves": [
          "AC-2"
        ],
        "next": "x-margin-pnl"
      },
      "x-margin-pnl": {
        "action": "assert_output",
        "source": "x-figures",
        "stream": "stdout",
        "contains": "MARGIN_INCLUDES_PNL=true",
        "intent": "Cross: the margin row adds the PnL to the venue margin",
        "proves": [
          "AC-2"
        ],
        "next": "p-percent-mode"
      },
      "p-percent-mode": {
        "action": "ui.press",
        "test_id": "close-amount-mode-percent",
        "intent": "Enter the close amount as a percentage",
        "next": "p-percent"
      },
      "p-percent": {
        "action": "ui.set_input",
        "selector": "[data-testid=\"close-amount-percent\"] input",
        "value": "50",
        "intent": "Close half of the cross position",
        "next": "p-percent-ready"
      },
      "p-percent-ready": {
        "action": "ui.wait_for",
        "test_id": "close-amount-slider-pct-50",
        "expected": "present",
        "timeout_ms": 5000,
        "intent": "The slider reflects a 50% close",
        "next": "p-fraction"
      },
      "p-figures": {
        "action": "command",
        "intent": "Read the partial close review rows",
        "next": "p-capture"
      },
      "p-capture": {
        "action": "ui.screenshot",
        "label": "Partial close review (50%): margin with PnL, fee, receive",
        "intent": "Keep the partial close review for visual review",
        "next": "p-reconciles"
      },
      "p-reconciles": {
        "action": "assert_output",
        "source": "p-figures",
        "stream": "stdout",
        "contains": "RECONCILED=true",
        "intent": "Partial: margin with PnL minus fee equals what you receive, to the cent",
        "proves": [
          "AC-2"
        ],
        "next": "p-margin-half"
      },
      "p-margin-half": {
        "action": "assert_output",
        "source": "p-figures",
        "stream": "stdout",
        "contains": "MARGIN_INCLUDES_PNL=true",
        "intent": "Partial: the margin row is the submitted share of the venue margin with PnL",
        "proves": [
          "AC-2"
        ],
        "next": "p-ready"
      },
      "p-ready": {
        "action": "ui.wait_for",
        "selector": "[data-testid=\"perps-close-position-modal-submit\"]:not([disabled])",
        "expected": "visible",
        "timeout_ms": 15000,
        "intent": "Close is enabled once the partial fee is quoted",
        "next": "p-submit"
      },
      "p-submit": {
        "action": "ui.press",
        "test_id": "perps-close-position-modal-submit",
        "intent": "Close half of the cross position at market",
        "next": "p-half"
      },
      "p-half": {
        "action": "command",
        "intent": "Wait for the venue to report the size the partial close leaves open",
        "next": "p-half-left"
      },
      "p-half-left": {
        "action": "assert_output",
        "source": "p-half",
        "stream": "stdout",
        "contains": "SIZE_MATCHES=true",
        "intent": "The partial close submitted the size the review quoted",
        "proves": [
          "AC-2"
        ],
        "next": "l-venue"
      },
      "l-venue": {
        "action": "metamask.perps.read_snapshot",
        "include": [
          "positions"
        ],
        "market": "{{params.market}}",
        "intent": "Read the remaining cross position before its limit close",
        "next": "l-review"
      },
      "l-review": {
        "action": "ui.press",
        "test_id": "perps-close-cta-button",
        "intent": "Review closing the remaining position",
        "next": "l-type"
      },
      "l-type": {
        "action": "ui.press",
        "test_id": "order-type-limit",
        "intent": "Close with a limit order",
        "next": "l-price-calc"
      },
      "l-price-calc": {
        "action": "command",
        "intent": "Pick a sell limit 1% under the live price so the close fills",
        "next": "l-price"
      },
      "l-price": {
        "action": "ui.set_input",
        "selector": "[data-testid=\"limit-price-input\"] input",
        "value": "{{outputs.l-price-calc.stdout}}",
        "intent": "Enter the limit close price",
        "next": "l-pnl"
      },
      "l-pnl": {
        "action": "command",
        "intent": "PnL of the remaining long closed at the limit price",
        "next": "l-fees"
      },
      "l-fees": {
        "action": "ui.wait_for",
        "test_id": "perps-close-summary-fees-value",
        "expected": "visible",
        "timeout_ms": 15000,
        "intent": "The limit close review shows the quoted fee",
        "next": "l-figures"
      },
      "l-figures": {
        "action": "command",
        "intent": "Read the limit close review rows, priced at the limit",
        "next": "l-capture"
      },
      "l-capture": {
        "action": "ui.screenshot",
        "label": "Limit close review: margin with PnL at the limit price, fee, receive",
        "intent": "Keep the limit close review for visual review",
        "next": "l-reconciles"
      },
      "l-reconciles": {
        "action": "assert_output",
        "source": "l-figures",
        "stream": "stdout",
        "contains": "RECONCILED=true",
        "intent": "Limit: margin with PnL minus fee equals what you receive, to the cent",
        "proves": [
          "AC-2"
        ],
        "next": "l-margin-pnl"
      },
      "l-margin-pnl": {
        "action": "assert_output",
        "source": "l-figures",
        "stream": "stdout",
        "contains": "MARGIN_INCLUDES_PNL=true",
        "intent": "Limit: the margin row adds the PnL priced at the limit",
        "proves": [
          "AC-2"
        ],
        "next": "l-ready"
      },
      "l-ready": {
        "action": "ui.wait_for",
        "selector": "[data-testid=\"perps-close-position-modal-submit\"]:not([disabled])",
        "expected": "visible",
        "timeout_ms": 15000,
        "intent": "Close is enabled once the limit fee is quoted",
        "next": "l-submit"
      },
      "l-submit": {
        "action": "ui.press",
        "test_id": "perps-close-position-modal-submit",
        "intent": "Place the limit close",
        "next": "l-flat"
      },
      "l-flat": {
        "action": "metamask.perps.assert_positions",
        "market": "{{params.market}}",
        "state": "none",
        "timeout_ms": 45000,
        "intent": "The limit close filled and the owned market is flat",
        "next": "done"
      },
      "p-fraction": {
        "action": "command",
        "intent": "Share of the position a 50% close submits after size-step rounding",
        "next": "p-remaining"
      },
      "p-remaining": {
        "action": "command",
        "intent": "Size left open after the 50% close",
        "next": "p-figures"
      }
    },
    "teardown": "teardown-dismiss"
  }
}

Validation Logs

Run: 3e1bf21c-67f9-45cf-8853-4f5c47d848ac

Runner: @metamask/recipe-runner

Runner revision: unknown

Recipe digest: sha256:1ce3b0e371fd75d7b04b22799d206c91a9262c905a68a22cab0074fae25cacf5

Full output (123/123 passed, pass)
# MetaMask Recipe Run

Status: pass
Duration: 79s
Nodes: 123/123 passed

## Side findings
- REVIEW 1 distinct application warning/error event(s) (non-blocking; expanded below and stored in diagnostics.json)

## Steps
- PASS setup-unlock (metamask.wallet.ensure_unlocked, 220ms): proof=extension-unlocked-state
- PASS setup-perps (ui.navigate, 1.5s): page=perps, proof=ui-navigation
- PASS setup-tutorial-clear (ui.wait_for, 637ms): matched=true, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps-home, cdpTargetId=7E986C920B11ED065CD19466D8D8B705, runtimeSessionId=5764c980-fa7b-46b5-8f62-513d9fb05053
- PASS setup-testnet (metamask.perps.start_state, 247ms): action=metamask.perps.start_state, phase=start_state
- PASS setup-flat-positions (metamask.perps.assert_positions, 224ms): matching=0
- PASS setup-flat-orders (metamask.perps.assert_orders, 220ms): matching=0
- PASS setup-run-start (command, 62ms): exitCode=0, stdout=1794248260694
- PASS setup-open (metamask.perps.place_order, 9.9s): matching=1
- PASS setup-position-open (metamask.perps.assert_positions, 234ms): matching=1
- PASS ac2-open-market (ui.navigate, 1.2s): page=perps-market, proof=ui-navigation
- PASS ac2-venue-position (metamask.perps.read_snapshot, 237ms): proof=background-perps-snapshot
- PASS ac2-open-close-modal (ui.press, 962ms): clicked=true, selector=[data-testid="perps-close-cta-button"], [data-test-id="perps-close-cta-button"], [data-test="perps-close-cta-button"], tagName=BUTTON, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/market/SOL
- PASS ac2-close-modal-receive (ui.wait_for, 585ms): matched=true, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/market/SOL, cdpTargetId=7E986C920B11ED065CD19466D8D8B705, runtimeSessionId=5764c980-fa7b-46b5-8f62-513d9fb05053
- PASS ac2-close-modal-fees (ui.wait_for, 608ms): matched=true, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/market/SOL, cdpTargetId=7E986C920B11ED065CD19466D8D8B705, runtimeSessionId=5764c980-fa7b-46b5-8f62-513d9fb05053
- PASS ac2-close-modal-figures (command, 82ms): exitCode=0, stdout={"margin":"$11.22","fees":"$0.02","receive":"$11.20","expectedMargin":11.218504,"marginDeltaCents":0,"leverageType":"isolated"}
RECONCILED=true
MARGIN_INCLUDES_PNL=true

- PASS ac2-close-reconciles (assert_output, 49ms): source=ac2-close-modal-figures, stream=stdout, contains=RECONCILED=true
- PASS ac2-close-modal-dismiss (ui.key_press, 508ms): key=Escape, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/market/SOL, cdpTargetId=7E986C920B11ED065CD19466D8D8B705, runtimeSessionId=5764c980-fa7b-46b5-8f62-513d9fb05053
- PASS ac2-close-modal-gone (ui.wait_for, 810ms): matched=true, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/market/SOL, cdpTargetId=7E986C920B11ED065CD19466D8D8B705, runtimeSessionId=5764c980-fa7b-46b5-8f62-513d9fb05053
- PASS ac2-close-all-home (ui.navigate, 1.5s): page=perps, proof=ui-navigation
- PASS ac2-close-all-open (ui.press, 876ms): clicked=true, selector=[data-testid="perps-close-all-positions"], [data-test-id="perps-close-all-positions"], [data-test="perps-close-all-positions"], tagName=BUTTON, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps-home
- PASS ac2-close-all-receive (ui.wait_for, 594ms): matched=true, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps-home, cdpTargetId=7E986C920B11ED065CD19466D8D8B705, runtimeSessionId=5764c980-fa7b-46b5-8f62-513d9fb05053
- PASS ac2-close-all-margin (ui.wait_for, 579ms): matched=true, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps-home, cdpTargetId=7E986C920B11ED065CD19466D8D8B705, runtimeSessionId=5764c980-fa7b-46b5-8f62-513d9fb05053
- PASS ac2-close-all-figures (command, 375ms): exitCode=0, stdout={"margin":"$11.21","fees":"-$0.02","receive":"$11.19"}
RECONCILED=true

- PASS ac2-close-all-reconciles (assert_output, 50ms): source=ac2-close-all-figures, stream=stdout, contains=RECONCILED=true
- PASS ac2-close-all-keep (ui.press, 991ms): clicked=true, selector=[data-testid="perps-close-all-positions-modal-cancel"], [data-test-id="perps-close-all-positions-modal-cancel"], [data-test="perps-close-all-positions-modal-cancel"], tagName=BUTTON, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps-home
- PASS ac2-close-all-gone (ui.wait_for, 588ms): matched=true, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps-home, cdpTargetId=7E986C920B11ED065CD19466D8D8B705, runtimeSessionId=5764c980-fa7b-46b5-8f62-513d9fb05053
- PASS ac2-reopen-market (ui.navigate, 1.2s): page=perps-market, proof=ui-navigation
- PASS ac2-close-review (ui.press, 714ms): clicked=true, selector=[data-testid="perps-close-cta-button"], [data-test-id="perps-close-cta-button"], [data-test="perps-close-cta-button"], tagName=BUTTON, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/market/SOL
- PASS ac2-close-ready (ui.wait_for, 635ms): matched=true, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/market/SOL, cdpTargetId=7E986C920B11ED065CD19466D8D8B705, runtimeSessionId=5764c980-fa7b-46b5-8f62-513d9fb05053
- PASS ac2-close-submit (ui.press, 757ms): clicked=true, selector=[data-testid="perps-close-position-modal-submit"], [data-test-id="perps-close-position-modal-submit"], [data-test="perps-close-position-modal-submit"], tagName=BUTTON, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/market/SOL
- PASS ac2-closed (metamask.perps.assert_positions, 757ms): matching=0
- PASS ac3-market-activity (ui.navigate, 1.6s): page=perps-market, proof=ui-navigation
- PASS ac3-owned-close (command, 92ms): exitCode=0, stdout=64933273724
- PASS ac3-close-row (ui.wait_for, 593ms): matched=true, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/market/SOL, cdpTargetId=7E986C920B11ED065CD19466D8D8B705, runtimeSessionId=5764c980-fa7b-46b5-8f62-513d9fb05053
- PASS ac3-close-row-scroll (ui.scroll, 798ms): scrolled=true, selector=[data-testid^="transaction-card-64933273724-"], intoView=true, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/market/SOL
- PASS ac3-close-row-visible (ui.wait_for, 769ms): matched=true, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/market/SOL, cdpTargetId=7E986C920B11ED065CD19466D8D8B705, runtimeSessionId=5764c980-fa7b-46b5-8f62-513d9fb05053
- PASS ac3-amount (command, 84ms): exitCode=0, stdout=-$0.02
- PASS ac3-recent-home (ui.navigate, 1.5s): page=perps, proof=ui-navigation
- PASS ac3-recent-row (ui.wait_for, 614ms): matched=true, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps-home, cdpTargetId=7E986C920B11ED065CD19466D8D8B705, runtimeSessionId=5764c980-fa7b-46b5-8f62-513d9fb05053
- PASS ac3-recent-scroll (ui.scroll, 679ms): scrolled=true, selector=[data-testid^="transaction-card-64933273724-"], intoView=true, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps-home
- PASS ac3-recent-visible (ui.wait_for, 583ms): matched=true, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps-home, cdpTargetId=7E986C920B11ED065CD19466D8D8B705, runtimeSessionId=5764c980-fa7b-46b5-8f62-513d9fb05053
- PASS ac3-recent-figures (command, 86ms): exitCode=0, stdout={"text":"Closed long 0.13 SOL -$0.02","amount":"-$0.02"}
RECENT_MATCHES=true

- PASS ac3-recent-matches (assert_output, 51ms): source=ac3-recent-figures, stream=stdout, contains=RECENT_MATCHES=true
- PASS ac3-open-details (ui.press, 526ms): clicked=true, selector=[data-testid^="transaction-card-64933273724-"], tagName=BUTTON, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps-home
- PASS ac3-details-pnl (ui.wait_for, 576ms): matched=true, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/transaction-details, cdpTargetId=7E986C920B11ED065CD19466D8D8B705, runtimeSessionId=5764c980-fa7b-46b5-8f62-513d9fb05053
- PASS ac3-details-figures (command, 89ms): exitCode=0, stdout={"pnl":"-$0.02","amount":"-$0.02"}
DETAILS_MATCHES=true

- PASS ac3-details-matches (assert_output, 50ms): source=ac3-details-figures, stream=stdout, contains=DETAILS_MATCHES=true
- PASS ac1-order-entry/open-perps (ui.navigate, 1.5s): page=perps, proof=ui-navigation
- PASS ac1-order-entry/tutorial-clear (ui.wait_for, 596ms): matched=true, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps-home, cdpTargetId=7E986C920B11ED065CD19466D8D8B705, runtimeSessionId=5764c980-fa7b-46b5-8f62-513d9fb05053
- PASS ac1-order-entry/open-market-list (ui.navigate, 1.3s): page=perps-market-list, proof=ui-navigation
- PASS ac1-order-entry/open-market (ui.navigate, 375ms): page=perps-market, proof=ui-navigation
- PASS ac1-order-entry/open-side (ui.press, 809ms): clicked=true, selector=[data-testid="perps-long-cta-button"], [data-test-id="perps-long-cta-button"], [data-test="perps-long-cta-button"], tagName=BUTTON, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/market/SOL
- PASS ac1-order-entry/wait-order-entry (ui.wait_for, 577ms): matched=true, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/trade/SOL?direction=long&mode=new, cdpTargetId=7E986C920B11ED065CD19466D8D8B705, runtimeSessionId=5764c980-fa7b-46b5-8f62-513d9fb05053
- PASS ac1-order-entry/done (end, 0ms)
- PASS ac1-order-entry (call, 5.5s): ref=perps.open-order-entry, status=pass
- PASS ac1-auto-close-on (ui.press, 930ms): clicked=true, selector=label:has([data-testid="auto-close-toggle"]), tagName=LABEL, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/trade/SOL?direction=long&mode=new
- PASS ac1-sl-percent-ready (ui.wait_for, 628ms): matched=true, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/trade/SOL?direction=long&mode=new, cdpTargetId=7E986C920B11ED065CD19466D8D8B705, runtimeSessionId=5764c980-fa7b-46b5-8f62-513d9fb05053
- PASS ac1-sl-roe (ui.set_input, 434ms): set=true, selector=[data-testid="sl-percent-input"] input, tagName=INPUT, previousLength=0, retainedLength=5
- PASS ac1-read-sl (metamask.perps.read_visible_state, 240ms): platform=extension, route=#/perps/trade/SOL?direction=long&mode=new, proof=visible-dom-accessibility
- PASS ac1-sl-scroll (ui.scroll, 864ms): scrolled=true, selector=[data-testid="sl-price-input"], intoView=true, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/trade/SOL?direction=long&mode=new
- PASS ac1-sl-visible (ui.wait_for, 580ms): matched=true, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/trade/SOL?direction=long&mode=new, cdpTargetId=7E986C920B11ED065CD19466D8D8B705, runtimeSessionId=5764c980-fa7b-46b5-8f62-513d9fb05053
- PASS ac1-auto-close-off/disable-auto-close (ui.press, 1.4s): clicked=true, selector=label:has([data-testid="auto-close-toggle"]), tagName=LABEL, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/trade/SOL?direction=long&mode=new
- PASS ac1-auto-close-off/read-cleared (metamask.perps.read_visible_state, 235ms): platform=extension, route=#/perps/trade/SOL?direction=long&mode=new, proof=visible-dom-accessibility
- PASS ac1-auto-close-off/cleared-proof (assert_output, 60ms): source=read-cleared, stream=stdout
- PASS ac1-auto-close-off/done (end, 0ms)
- PASS ac1-auto-close-off (call, 1.9s): ref=perps.disable-auto-close, status=pass
- PASS ac1-sl-roe-cap (assert_output, 50ms): source=ac1-read-sl, stream=stdout
- PASS ac2-cross-margin-includes-pnl (assert_output, 50ms): source=ac2-close-modal-figures, stream=stdout, contains=MARGIN_INCLUDES_PNL=true
- PASS ac1-core-version-read (command, 62ms): exitCode=0, stdout=22.3.0

- PASS ac1-core-version (assert_output, 48ms): source=ac1-core-version-read, stream=stdout, match=^22\.3\.
- PASS x-open (command, 2.3s): exitCode=0, stdout={"market":"SOL","size":"0.28","price":86.349}
OPENED=true

- PASS x-opened (assert_output, 74ms): source=x-open, stream=stdout, contains=OPENED=true
- PASS x-position-open (metamask.perps.assert_positions, 4.3s): matching=1
- PASS x-market (ui.navigate, 2.2s): page=perps-market, proof=ui-navigation
- PASS x-venue (metamask.perps.read_snapshot, 242ms): proof=background-perps-snapshot
- PASS x-is-cross (assert_output, 51ms): source=x-venue, stream=stdout
- PASS x-review (ui.press, 858ms): clicked=true, selector=[data-testid="perps-close-cta-button"], [data-test-id="perps-close-cta-button"], [data-test="perps-close-cta-button"], tagName=BUTTON, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/market/SOL
- PASS x-receive (ui.wait_for, 582ms): matched=true, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/market/SOL, cdpTargetId=7E986C920B11ED065CD19466D8D8B705, runtimeSessionId=5764c980-fa7b-46b5-8f62-513d9fb05053
- PASS x-fees (ui.wait_for, 876ms): matched=true, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/market/SOL, cdpTargetId=7E986C920B11ED065CD19466D8D8B705, runtimeSessionId=5764c980-fa7b-46b5-8f62-513d9fb05053
- PASS x-figures (command, 82ms): exitCode=0, stdout={"margin":"$25.03","fees":"$0.04","receive":"$24.99","expectedMargin":25.02497,"marginDeltaCents":1,"leverageType":"cross"}
RECONCILED=true
MARGIN_INCLUDES_PNL=true

- PASS x-reconciles (assert_output, 51ms): source=x-figures, stream=stdout, contains=RECONCILED=true
- PASS x-margin-pnl (assert_output, 52ms): source=x-figures, stream=stdout, contains=MARGIN_INCLUDES_PNL=true
- PASS p-percent-mode (ui.press, 514ms): clicked=true, selector=[data-testid="close-amount-mode-percent"], [data-test-id="close-amount-mode-percent"], [data-test="close-amount-mode-percent"], tagName=BUTTON, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/market/SOL
- PASS p-percent (ui.set_input, 468ms): set=true, selector=[data-testid="close-amount-percent"] input, tagName=INPUT, previousLength=3, retainedLength=2
- PASS p-percent-ready (ui.wait_for, 590ms): matched=true, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/market/SOL, cdpTargetId=7E986C920B11ED065CD19466D8D8B705, runtimeSessionId=5764c980-fa7b-46b5-8f62-513d9fb05053
- PASS p-fraction (command, 87ms): exitCode=0, stdout=0.48275862068965525
- PASS p-remaining (command, 88ms): exitCode=0, stdout=0.15
- PASS p-figures (command, 85ms): exitCode=0, stdout={"margin":"$12.09","fees":"$0.02","receive":"$12.07","expectedMargin":12.081020000000002,"marginDeltaCents":1,"leverageType":"cross"}
RECONCILED=true
MARGIN_INCLUDES_PNL=true

- PASS p-reconciles (assert_output, 64ms): source=p-figures, stream=stdout, contains=RECONCILED=true
- PASS p-margin-half (assert_output, 50ms): source=p-figures, stream=stdout, contains=MARGIN_INCLUDES_PNL=true
- PASS p-ready (ui.wait_for, 594ms): matched=true, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/market/SOL, cdpTargetId=7E986C920B11ED065CD19466D8D8B705, runtimeSessionId=5764c980-fa7b-46b5-8f62-513d9fb05053
- PASS p-submit (ui.press, 654ms): clicked=true, selector=[data-testid="perps-close-position-modal-submit"], [data-test-id="perps-close-position-modal-submit"], [data-test="perps-close-position-modal-submit"], tagName=BUTTON, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/market/SOL
- PASS p-half (command, 1.1s): exitCode=0, stdout={"market":"SOL","size":"0.15"}
SIZE_MATCHES=true

- PASS p-half-left (assert_output, 49ms): source=p-half, stream=stdout, contains=SIZE_MATCHES=true
- PASS l-venue (metamask.perps.read_snapshot, 233ms): proof=background-perps-snapshot
- PASS l-review (ui.press, 690ms): clicked=true, selector=[data-testid="perps-close-cta-button"], [data-test-id="perps-close-cta-button"], [data-test="perps-close-cta-button"], tagName=BUTTON, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/market/SOL
- PASS l-type (ui.press, 913ms): clicked=true, selector=[data-testid="order-type-limit"], [data-test-id="order-type-limit"], [data-test="order-type-limit"], tagName=BUTTON, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/market/SOL
- PASS l-price-calc (command, 190ms): exitCode=0, stdout=85.47
- PASS l-price (ui.set_input, 727ms): set=true, selector=[data-testid="limit-price-input"] input, tagName=INPUT, previousLength=0, retainedLength=5
- PASS l-pnl (command, 69ms): exitCode=0, stdout=-0.13725000000000093
- PASS l-fees (ui.wait_for, 768ms): matched=true, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/market/SOL, cdpTargetId=7E986C920B11ED065CD19466D8D8B705, runtimeSessionId=5764c980-fa7b-46b5-8f62-513d9fb05053
- PASS l-figures (command, 88ms): exitCode=0, stdout={"margin":"$12.81","fees":"$0.01","receive":"$12.80","expectedMargin":12.809249999999999,"marginDeltaCents":0,"leverageType":"cross"}
RECONCILED=true
MARGIN_INCLUDES_PNL=true

- PASS l-reconciles (assert_output, 51ms): source=l-figures, stream=stdout, contains=RECONCILED=true
- PASS l-margin-pnl (assert_output, 50ms): source=l-figures, stream=stdout, contains=MARGIN_INCLUDES_PNL=true
- PASS l-ready (ui.wait_for, 694ms): matched=true, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/market/SOL, cdpTargetId=7E986C920B11ED065CD19466D8D8B705, runtimeSessionId=5764c980-fa7b-46b5-8f62-513d9fb05053
- PASS l-submit (ui.press, 519ms): clicked=true, selector=[data-testid="perps-close-position-modal-submit"], [data-test-id="perps-close-position-modal-submit"], [data-test="perps-close-position-modal-submit"], tagName=BUTTON, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/market/SOL
- PASS l-flat (metamask.perps.assert_positions, 745ms): matching=0
- PASS done (end, 0ms)
- PASS teardown-dismiss (ui.key_press, 420ms): key=Escape, cdpPort=7666, targetUrl=chrome-extension://hebhblbkkdabgoldnojllkipeoacjioc/home.html#/perps/market/SOL, cdpTargetId=7E986C920B11ED065CD19466D8D8B705, runtimeSessionId=5764c980-fa7b-46b5-8f62-513d9fb05053
- PASS teardown-owned (switch, 37ms): matched=true, value=opened:metamask.perps.place_order, expected=opened:metamask.perps.place_order
- PASS teardown-read (metamask.perps.read_positions, 258ms): count=0, matching=0
- PASS teardown-choose (switch, 34ms): matched=true, value=positions:0, expected=positions:0
- PASS teardown-flat (metamask.perps.assert_positions, 228ms): matching=0
- PASS teardown-done (end, 0ms)

Pre-merge author checklist

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.

Note

Medium Risk
Touches user-visible Perps money figures and trading confirmations across close, activity, and TP/SL; behavior changes are intentional but regressions would misstate PnL or enable premature submits.

Overview
Bumps @metamask/perps-controller to 22.3.0 and routes Perps UI math through Core instead of Extension-local copies, so figures match mobile/terminal. LavaMoat policies only adjust @metamask/abi-utils after the dependency hoist.

Close / close-all now use estimateCloseReceive (via close-position-utils): margin-with-PnL at the live or limit price, cross vs isolated handling, cent rounding half away from zero, signed receive (including negatives), partial closes sized to the market step, and Close disabled until fees are quoted (-- placeholders).

Activity and transaction details use calculateNetClosedPnl and Core formatters; split-fill aggregation keeps net PnL unknown when any fee is missing, and details reuse the same display string as list rows (no double fee subtraction).

TP/SL (auto-close, edit modal, order entry) delegates RoE ↔ price, validation, caps, and formatting to Core (calculatePriceForRoE, validateTPSLPrices, etc.); local tpslValidation helpers shrink to i18n direction strings only.

The global Jest mock re-exports real Core calculation modules so unit tests assert against shared formulas.

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

…culations

Bump @metamask/perps-controller to ^22.3.0 and replace Extension's local
copies with Core's shared calculations:

- Close and close-all use estimateCloseReceive: cross margin keeps its
  PnL, PnL is priced at the close price, cents round half away from
  zero, receive stays signed, and an unquoted fee shows as unavailable
  with Close disabled until it is quoted.
- Activity and transaction details use calculateNetClosedPnl; an unknown
  fee stays unknown through fill aggregation and details show the same
  amount as activity.
- TP/SL RoE, trigger prices and validation use Core's helpers.

The LavaMoat policies follow the hoisted @metamask/abi-utils edge, and
the Jest stub re-exports Core's real calculation modules.
@github-actions

Copy link
Copy Markdown
Contributor

CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes.

@metamask-ci

metamask-ci Bot commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

✨ Files requiring CODEOWNER review ✨

👨‍🔧 @MetaMask/perps (21 files, +1289 -970)
  • 📁 ui/
    • 📁 components/
      • 📁 app/
        • 📁 perps/
          • 📁 close-position/
            • 📄 close-all-positions-modal.test.tsx +113 -23
            • 📄 close-all-positions-modal.tsx +151 -130
            • 📄 close-position-modal.test.tsx +245 -34
            • 📄 close-position-modal.tsx +145 -124
            • 📄 close-position-utils.test.ts +104 -0
            • 📄 close-position-utils.ts +98 -0
          • 📁 order-entry/
            • 📁 components/
              • 📁 auto-close-section/
                • 📄 auto-close-section.test.tsx +55 -5
                • 📄 auto-close-section.tsx +36 -69
          • 📁 update-tpsl/
            • 📄 update-tpsl-modal-content.test.tsx +78 -0
            • 📄 update-tpsl-modal-content.tsx +52 -78
          • 📁 utils/
            • 📄 index.ts +0 -2
            • 📄 tpslInput.ts +11 -0
            • 📄 tpslValidation.test.ts +0 -286
            • 📄 tpslValidation.ts +5 -101
            • 📄 transactionTransforms.test.ts +91 -0
            • 📄 transactionTransforms.ts +43 -22
            • 📄 utils.test.ts +0 -27
            • 📄 utils.ts +0 -21
    • 📁 pages/
      • 📁 perps/
        • 📄 perps-order-entry-page.tsx +13 -36
        • 📄 perps-transaction-details-page.test.tsx +43 -0
        • 📄 perps-transaction-details-page.tsx +6 -12

📜 @MetaMask/policy-reviewers (8 files, +8 -56)
  • 📁 lavamoat/
    • 📁 webpack/
      • 📁 mv2/
        • 📁 beta/
          • 📄 policy.json +1 -7
        • 📁 experimental/
          • 📄 policy.json +1 -7
        • 📁 flask/
          • 📄 policy.json +1 -7
        • 📁 main/
          • 📄 policy.json +1 -7
      • 📁 mv3/
        • 📁 beta/
          • 📄 policy.json +1 -7
        • 📁 experimental/
          • 📄 policy.json +1 -7
        • 📁 flask/
          • 📄 policy.json +1 -7
        • 📁 main/
          • 📄 policy.json +1 -7

Tip

Follow the policy review process outlined in the LavaMoat Policy Review Process doc before expecting an approval from Policy Reviewers.

@metamask-ci metamask-ci Bot added the team-perps Perps team label Nov 10, 2026
@socket-security

socket-security Bot commented Nov 10, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updated@​metamask/​perps-controller@​22.1.1 ⏵ 22.3.099 +1100100100 +1100

View full report

@abretonc7s abretonc7s changed the title chore: prepare farmslot publication pkg-6ae37c17-mw9l8m8l feat(perps): update to perps-controller 22.3.0 and use its shared calculations instead of Extension copies Nov 10, 2026
@abretonc7s
abretonc7s marked this pull request as ready for review November 10, 2026 01:07
@abretonc7s
abretonc7s requested review from a team as code owners November 10, 2026 01:07
isSubmitting ||
positions.length === 0 ||
hasInvalidNotional ||
totalFees === undefined;

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.

ClosePositionModal blocks confirmation when youWillReceive < 0 (shows perpsInsufficientMargin and keeps submit disabled). After this change, Close all can display a signed negative receive (e.g. the new cross-loss fixture) but isSubmitDisabled still only keys off fee quotes / notional — submit stays enabled.

Consider aligning the two flows: disable Close all (and surface the same insufficient-margin copy if you show inline errors) when totalReceive !== undefined && totalReceive < 0, or document why bulk close is allowed in that state.

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

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

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit f54a74f. Configure here.

Use formatPnl and formatPerpsFiat from @metamask/perps-controller instead of pre-rounding with toFixed before a local formatter copy. Rendered values are unchanged; the Jest mock exposes the real Core formatters.
@metamask-ci

metamask-ci Bot commented Nov 10, 2026

Copy link
Copy Markdown
Contributor
Builds ready [c5754dc]
⚡ Performance Benchmarks (Total: 🟢 8 pass · 🟡 3 warn · 🔴 2 fail)

Baseline (latest main): f693520 | Date: 11/3/2026 | Pipeline: 42734657850 | Baseline logs

Metricschrome-webpackfirefox-webpack
loadNewAccount
[Sentry log · main/release]
🔴 load_new_account(p95) [CI log]🔴 load_new_account(p95) [CI log]

Regressions (🔴 2 failures)

Interaction Benchmarks · Samples: 5 🔴 2
Benchmarkchrome-webpackfirefox-webpack
loadNewAccount
[Sentry log · main/release]
🔴 [CI log]
🔴 load_new_account
🔴 [CI log]
🔴 load_new_account
confirmTx
[Sentry log · main/release]
🟡 [CI log]🟢 [CI log]
bridgeUserActions
[Sentry log · main/release]
🟡 [CI log]🟢 [CI log]

📈 Results compared to the previous 5 runs on main

  • ↑ loadNewAccount/load_new_account: +185%
  • ↑ loadNewAccount/total: +185%
  • ↓ loadNewAccount/inp: -18%
  • ↑ loadNewAccount/fcp: +12%
  • ↑ loadNewAccount/lcp: +997%
  • ↑ loadNewAccount/cls: +583%
  • ↑ confirmTx/longTaskCount: +67%
  • ↑ confirmTx/longTaskTotalDuration: +77%
  • ↑ confirmTx/longTaskMaxDuration: +70%
  • ↑ confirmTx/tbt: +104%
  • ↓ confirmTx/inp: -13%
  • ↑ confirmTx/fcp: +14%
  • ↑ confirmTx/lcp: +1048%
  • ↑ bridgeUserActions/bridge_load_page: +134%
  • ↑ bridgeUserActions/bridge_load_asset_picker: +52%
  • ↑ bridgeUserActions/longTaskCount: +36%
  • ↑ bridgeUserActions/longTaskTotalDuration: +93%
  • ↑ bridgeUserActions/longTaskMaxDuration: +45%
  • ↑ bridgeUserActions/tbt: +185%
  • ↑ bridgeUserActions/total: +24%
  • ↑ bridgeUserActions/inp: +10%
  • ↑ bridgeUserActions/lcp: +961%
  • ↑ bridgeUserActions/cls: +634%
  • ↑ loadNewAccount/load_new_account: +182%
  • ↑ loadNewAccount/total: +182%
  • ↓ loadNewAccount/inp: -25%
  • ↓ loadNewAccount/fcp: -33%
  • ↑ loadNewAccount/lcp: +1258%
  • ↓ confirmTx/longTaskCount: -100%
  • ↓ confirmTx/longTaskTotalDuration: -100%
  • ↓ confirmTx/longTaskMaxDuration: -100%
  • ↓ confirmTx/tbt: -100%
  • ↓ confirmTx/inp: -13%
  • ↓ confirmTx/fcp: -34%
  • ↑ confirmTx/lcp: +1298%
  • ↑ bridgeUserActions/bridge_load_page: +244%
  • ↑ bridgeUserActions/bridge_load_asset_picker: +137%
  • ↑ bridgeUserActions/bridge_search_token: +20%
  • ↓ bridgeUserActions/longTaskCount: -100%
  • ↓ bridgeUserActions/longTaskTotalDuration: -100%
  • ↓ bridgeUserActions/longTaskMaxDuration: -100%
  • ↓ bridgeUserActions/tbt: -100%
  • ↑ bridgeUserActions/total: +55%
  • ↑ bridgeUserActions/inp: +69%
  • ↓ bridgeUserActions/fcp: -32%
  • ↑ bridgeUserActions/lcp: +1257%

🌐 Core Web Vitals — 🟢 good · 🟡 needs improvement · 🔴 poor (web.dev thresholds)

  • 🟡 loadNewAccount/FCP: p75 1.9s
  • 🟡 confirmTx/FCP: p75 2.0s
  • 🟡 bridgeUserActions/FCP: p75 1.9s
Startup Benchmarks · Samples: 100
Benchmarkchrome-webpackfirefox-webpack
startupStandardHome
[Sentry log · main/release]
🟢 [CI log]🟢 [CI log]

📈 Results compared to the previous 5 runs on main

  • ↑ startupStandardHome/uiStartup: +75%
  • ↑ startupStandardHome/load: +107%
  • ↑ startupStandardHome/domContentLoaded: +108%
  • ↑ startupStandardHome/domInteractive: +26%
  • ↓ startupStandardHome/firstPaint: -55%
  • ↓ startupStandardHome/firstReactRender: -98%
  • ↓ startupStandardHome/initialActions: -29%
  • ↑ startupStandardHome/loadScripts: +106%
  • ↑ startupStandardHome/setupStore: +95%
  • ↑ startupStandardHome/longTaskCount: +43%
  • ↑ startupStandardHome/longTaskTotalDuration: +106%
  • ↑ startupStandardHome/longTaskMaxDuration: +117%
  • ↑ startupStandardHome/tbt: +126%
  • ↓ startupStandardHome/fcp: -52%
  • ↑ startupStandardHome/lcp: +113%
  • ↓ startupStandardHome/cls: -72%
  • ↓ startupStandardHome/uiStartup: -24%
  • ↓ startupStandardHome/load: -17%
  • ↓ startupStandardHome/domContentLoaded: -19%
  • ↓ startupStandardHome/domInteractive: -34%
  • ↓ startupStandardHome/backgroundConnect: -12%
  • ↓ startupStandardHome/firstReactRender: -99%
  • ↓ startupStandardHome/initialActions: -50%
  • ↓ startupStandardHome/loadScripts: -17%
  • ↓ startupStandardHome/setupStore: -24%
  • ↓ startupStandardHome/fcp: -21%
  • ↓ startupStandardHome/lcp: -16%
User Journey Benchmarks · Samples: 5 · mock API
Benchmarkchrome-webpackfirefox-webpack
onboardingImportWallet
[Sentry log · main/release]
🟢 [CI log]🟡 [CI log]
🟡 total
onboardingNewWallet
[Sentry log · main/release]
🟢 [CI log]🟢 [CI log]

📈 Results compared to the previous 5 runs on main

  • ↑ onboardingImportWallet/srpButtonToSrpForm: +12%
  • ↑ onboardingImportWallet/confirmSrpToPwForm: +21%
  • ↑ onboardingImportWallet/pwFormToMetricsScreen: +33%
  • ↑ onboardingImportWallet/metricsToWalletReadyScreen: +19%
  • ↓ onboardingImportWallet/doneButtonToHomeScreen: -91%
  • ↓ onboardingImportWallet/openAccountMenuToAccountListLoaded: -74%
  • ↓ onboardingImportWallet/longTaskCount: -82%
  • ↓ onboardingImportWallet/longTaskTotalDuration: -95%
  • ↓ onboardingImportWallet/longTaskMaxDuration: -90%
  • ↓ onboardingImportWallet/tbt: -99%
  • ↓ onboardingImportWallet/total: -85%
  • ↑ onboardingNewWallet/createPwToRecoveryScreen: +34%
  • ↑ onboardingNewWallet/skipBackupToMetricsScreen: +10%
  • ↓ onboardingNewWallet/doneButtonToAssetList: -71%
  • ↓ onboardingNewWallet/longTaskCount: -62%
  • ↓ onboardingNewWallet/longTaskTotalDuration: -74%
  • ↓ onboardingNewWallet/longTaskMaxDuration: -37%
  • ↓ onboardingNewWallet/tbt: -97%
  • ↓ onboardingNewWallet/total: -67%
Dapp Page Load Benchmarks · Samples: 100
Benchmarkchrome-webpack
dappPageLoad
[Sentry log · main/release]
🟢 [CI log]

📈 Results compared to the previous 5 runs on main

  • ↓ dappPageLoad/pageLoadTime: -59%
  • ↓ dappPageLoad/domContentLoaded: -16%
  • ↓ dappPageLoad/firstPaint: -42%
  • ↓ dappPageLoad/firstContentfulPaint: -42%
Bundle Size Diffs [🚨 Warning! Bundle size has increased!]
Status Bundle Total Diff Change
✅ background 15.21 MiB -44.89 KiB -0.29%
🚨 ui 19.47 MiB +56.92 KiB +0.29%
✅ common 0 Bytes 0 Bytes 0.00%
✅ other 4.46 MiB -543 Bytes -0.01%
✅ content scripts 3.23 MiB -1.07 KiB -0.03%
🚨 unzipped 79.88 MiB +111.64 KiB +0.14%
🚨 zip 23.97 MiB +113.63 KiB +0.47%

This branch was successfully deployed

1 active deployment
pr-comment — c5754dc4 Deployed Nov 10, 2026 by abretonc7s via Publish prerelease / Publish prerelease #173685
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant