Skip to content

fix(perps): preserve Lighter executed size on cancellation - #10308

Merged
abretonc7s merged 2 commits into
mainfrom
fix/perps/lighter-canceled-filled-size
Sep 22, 2026
Merged

abretonc7s merged 2 commits into
mainfrom
fix/perps/lighter-canceled-filled-size

Conversation

@abretonc7s

@abretonc7s abretonc7s commented Sep 21, 2026 •

Copy link
Copy Markdown
Contributor

Explanation

Lighter clears remaining_base_amount on cancellation, so initial - remaining incorrectly reports an unfilled canceled order as fully filled. Prefer the validated optional filled_base_amount; retain the existing calculation when the field is omitted.

References

Live controller proof

The recipe invokes PerpsController.getOrders() through the real Lighter provider and HTTP client. No Jest or mocked venue responses. The existing Mobile signer supplies read-token signatures; venue writes and transaction signing are blocked.

Same testnet account 59, key slot 8, BTC cancellation 844424924447876, client receipt 188515645512169:

Read Original BTC Remaining BTC Filled BTC Recipe
Live Lighter response 0.00029 0 0 Exact receipt matched
Original calculation 0.00029 0 0.00029 Failed as expected
Patched controller 0.00029 0 0 Passed

The published library recipe passed 4/4 steps on the pushed Core commit. No orders, funding or key registration were needed. Reproduction inputs and signer requirements are in the linked contract.

Validation

  • Nine regression cases failed before the fix; all 132 focused tests passed afterward.
  • Full Perps suite: 3,617 passed, 40 skipped.
  • Full production build, changed-file lint, driver typecheck, formatting and package changelog validation passed.
  • Independent review found no actionable issues.
  • The broader test-inclusive typecheck reports errors in untouched tests, including orderCalculations.advanced-orders.test.ts; no unrelated type fixes are included.

Checklist

  • I've updated the test suite for new or updated code as appropriate
  • I've updated documentation (JSDoc, Markdown, etc.) for new or updated code as appropriate
  • I've communicated my changes to consumers by updating changelogs for packages I've changed
  • I've introduced breaking changes in this PR and have prepared draft pull requests for clients and consumer packages to resolve them

Note

Low Risk
Localized Lighter order normalization and display logic with backward-compatible fallback when filled_base_amount is omitted; no trading, auth, or signing path changes.

Overview
Fixes incorrect fully-filled display for Lighter orders that were canceled with zero remaining size but no execution. The adapter no longer infers fill size only from initial - remaining; when the venue sends filled_base_amount, that value drives filledSize (including 0 on cancel). If the field is absent, the previous derivation is unchanged.

Validation and types: optional filledBaseAmount is accepted on inactive/active order payloads with strict decimal checks; malformed values still fail as invalid venue data.

Tests: Jest covers adapter and client parsing; a new tsx e2e driver asserts PerpsController.getOrders matches live testnet inactive-order wire data for a known canceled receipt (read-only, no venue writes).

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

