Repository navigation
feat(perps): integrate Lighter with funded Lite order entry - #34865
Conversation
|
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. |
🧪 Flaky unit test detectionRun history flaky detectionHistorical failure rate is a hint, not proof — review each suggestion in context. See the flaky-test-detection skill for the full pattern reference and manual audit workflow. Failures / runs sampled per window:
AI-detected flaky patterns
|
…on, flag-gated) (MetaMask#9889) ## Explanation **TAT-3766: add Lighter as a second perps venue.** Started as a spike, now a full `LighterProvider` in `@metamask/perps-controller`, hardened through 23 rounds of adversarial cross-review and validated end-to-end against live Lighter testnet. ### What's in it - **Full trading surface** behind the `PerpsProvider` interface: place/cancel/close orders, venue-linked OCO TP/SL (grouped trigger orders), isolated margin add/remove, leverage updates, signed L2 withdraw, historical orders/fills/funding/ledger, and live streams (prices, account, positions, orders, fills, order book, candles) over a shared WebSocket with REST-polling fallback. - **Signer model**: deterministic venue-key derivation via `personal_sign` (raw EVM key never leaves the keyring — hardware-wallet compatible). The Go/WASM signer is injected through a `LighterSignerBridge` seam so mobile's WebView bridge and the headless Node adapter are interchangeable. - **Financial-safety architecture** (the bulk of the review rounds): a durable nonce-dispatch ledger with exact tx-hash reconciliation — an ambiguous dispatch is never blindly retried; proven-committed outcomes are quarantined until explicitly acknowledged per-outcome (`getRecoveredDispatches`/`acknowledgeRecoveredDispatch`); a TP/SL settlement journal that survives crashes and restores nothing it cannot prove, parking unprovable protection as durable manual recovery (`getPendingManualRecoveries`); process-wide serialization of venue writes, the ledger, and the bridge's singleton WASM client; session fences on every account switch. - **Controller wiring behind a flag**: `PerpsProviderType` gains `'lighter'`; enablement via `providerCredentials.lighter.enabled` or the `perpsLighterProviderEnabled` remote flag. New controller actions expose the safety states to clients. ### How it was validated - **Unit**: 213 provider tests (3,031 package-wide), including deterministic fault-injection proofs for response loss, masked commits, restarts, account switches mid-flight, concurrent providers, and storage failures. Every review-round fix was proven RED against the prior commit before landing. - **Live testnet e2e** (real venue, real WASM signer built from source): sign-only 9/9 (pinned WASM identity contract), key registration, order-lifecycle 7/7, TP/SL 12/12 (including a real venue-linked OCO pair), close-position 5/5 with both fills observed on the trades stream, margin/leverage 7/7 on a fresh faucet account. - **Mainnet read path**: 227 active perp markets, full-catalog WS snapshot, candles. - **On-device (iOS simulator)**: live prices/candles/positions over WS-only transport; WASM signer mount→ready ~2.1 s, round-trip 32–78 ms. ### Open items - `editOrder` deliberately returns an error: Lighter accepts `ModifyOrder` submissions without executing the reprice — raised with the venue; cancel + re-place instead. - Mobile client integration (WebView signer bridge + recovery-state UI): MetaMask/metamask-mobile#34865 (draft, consumes this package). ## References - https://consensyssoftware.atlassian.net/browse/TAT-3766 - Signer reference: https://github.com/elliottech/lighter-go/tree/web-wasm - Risk context: https://l2beat.com/scaling/projects/lighter ## Checklist - [x] I've updated the test suite for new or updated code as appropriate - [x] I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate - [x] I've communicated my changes to consumers by [updating changelogs for packages I've changed](https://github.com/MetaMask/core/tree/main/docs/processes/updating-changelogs.md) - [x] I've introduced [breaking changes](https://github.com/MetaMask/core/tree/main/docs/processes/breaking-changes.md) in this PR and have prepared draft pull requests for clients and consumer packages to resolve them — not applicable: no breaking changes <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **High Risk** > Introduces a new trading venue, client signer bridge, and durable dispatch/recovery flows for ambiguous L2 submissions—areas that affect funds, leverage, and order settlement even when feature-gated. > > **Overview** > Adds **Lighter** as an opt-in perps venue (`PerpsProviderType` now includes `'lighter'`, a breaking change for exhaustive switches). Registration mirrors MYX: dynamic import of `LighterProvider`, gated by `providerCredentials.lighter.enabled` or remote `perpsLighterProviderEnabled` **only when** the client supplies `LighterSignerBridge` (venue keys stay client-side; no deposit ads until bridge deposit flow exists). `PROVIDER_CONFIG.LIGHTER_TESTNET_ONLY` and cache keys follow the same pattern as MYX. > > Exposes **durable settlement** on the controller and aggregator: `getPendingManualRecoveries`, `getRecoveredDispatches`, and `acknowledgeRecoveredDispatch`, with exported `PerpsPendingManualRecovery` / `PerpsRecoveredDispatch` types. Aggregated mode fans these out across providers that implement the contract. > > **Multi-provider routing** for margin math: optional `providerId` on liquidation/maintenance params and on `getMaxLeverage(asset, providerId?)`, with `AggregatedPerpsProvider` delegating to the owning venue instead of always using the default. > > Ships Lighter **constants/types exports** (`lighterConfig`, signer bridge types) and changelog notes for the initial (disabled-by-default) implementation. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit f8a378e. Bugbot is set up for automated code reviews on this repo. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
geositta
left a comment
There was a problem hiding this comment.
Thanks for documenting the POC constraints and adding focused coverage around bridge reset, recovery refresh ordering, leverage limits, and fill transforms. I am requesting changes because the current branch contains correctness and release blockers that need to be resolved before this can move out of its POC state.
Please address the inline comments, rebase onto current main, replace the temporary .yalc dependency with a published controller version, remove the temporary recipes and evidence, and rerun CI. GitHub currently reports merge conflicts, 18 failed checks, 2 pending checks, and the DO-NOT-MERGE label.
Validation performed: yarn lint:tsc passed; focused ESLint completed with no errors and one React Compiler warning; four focused suites passed 60 tests with coverage disabled. A broader focused run reported all six suites as PASS before the process exhausted its 4 GB heap, consistent with the cost of the inlined signer asset. The Hyperliquid behavior was checked against the official fills, PnL, and perpetual metadata documentation.
a93b2a4 to
11f64b4
Compare
|
Flaky-test detection triage:
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ 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 64560c6. Configure here.
There was a problem hiding this comment.
🔒 Agentic Security Review — 1 finding(s).
- [MEDIUM]
app/components/UI/Perps/Lighter/wasm-wrapper.standalone.html:616— The Lighter signer WebView vendors a ~7.7MB Go/WASM blob copied from Lighter’s live testnet frontend CDN. The revision is not in public lighter-go, provenance.md says it is not a reproducible local build, and CI never asserts the documented WASM digest. That binary is WebAssembly.instantiate’d and then given the SecureKeychain venue private key for orders, cancels, leverage, margin, withdrawals, and auth tokens, including mainnet chain IDs.- Impact: This PR newly executes an unreproducible third-party signer with custody of the Lighter venue key. A backdoored copy-time CDN artifact (or a later unpinned blob swap that also updates CSP hashes) can emit attacker-chosen L2 signatures or disclose that key. That is a public-artifact / lockfile-equivalent integrity failure on a signing execution boundary, not a UI-only POC issue. Default store production still omits the
lighterfence unless MM_PERPS_LIGHTER_PROVIDER_ENABLED=true or METAMASK_ENVIRONMENT=dev, but those opt-in/dev builds are exactly the path this signer is wired for, and client params allow LIGHTER_MAINNET_CHAIN_ID. - Remediation: Do not ship a live testnet Vite asset as the production/mainnet-capable signer. Vendor a signer built from a tagged public revision or a Lighter-signed release, record that digest in CI against decoded wasmBase64 (not markdown-only; CSP self-consistency is not a pin), and keep mainnet chain IDs disabled until that rebuild path exists.
- Impact: This PR newly executes an unreproducible third-party signer with custody of the Lighter venue key. A backdoored copy-time CDN artifact (or a later unpinned blob swap that also updates CSP hashes) can emit attacker-chosen L2 signatures or disclose that key. That is a public-artifact / lockfile-equivalent integrity failure on a signing execution boundary, not a UI-only POC issue. Default store production still omits the
aganglada
left a comment
There was a problem hiding this comment.
Main blocker for me is not lazy loading the wasm, we should make it so it's lazy loaded only if the provider is enabled
🔍 Smart E2E Test Selection
click to see 🤖 AI reasoning detailsE2E Test Selection: Performance Test Selection: |
|
|
| Platform | Device | Reason | Recording |
|---|---|---|---|
| Android | Google Pixel 8 Pro (v14.0) | no_performance_metrics | 📹 Watch |
🔬 App profiling check · Current run 35845018597 · Baseline (last green on main) run 34935384411 @ 4e14632
Summary: ✅ No metrics over the +10% baseline margin.
ℹ️ API calls unavailable:
Network logs API error: Bad Request
Full metric table (+10% variance rules)
Disclaimer — allowed variance: a +10% margin over the baseline is permitted.
- If
Current <= Baseline + 10%, treated as acceptable noise.- If
Current > Baseline + 10%, Current and variance % are highlighted with⚠️ .
| Metric | Baseline | Current | Δ |
|---|---|---|---|
| CPU avg | 9.95% | 8.29% | -1.66 (-16.7%) |
| CPU max | 22.93% | 22.06% | -0.87 (-3.8%) |
| Memory avg | 702.75 MB | 674.27 MB | -28.48 (-4%) |
| Memory max | 877.25 MB | 819.58 MB | -57.67 (-6.6%) |
| Slow frames | 15.97% | 11.67% | -4.3 (-26.9%) |
| Frozen frames | 0% | 0% | 0 (0%) |
| ANRs | 0 | 0 | 0 (0%) |
| Issues | 2 | 1 | -1 (-50%) |
| Critical issues | 1 | 1 | 0 (0%) |
| App size | 394.88 MB | 396.73 MB | +1.85 (+0.5%) |
Perps open position and close it
| Platform | Device | Reason | Recording |
|---|---|---|---|
| Android | Google Pixel 8 Pro (v14.0) | no_performance_metrics | 📹 Watch |
🔬 App profiling check · Current run 35845018597 · Baseline (last green on main) run 34966774120 @ acda9d8
Summary:
ℹ️ API calls unavailable:
Network logs API error: Bad Request
Full metric table (+10% variance rules)
Disclaimer — allowed variance: a +10% margin over the baseline is permitted.
- If
Current <= Baseline + 10%, treated as acceptable noise.- If
Current > Baseline + 10%, Current and variance % are highlighted with⚠️ .
| Metric | Baseline | Current | Δ |
|---|---|---|---|
| CPU avg | 8.17% | 8.42% | +0.25 (+3.1%) |
| CPU max | 25.69% | 23.64% | -2.05 (-8%) |
| Memory avg | 826.35 MB | 796.19 MB | -30.16 (-3.6%) |
| Memory max | 1069.37 MB | 989.57 MB | -79.8 (-7.5%) |
| Slow frames | 9.2% | 24% | +14.8 (+160.9%) |
| Frozen frames | 0% | 0% | 0 (0%) |
| ANRs | 0 | 0 | 0 (0%) |
| Issues | 2 | 2 | 0 (0%) |
| Critical issues | 2 | 2 | 0 (0%) |
| App size | 394.9 MB | 396.73 MB | +1.83 (+0.5%) |
Branch: TAT-3766-feat-lighter-webview-signer-poc · Build: E2E · Commit: 3988e65 · View full run
| } | ||
|
|
||
| // Exposed for focused code-fencing tests only. | ||
| module.exports.getBuildTypeFeatures = getBuildTypeFeatures; |
There was a problem hiding this comment.
We will remove code fencing soon, it is already removed on extension




Description
Initial feature-gated Lighter POC using published
@metamask/perps-controller@17.3.0, a pinned embedded WebView signer, and funded Lite order entry. Aggregated Lighter orders switch to Lighter before reading balance and market metadata. Hyperliquid retains its existing funding path.The follow-up constrains registration plaintext, blocks signer network APIs/subresources, recovers WASM/Go startup failures, cancels retired keychain/setup work, and records terminal failures. It also adds funded-screen telemetry, validates optional signer indices, collects the Metro test in CI, and prevents an unsupported Lighter deposit route. Controller 17.3.0 includes the upstream canceled-order filled-size fix; the temporary Yarn patch has been removed.
Capabilities, fees and POC limits · Pinned signer provenance and trust boundary
Controller upgrade verification at
fd68ef7d27: installed unpatched 17.3.0; 225 unit tests, 6 component-view tests, TypeScript, lint and formatting passed. Earlier trading captures retain the source/build provenance documented below.Bundle isolation at
365d4a12029:MM_PERPS_LIGHTER_PROVIDER_ENABLED=trueis now required in every build, including local development. Normal production already excluded the signer; development no longer includes it automatically. Real iOS bundles with the flag off contain no signer host/HTML in their source maps and no embedded WASM payload: production 20,804 modules, development 21,098 modules. Enabled POC builds still inline the local signer; this is build-time exclusion, not runtime code splitting. 235 focused unit tests, 7 component-view tests, TypeScript, lint and formatting passed.CI follow-up: Jest stubs the native signer asset instead of inlining 10 MB into each test VM. Component-view coverage now reports exercised files, matching the integration layer; unit tests retain repository-wide coverage. The previously failing shard passed locally with the same 12 GiB limit (36 suites / 425 tests) and all three component-view shards passed in CI on
903d04f13cc. Merged main638585d253f, including its wallet compatibility fix from #36671, and removed this PR's wallet-version override. No patch files or patch resolutions differ from main. 144 wallet-adapter and order-normalization tests, immutable install and TypeScript passed after the merge.Changelog
CHANGELOG entry: Added initial Lighter integration with funded-balance order entry in Lite mode.
Related issues
Upstream controller fix: MetaMask/core#10308, merged and released in
@metamask/perps-controller@17.3.0, now consumed directly without a Yarn patch. Core live cancellation recipe callsPerpsController.getOrders()directly: the same Lighter receipt fails with the old filled-size calculation and passes with the upstream fix, without placing new orders.Refs: MetaMask/core#10187
Tracking: TAT-3766.
Manual testing steps
MM_PERPS_LIGHTER_PROVIDER_ENABLED=truein.js.envand restart Metro with a cleared cache, select an owned funded Lighter testnet account with an existing signer slot, and reserve an empty BTC market.Native build prerequisite: the current branch requires
ExpoGlassEffect, added to main on September 17. Runway35154767219, the newest downloadable iOS artifact checked on September 21, predates that module and can crash after unlock when Trade renders. Use a native development build matching this branch or a newer compatible Runway. When rebuilding an existing Runway fixture locally, preserve its keychain encryption configuration so saved signer credentials remain readable.Native compatibility follow-up on
717df71d4a: installed a local iOS development build withExpoGlassEffectand the original fixture encryption configuration. Actual lock/unlock and existing Lighter signer recovery passed 15/15 nodes; native module availability and the Trade menu passed 9/9. No new key registration or trades in this follow-up. The trading receipts below retain their original build provenance.Executed Lighter proof
iOS simulator
mm-1, Runway development build35154767219, existing four-account fixture,dev1, Lighter testnet account 59, existing slot 8. No funding or key registration. The unrelated ETH position was preserved.Trading captures used the runtime source committed as
74d8077aa7; only test formatting followed the captures. Post-commit launch verification and exact-row history validation passed. The library recipes explicitly select Lighter, assert the mounted order inputs, and reconcile run-scoped UI client IDs with Lighter'saccountInactiveOrdersendpoint and controller records bearingproviderId: lighter.683154378305478444249244816510.00012 BTC1742284954285065629499591759790.00012 BTC2585754853652768444249244803620Market cycle: 39/39 nodes. Limit cycle: 35/35 nodes. History: 17/17 nodes. Both trading cycles ended with no BTC residue. The earlier failed receipt check is retained; Lighter's inactive-order read lagged cancellation acknowledgement, so the reusable assertion now polls only the exact IDs for a bounded period.
The real iOS signer probe denied ten transport APIs and observed CSP violations for external image/script loads. Signing continued to work. No iframe CSP event was observed. Focused checks passed: 135 unit tests, 7 funded-screen component-view tests, TypeScript, lint, formatting and normal CI test discovery.
Reusable recipes are now on the Perps library main branch, pinned revision
32e9d79. The reproduction contract covers venue selection, owned fixture/signer prerequisites, limits and cleanup.The published limit recipe additionally refuses an ambiguous cancellation target across markets. Its final replay passed 38/38, with Lighter order
844424924468408canceled and filled size0. The screenshots above come from the earlier receipt table's runs.Replay the published recipes
Use
@deeeed/metamask-harness@0.61.1or newer. It includes@farmslot/recipe-harness@0.18.1, which retains typed invocation inputs for portable artifact validation.Use a funded, owned Lighter testnet fixture and an already registered key slot. Reserve the selected market, start with no BTC orders/positions, and restart the app before each trading journey. Set these variables to your fixture's public metadata; they are not credentials.
Remaining limits: Android runtime, mainnet financial trading, Extension compilation parity and a full audit/reproducible build of the deployed signer remain unverified. The registration protocol's L1 plaintext has no chain field; the pinned signer remains trusted for cryptography. The signer CSP now permits only the four embedded scripts by hash; Chromium and WebKit both loaded the real signer and blocked an extra inline script. The non-required PR-size check remains above its 1,000-line threshold. All required checks pass on
c3666c30fe5, including unit, component-view, integration, all Android smoke shards and Sonar. The full CI run on that revision completed successfully, including the Android performance follow-up. Bugbot passed with no unresolved review threads. CI and Bugbot are rerunning on365d4a12029; reviewer approval is still required.Review follow-up,
c25aa718c5Wallet lock now retires pending signer work and removes the native WebView; unlocking creates a fresh page. The iOS wallet lifecycle recipe passed 15/15 nodes: locked keyring, zero native signers, rejected signing, then a new native page and successful read-token signing after unlock. Unit coverage includes pending work and rapid lock/unlock with stale page messages.
The shared market router now selects the explicit Pro venue before mounting its order form and keeps the form closed on switch failure. Runtime proof recorded aggregated BTC metadata at Hyperliquid's 40x limit, switched to Lighter's 50x metadata, then placed and canceled the Lighter limit order. Client receipt
188515645512169matched venue order844424924447876, canceled with filled size0; BTC ended flat. The combined run's final receipt node exceeded its bounded wait, and a separate exact-ID read subsequently passed. Both traces are retained.Focused unit/component-view checks, TypeScript and lint passed. Follow-up
717df71d4akeeps Lite available after a failed Pro venue switch; its regression failed before the fix and the six router view tests pass afterward. The obsolete profiling-analyzer discussion and the Lite-mode follow-up were replied to and resolved.Screenshots/Recordings
Screenshots are grouped for quick reviewer comparison.
After
Actual Lighter testnet trades, backed by the exact venue receipts above. The shared UI does not display a venue label. The developer control remains visible.
Before
N/A. Initial provider integration.
Pre-merge author checklist
Performance checks (if applicable)
Pre-merge reviewer checklist
Note
High Risk
Changes add off-device-adjacent cryptographic signing, private-key persistence, and a pinned WASM trust boundary—core wallet and trading security surface.
Overview
Adds feature-gated Lighter perps configuration in
.js.env.example(MM_PERPS_LIGHTER_PROVIDER_ENABLEDplus optional testnet account and API-key index overrides).Introduces a mobile Lighter signing stack: a hidden WebView loads a local embedded Go/WASM page (network APIs stubbed, navigation restricted to
https://localhost/, hash-locked CSP) and talks to React Native via a strict postMessage protocol. A module-levellighterSignerBridgeimplements the@metamask/perps-controllersigner interface—queues work until the page is ready, persists signer keys in SecureKeychain (chain/account/slot scoped), validatesRegister Lighter Accountplaintext before registration reaches the controller, and enforces wallet-lock behavior by tearing down the WebView and failing in-flight calls.Lifecycle hardening includes exponential-backoff reloads after WASM init or renderer crashes (with reload counters reset after a successful ready), terminal unavailability after repeated failures, and extensive unit tests (including a lightweight HTML mock for CI).
wasm-provenance.mddocuments the pinned testnet WASM artifact and trust boundaries. A smalllighterOrderNormalizationtest guards the upstream canceled-order filled-size behavior in the installed controller adapter.Reviewed by Cursor Bugbot for commit c3666c3. Bugbot is set up for automated code reviews on this repo. Configure here.