@abretonc7s abretonc7s self-assigned this Sep 21, 2026
@abretonc7s abretonc7s added team-perps bug Something isn't working labels Sep 21, 2026
@abretonc7s
abretonc7s marked this pull request as ready for review September 21, 2026 10:18
@abretonc7s
abretonc7s requested review from a team as code owners September 21, 2026 10:18
@abretonc7s
abretonc7s deployed to default-branch September 21, 2026 10:18 — with GitHub Actions Active
@abretonc7s
abretonc7s added this pull request to the merge queue Sep 22, 2026
Merged via the queue into main with commit 4c4dec7 Sep 22, 2026
50 checks passed
@abretonc7s
abretonc7s deleted the fix/perps/lighter-canceled-filled-size branch September 22, 2026 01:00
@abretonc7s abretonc7s mentioned this pull request Sep 22, 2026
1 of 2 tasks
pull Bot pushed a commit to Reality2byte/core that referenced this pull request Sep 22, 2026
## Summary
- Bump `@metamask/core-monorepo` to `1269.0.0`
- Bump `@metamask/perps-controller` to `17.3.0`
- Fixed: use Lighter's reported executed amount for order filled sizes
so canceled orders do not appear fully filled (MetaMask#10308)

## Test plan
- [x] `yarn changelog:validate`
- [ ] CI

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Version and changelog updates only; trading behavior change was
already merged in the referenced fix PR.
> 
> **Overview**
> This is a **release-only** PR: it bumps the monorepo to **`1269.0.0`**
and publishes **`@metamask/perps-controller` `17.3.0`**, with matching
**`CHANGELOG.md`** and compare-link updates.
> 
> The **`17.3.0`** notes document the shipped fix from
[MetaMask#10308](MetaMask#10308): **Lighter order
filled sizes now use the venue’s reported executed amount**, so
partially filled then **canceled** orders no longer show as **100%
filled** in UI/state.
> 
> There are **no runtime code changes** in this diff—only version
metadata and release notes.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
ea73c3b. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
pull Bot pushed a commit to Reality2byte/metamask-mobile that referenced this pull request Sep 23, 2026
…#34865)

## **Description**

<!-- mms-check: type=text required=true -->

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](https://github.com/MetaMask/metamask-mobile/blob/TAT-3766-feat-lighter-webview-signer-poc/docs/perps/lighter-capabilities-and-fees.html)
· [Pinned signer provenance and trust
boundary](https://github.com/MetaMask/metamask-mobile/blob/TAT-3766-feat-lighter-webview-signer-poc/app/components/UI/Perps/Lighter/wasm-provenance.md)

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=true` is 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 main `638585d253f`,
including its wallet compatibility fix from
[MetaMask#36671](MetaMask#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**

<!-- mms-check: type=changelog required=true blocking=true -->

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](https://github.com/MetaMask/experimental-metamask-recipe-perps/blob/248fef4/docs/core-lighter-canceled-order.md)
calls `PerpsController.getOrders()` directly: the same Lighter receipt
fails with the old filled-size calculation and passes with the upstream
fix, without placing new orders.

<!-- mms-check: type=issue-link required=true -->

Refs: MetaMask/core#10187

Tracking: TAT-3766.

## **Manual testing steps**

<!-- mms-check: type=manual-testing required=true -->

1. Set `MM_PERPS_LIGHTER_PROVIDER_ENABLED=true` in `.js.env` and restart
Metro with a cleared cache, select an owned funded Lighter testnet
account with an existing signer slot, and reserve an empty BTC market.
2. In Lite, submit a small market Long. Confirm the accepted size, then
close through the UI and verify BTC is flat.
3. In Pro, place a non-crossing limit Long and cancel it individually.
Verify no BTC orders/positions remain and canceled filled size is zero.
4. Open history and match the opening, closing and canceled records to
their exact Lighter venue IDs.

**Native build prerequisite:** the current branch requires
`ExpoGlassEffect`, added to main on September 17. Runway `35154767219`,
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 with `ExpoGlassEffect` and 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 build `35154767219`, 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's `accountInactiveOrders` endpoint
and controller records bearing `providerId: lighter`.

| Operation | UI client receipt | Lighter venue order | Result |
|---|---|---|---|
| Lite market Long | `68315437830547` | `844424924481651` | Filled
`0.00012 BTC` |
| UI close | `174228495428506` | `562949959175979` | Filled `0.00012
BTC` |
| Pro limit/cancel | `258575485365276` | `844424924480362` | Canceled,
filled size `0` |

Market 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`](https://github.com/MetaMask/experimental-metamask-recipe-perps/tree/32e9d79).
The [reproduction
contract](https://github.com/MetaMask/experimental-metamask-recipe-perps/blob/32e9d79/docs/mobile-lighter-proof.md)
covers venue selection, owned fixture/signer prerequisites, limits and
cleanup.

- [Explicit venue start
state](https://github.com/MetaMask/experimental-metamask-recipe-perps/blob/32e9d79/recipes/mobile/perps/venue-start-state.recipe.json)
- [Lite market
open/close](https://github.com/MetaMask/experimental-metamask-recipe-perps/blob/32e9d79/recipes/mobile/perps/lighter-lite-market-cycle.recipe.json)
- [Pro
limit/cancel](https://github.com/MetaMask/experimental-metamask-recipe-perps/blob/32e9d79/recipes/mobile/perps/lighter-pro-limit-cycle.recipe.json)
- [Exact-ID
history](https://github.com/MetaMask/experimental-metamask-recipe-perps/blob/32e9d79/recipes/mobile/perps/lighter-history.recipe.json)

The published limit recipe additionally refuses an ambiguous
cancellation target across markets. Its final replay passed **38/38**,
with Lighter order `844424924468408` canceled and filled size `0`. The
screenshots above come from the earlier receipt table's runs.

<details><summary>Replay the published recipes</summary>

Use `@deeeed/metamask-harness@0.61.1` or 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.

```sh
export RECIPE_LIBRARY_PATH="perps=/path/to/experimental-metamask-recipe-perps"
export METAMASK_RECIPE_AUTO_HUD=0
LIGHTER_PROOF_ARGS=(
  account="$FIXTURE_ACCOUNT" account_index="$LIGHTER_ACCOUNT_INDEX"
  api_key_index="$LIGHTER_KEY_SLOT" market=BTC market_index="$LIGHTER_BTC_INDEX"
  expected_leverage=2 min_spendable_usd=20 exclusive_testnet_market=true
)

# Inspect each plan before replacing --plan with a new --artifacts-dir path.
mm-harness run perps.lighter-lite-market-cycle "${LIGHTER_PROOF_ARGS[@]}" \
  'amount_usd="10"' --plan
mm-harness run perps.lighter-pro-limit-cycle "${LIGHTER_PROOF_ARGS[@]}" \
  'amount_usd="20"' "limit_price=\"$NON_CROSSING_BID\"" --plan

# Use the exact activity IDs returned by the receipt verification nodes.
mm-harness run perps.lighter-history account="$FIXTURE_ACCOUNT" market=BTC \
  open_activity_id="$OPEN_ACTIVITY_ID" close_activity_id="$CLOSE_ACTIVITY_ID" \
  canceled_activity_id="$CANCELED_ACTIVITY_ID" --artifacts-dir temp/lighter-history-proof
```

</details>

**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`](https://github.com/MetaMask/metamask-mobile/actions/runs/35805884030/job/107011429452),
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 on `365d4a12029`;
reviewer approval is still required.

### Review follow-up, `c25aa718c5`

Wallet 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 `188515645512169` matched venue
order `844424924447876`, canceled with filled size `0`; 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 `717df71d4a` keeps 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**

<table>
<tr><td colspan="2"><strong>Recipe visual
evidence</strong><br/>Screenshots are grouped for quick reviewer
comparison.</td></tr>
<tr>
<td align="center" width="50%"><strong>Lighter: market Long filled,
0.00012 BTC</strong><br/><img
src="https://raw.githubusercontent.com/abretonc7s/mm-mobile-artifacts/main/evidence/TAT-3766-dev-lighter-main-migration-1459/01-lighter-market-fill.png"
alt="Lighter: market Long filled, 0.00012 BTC" width="400" /></td>
<td align="center" width="50%"><strong>Lighter: full-position UI
close</strong><br/><img
src="https://raw.githubusercontent.com/abretonc7s/mm-mobile-artifacts/main/evidence/TAT-3766-dev-lighter-main-migration-1459/02-lighter-close.png"
alt="Lighter: full-position UI close" width="400" /></td>
</tr>
<tr>
<td align="center" width="50%"><strong>Lighter: resting 0.00029 BTC
limit at $70,000</strong><br/><img
src="https://raw.githubusercontent.com/abretonc7s/mm-mobile-artifacts/main/evidence/TAT-3766-dev-lighter-main-migration-1459/03-lighter-resting-limit.png"
alt="Lighter: resting 0.00029 BTC limit at $70,000" width="400" /></td>
<td align="center" width="50%"><strong>Lighter: individual cancel, no
open BTC orders</strong><br/><img
src="https://raw.githubusercontent.com/abretonc7s/mm-mobile-artifacts/main/evidence/TAT-3766-dev-lighter-main-migration-1459/04-lighter-cancel.png"
alt="Lighter: individual cancel, no open BTC orders" width="400" /></td>
</tr>
<tr>
<td align="center" width="50%"><strong>Lighter: exact opening and
closing fills</strong><br/><img
src="https://raw.githubusercontent.com/abretonc7s/mm-mobile-artifacts/main/evidence/TAT-3766-dev-lighter-main-migration-1459/05-lighter-trade-history.png"
alt="Lighter: exact opening and closing fills" width="400" /></td>
<td align="center" width="50%"><strong>Lighter: canceled limit and
filled market orders</strong><br/><img
src="https://raw.githubusercontent.com/abretonc7s/mm-mobile-artifacts/main/evidence/TAT-3766-dev-lighter-main-migration-1459/06-lighter-order-history.png"
alt="Lighter: canceled limit and filled market orders" width="400"
/></td>
</tr>
</table>

### **After**

<!-- mms-check: type=screenshot required=true -->

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**

<!-- mms-check: type=checklist required=true -->

- [x] I've followed MetaMask Contributor Docs and Coding Standards.
- [x] I've completed the PR template to the best of my ability.
- [x] I've included tests if applicable.
- [x] I've documented my code using JSDoc format if applicable.
- [x] I've applied the right labels on the PR.

#### Performance checks (if applicable)

- [x] I've assessed Android testing: not run for this iOS POC; remains a
validation gap.
- [x] I've assessed power-user testing: not run; no performance sign-off
claimed.
- [x] I've assessed Sentry tracing: funded-screen measurement added and
tested; production delivery unverified.

## **Pre-merge reviewer checklist**

- [ ] I've manually tested the PR.
- [ ] I confirm the acceptance criteria and testing evidence are
sufficient.

<!-- CURSOR_SUMMARY -->
---

> [!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_ENABLED` plus 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-level **`lighterSignerBridge`** implements the
`@metamask/perps-controller` signer interface—queues work until the page
is **ready**, persists signer keys in **SecureKeychain**
(chain/account/slot scoped), validates **`Register Lighter Account`**
plaintext 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.md`** documents the pinned testnet WASM artifact and
trust boundaries. A small **`lighterOrderNormalization`** test guards
the upstream canceled-order filled-size behavior in the installed
controller adapter.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
c3666c3. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
default-branch — c1cbfdaa Deployed Sep 21, 2026 by abretonc7s via Determine whether this PR is a release PR #4294
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working team-perps

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants