@deeeed/metamask-harness 0.47.4 → 0.49.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +3 -2
- package/adapters/extension/lib/macos-focus.cjs +2 -2
- package/adapters/extension/live.sh +6 -8
- package/adapters/mobile/bridge-runtime/lib/bridge-errors.cjs +1 -0
- package/adapters/shared/ensure-runner-deps.sh +6 -0
- package/dist/adapters/extension/runtime-decision.js +18 -1
- package/dist/adapters/extension/runtime.js +5 -5
- package/dist/adapters/extension/surface.js +1 -0
- package/dist/app-lifecycle.js +3 -1
- package/dist/command-contract.js +2 -0
- package/dist/commands/call.js +1 -8
- package/dist/commands/device-target.js +12 -3
- package/dist/commands/doctor.js +7 -5
- package/dist/commands/fixtures.js +1 -1
- package/dist/commands/help.js +17 -5
- package/dist/commands/launch/index.js +10 -0
- package/dist/commands/manifest.js +3 -1
- package/dist/commands/mobile-device-view.js +13 -3
- package/dist/commands/parse-args.js +2 -0
- package/dist/commands/run-engine.js +29 -4
- package/dist/commands/run.js +1 -1
- package/dist/commands/runtime-launch.js +10 -1
- package/dist/commands/status.js +8 -4
- package/dist/devices.js +31 -9
- package/dist/execution-provenance.js +8 -7
- package/dist/funding-execution-context.js +62 -17
- package/dist/heal-bounds.js +3 -3
- package/dist/live-adapter-contract.js +4 -0
- package/dist/manifest.js +2 -13
- package/dist/metamask-action-validation.js +3 -2
- package/dist/mm-harness-cli.js +4 -2
- package/dist/runner.js +14 -3
- package/docs/RECIPES.md +135 -24
- package/docs/RELEASE-QA-CAPABILITY-MAP.md +3 -2
- package/library/actions/core/perps/_controller.mjs +24 -24
- package/library/actions/core/perps/assert_orders.mjs +3 -5
- package/library/actions/core/perps/assert_positions.mjs +3 -5
- package/library/actions/core/perps/close_orders.mjs +39 -19
- package/library/actions/core/perps/close_positions.mjs +53 -12
- package/library/actions/core/perps/edit_order.mjs +2 -0
- package/library/actions/core/perps/ensure_orders.mjs +3 -4
- package/library/actions/core/perps/ensure_positions.mjs +4 -5
- package/library/actions/core/perps/place_order.mjs +7 -14
- package/library/actions/core/perps/read_account.mjs +2 -0
- package/library/actions/core/perps/read_orders.mjs +2 -0
- package/library/actions/core/perps/read_positions.mjs +3 -1
- package/library/actions/core/perps/read_snapshot.mjs +3 -0
- package/library/actions/core/perps/start_state.mjs +3 -14
- package/library/actions/core/perps/teardown_state.mjs +3 -14
- package/library/actions/core/perps/update_position_tpsl.mjs +13 -19
- package/library/actions/core/wallet/list_accounts.mjs +3 -0
- package/library/actions/extension/analytics/consent.mjs +2 -0
- package/library/actions/extension/analytics/set_consent.mjs +2 -0
- package/library/actions/extension/assets/home-token-identity.mjs +31 -0
- package/library/actions/extension/assets/import_custom_token.mjs +63 -79
- package/library/actions/extension/assets/open_details.mjs +19 -7
- package/library/actions/extension/assets/prepare_send.mjs +1 -0
- package/library/actions/extension/assets/read_details.mjs +62 -29
- package/library/actions/extension/assets/read_visible_state.mjs +5 -1
- package/library/actions/extension/assets/review_send.mjs +1 -0
- package/library/actions/extension/assets/select_network_scope.mjs +17 -9
- package/library/actions/extension/assets/send.mjs +2 -0
- package/library/actions/extension/assets/set_custom_gas.mjs +1 -0
- package/library/actions/extension/assets/set_token_visibility.mjs +86 -17
- package/library/actions/extension/assets/verify_sorting.mjs +63 -14
- package/library/actions/extension/deeplink/open.mjs +1 -0
- package/library/actions/extension/networks/add_chainlist.mjs +2 -0
- package/library/actions/extension/networks/add_custom.mjs +21 -17
- package/library/actions/extension/networks/custom_network.mjs +10 -3
- package/library/actions/extension/networks/read_visible_state.mjs +2 -0
- package/library/actions/extension/networks/remove_custom.mjs +1 -0
- package/library/actions/extension/performance/_navigation-memory.mjs +37 -6
- package/library/actions/extension/performance/compare_idle_navigation_memory.mjs +5 -2
- package/library/actions/extension/performance/measure_detached_dom.mjs +5 -2
- package/library/actions/extension/performance/measure_navigation_memory.mjs +5 -2
- package/library/actions/extension/perps/assert_orders.mjs +1 -0
- package/library/actions/extension/perps/assert_positions.mjs +1 -0
- package/library/actions/extension/perps/assert_visible_consistency.mjs +11 -4
- package/library/actions/extension/perps/close_orders.mjs +4 -1
- package/library/actions/extension/perps/close_positions.mjs +1 -0
- package/library/actions/extension/perps/close_visible_position.mjs +160 -11
- package/library/actions/extension/perps/compare_provider_market.mjs +2 -0
- package/library/actions/extension/perps/edit_margin.mjs +3 -1
- package/library/actions/extension/perps/ensure_orders.mjs +1 -0
- package/library/actions/extension/perps/ensure_positions.mjs +1 -0
- package/library/actions/extension/perps/mutation-receipt.mjs +54 -3
- package/library/actions/extension/perps/open_balance_action.mjs +17 -7
- package/library/actions/extension/perps/open_position_action.mjs +1 -0
- package/library/actions/extension/perps/perps.mjs +550 -69
- package/library/actions/extension/perps/place_order.mjs +1 -0
- package/library/actions/extension/perps/read_eligibility.mjs +1 -0
- package/library/actions/extension/perps/read_funds_confirmation.mjs +10 -0
- package/library/actions/extension/perps/read_orders.mjs +1 -0
- package/library/actions/extension/perps/read_positions.mjs +1 -0
- package/library/actions/extension/perps/read_snapshot.mjs +4 -1
- package/library/actions/extension/perps/read_visible_state.mjs +12 -6
- package/library/actions/extension/perps/search_markets.mjs +8 -2
- package/library/actions/extension/perps/select_activity_filter.mjs +2 -1
- package/library/actions/extension/perps/select_market_filter.mjs +75 -45
- package/library/actions/extension/perps/set_market_favorite.mjs +7 -3
- package/library/actions/extension/perps/state.mjs +2 -0
- package/library/actions/extension/perps/update_position_tpsl.mjs +3 -1
- package/library/actions/extension/perps/visible-mutation-identity.mjs +6 -2
- package/library/actions/extension/platform/cdp.mjs +50 -142
- package/library/actions/extension/settings/set_basic_functionality.mjs +1 -0
- package/library/actions/extension/swap_bridge/quote-state.mjs +56 -0
- package/library/actions/extension/swap_bridge/read_visible_state.mjs +5 -1
- package/library/actions/extension/swap_bridge/select_assets.mjs +12 -1
- package/library/actions/extension/swap_bridge/set_amount.mjs +11 -8
- package/library/actions/extension/swap_bridge/set_max_amount.mjs +18 -18
- package/library/actions/extension/swap_bridge/set_slippage.mjs +1 -9
- package/library/actions/extension/ui/locators.mjs +1 -0
- package/library/actions/extension/ui/navigate.mjs +60 -14
- package/library/actions/extension/wallet/ensure_unlocked.mjs +2 -0
- package/library/actions/extension/wallet/import.mjs +2 -0
- package/library/actions/extension/wallet/list_accounts.mjs +1 -0
- package/library/actions/extension/wallet/lock.mjs +4 -1
- package/library/actions/extension/wallet/read_state.mjs +1 -0
- package/library/actions/extension/wallet/reset.mjs +2 -0
- package/library/actions/extension/wallet/secret-input.mjs +2 -0
- package/library/actions/extension/wallet/select_account.mjs +2 -0
- package/library/actions/extension/wallet/setup.mjs +1 -0
- package/library/actions/extension/wallet/state.mjs +2 -0
- package/library/actions/extension/wallet/visible-session.mjs +2 -0
- package/library/actions/mobile/analytics/consent-settings.mjs +1 -0
- package/library/actions/mobile/analytics/set_consent.mjs +1 -0
- package/library/actions/mobile/app/network-control.mjs +2 -0
- package/library/actions/mobile/app/network.mjs +1 -0
- package/library/actions/mobile/app/network_assert.mjs +1 -0
- package/library/actions/mobile/app/network_capture.mjs +1 -0
- package/library/actions/mobile/assets/import_custom_token.mjs +3 -0
- package/library/actions/mobile/assets/open_details.mjs +1 -0
- package/library/actions/mobile/assets/read_details.mjs +1 -0
- package/library/actions/mobile/assets/read_visible_state.mjs +4 -1
- package/library/actions/mobile/assets/set_token_visibility.mjs +15 -9
- package/library/actions/mobile/assets/verify_sorting.mjs +90 -7
- package/library/actions/mobile/deeplink/open.mjs +1 -0
- package/library/actions/mobile/networks/add_custom.mjs +1 -0
- package/library/actions/mobile/networks/network-management.mjs +23 -15
- package/library/actions/mobile/networks/read_visible_state.mjs +1 -0
- package/library/actions/mobile/networks/remove_custom.mjs +7 -2
- package/library/actions/mobile/perps/assert_orders.mjs +1 -0
- package/library/actions/mobile/perps/assert_positions.mjs +1 -0
- package/library/actions/mobile/perps/capture_performance.mjs +1 -0
- package/library/actions/mobile/perps/close_orders.mjs +1 -0
- package/library/actions/mobile/perps/close_positions.mjs +1 -0
- package/library/actions/mobile/perps/ensure_orders.mjs +1 -0
- package/library/actions/mobile/perps/ensure_positions.mjs +1 -0
- package/library/actions/mobile/perps/measure_homepage_visible.mjs +1 -0
- package/library/actions/mobile/perps/performance-capture.mjs +2 -0
- package/library/actions/mobile/perps/perps.mjs +8 -1
- package/library/actions/mobile/perps/place_order.mjs +1 -0
- package/library/actions/mobile/perps/prepare_local_snapshot_endpoint.mjs +2 -0
- package/library/actions/mobile/perps/read-visible-state-loop.mjs +3 -0
- package/library/actions/mobile/perps/read_orders.mjs +1 -0
- package/library/actions/mobile/perps/read_positions.mjs +1 -0
- package/library/actions/mobile/perps/read_visible_state.mjs +1 -0
- package/library/actions/mobile/perps/search_markets.mjs +1 -0
- package/library/actions/mobile/perps/set_market_favorite.mjs +17 -19
- package/library/actions/mobile/platform/bridge.mjs +16 -4
- package/library/actions/mobile/platform/native-session-name.mjs +1 -0
- package/library/actions/mobile/platform/native-session.mjs +28 -17
- package/library/actions/mobile/platform/observe-ui.mjs +4 -2
- package/library/actions/mobile/platform/tool-paths.mjs +1 -0
- package/library/actions/mobile/swap_bridge/native-session.mjs +1 -0
- package/library/actions/mobile/swap_bridge/read_visible_state.mjs +1 -0
- package/library/actions/mobile/swap_bridge/select_assets.mjs +2 -0
- package/library/actions/mobile/swap_bridge/set_amount.mjs +1 -0
- package/library/actions/mobile/swap_bridge/set_max_amount.mjs +1 -0
- package/library/actions/mobile/swap_bridge/set_slippage.mjs +1 -0
- package/library/actions/mobile/swap_bridge/submit_transaction.mjs +2 -0
- package/library/actions/mobile/ui/locators.mjs +1 -0
- package/library/actions/mobile/ui/native-navigation.mjs +2 -0
- package/library/actions/mobile/ui/navigate.mjs +3 -1
- package/library/actions/mobile/wallet/ensure_unlocked.mjs +2 -0
- package/library/actions/mobile/wallet/home.mjs +1 -0
- package/library/actions/mobile/wallet/import.mjs +2 -0
- package/library/actions/mobile/wallet/list_accounts.mjs +1 -0
- package/library/actions/mobile/wallet/lock.mjs +1 -0
- package/library/actions/mobile/wallet/native-ui.mjs +2 -0
- package/library/actions/mobile/wallet/read_state.mjs +2 -0
- package/library/actions/mobile/wallet/reset-helper.mjs +1 -0
- package/library/actions/mobile/wallet/reset.mjs +1 -0
- package/library/actions/mobile/wallet/select_account.mjs +2 -0
- package/library/actions/mobile/wallet/setup.mjs +2 -0
- package/library/actions/shared/analytics/assert_events.mjs +3 -0
- package/library/actions/shared/analytics/consent.mjs +3 -0
- package/library/actions/shared/analytics/read_events.mjs +2 -0
- package/library/actions/shared/analytics/start_capture.mjs +3 -0
- package/library/actions/shared/app/network-artifact.mjs +2 -0
- package/library/actions/shared/app/network-assert.mjs +2 -0
- package/library/actions/shared/assets/visible-state.mjs +2 -0
- package/library/actions/shared/deeplink/url.mjs +3 -0
- package/library/actions/shared/networks/visible-state.mjs +2 -0
- package/library/actions/shared/perps/visible-state.mjs +16 -3
- package/library/actions/shared/swap-bridge/transaction.mjs +3 -0
- package/library/actions/shared/swap-bridge/visible-state.mjs +2 -0
- package/library/actions/shared/ui/locators.mjs +2 -0
- package/library/actions/shared/wallet/import-source.mjs +3 -0
- package/library/manifests/core.action-manifest.json +34 -2
- package/library/manifests/extension.action-manifest.json +50 -17
- package/library/manifests/mobile.action-manifest.json +11 -10
- package/library/recipes/core/perps/smoke.recipe.json +23 -0
- package/library/recipes/core/perps/snapshot.recipe.json +26 -1
- package/library/recipes/extension/assets/release-custom-network-token-persistence.recipe.json +25 -8
- package/library/recipes/extension/performance/navigation-memory-suite.recipe.json +219 -0
- package/library/recipes/extension/performance/navigation-memory.recipe.json +61 -156
- package/library/recipes/extension/wallet/smoke.recipe.json +17 -8
- package/library/recipes/mobile/app/lifecycle-smoke.recipe.json +89 -0
- package/library/recipes/mobile/networks/release-custom-network-persistence.recipe.json +31 -6
- package/library/recipes/mobile/networks/release-multichain-visibility.recipe.json +5 -4
- package/library/recipes/mobile/wallet/smoke.recipe.json +17 -8
- package/library/recipes/{assets → shared/assets}/release-token-address-import.recipe.json +7 -0
- package/library/recipes/{assets → shared/assets}/release-token-details.recipe.json +2 -2
- package/library/recipes/shared/assets/release-token-hide-readd.recipe.json +91 -0
- package/library/recipes/{assets → shared/assets}/release-token-persistence.recipe.json +3 -0
- package/library/recipes/{assets → shared/assets}/release-token-search-import.recipe.json +4 -1
- package/library/recipes/{assets → shared/assets}/release-token-sorting.recipe.json +13 -2
- package/library/recipes/{swap-bridge → shared/swap-bridge}/release-quote.recipe.json +29 -3
- package/package.json +3 -3
- package/site/assets/help-recipes.json +220 -27
- package/library/recipes/assets/release-token-hide-readd.recipe.json +0 -57
- package/library/recipes/core/perps/clean-market-testnet.recipe.json +0 -44
- package/library/recipes/core/perps/order-lifecycle.recipe.json +0 -82
- package/library/recipes/core/perps/read-markets.recipe.json +0 -36
- package/library/recipes/core/perps/trading-lifecycle.recipe.json +0 -80
- package/library/recipes/extension/perps/release-activity-filters.recipe.json +0 -51
- package/library/recipes/extension/perps/release-funds-flows.recipe.json +0 -76
- package/library/recipes/extension/perps/release-live-limit-order.recipe.json +0 -109
- package/library/recipes/extension/perps/release-market-filters.recipe.json +0 -97
- package/library/recipes/extension/perps/release-order-entry.recipe.json +0 -158
- package/library/recipes/extension/perps/release-order-validation.recipe.json +0 -259
- package/library/recipes/extension/perps/release-visible-consistency.recipe.json +0 -47
- package/library/recipes/extension/perps/source-dev-snapshot-consistency.recipe.json +0 -76
- package/library/recipes/mobile/app/lifecycle.android-smoke.recipe.json +0 -69
- package/library/recipes/mobile/perps/chase-assert-running.recipe.json +0 -110
- package/library/recipes/mobile/perps/chase-place.recipe.json +0 -145
- package/library/recipes/mobile/perps/chase-terminate.recipe.json +0 -98
- package/library/recipes/mobile/perps/performance.recipe.json +0 -722
- package/library/recipes/mobile/perps/pro-order-setup.recipe.json +0 -111
- package/library/recipes/mobile/perps/pro-order-start-state.recipe.json +0 -53
- package/library/recipes/mobile/perps/scale-assert-orders.recipe.json +0 -126
- package/library/recipes/mobile/perps/scale-place.recipe.json +0 -186
- package/library/recipes/mobile/perps/twap-assert-active.recipe.json +0 -97
- package/library/recipes/mobile/perps/twap-place.recipe.json +0 -146
- package/library/recipes/mobile/runner/action-validation.recipe.json +0 -336
- package/library/recipes/perps/clean-market-testnet.recipe.json +0 -49
- package/library/recipes/perps/lifecycle.recipe.json +0 -144
- package/library/recipes/perps/release-live-market-order.recipe.json +0 -139
- package/library/recipes/perps/release-watchlist.recipe.json +0 -111
- package/library/recipes/swap-bridge/release-max-quote.recipe.json +0 -72
- /package/library/recipes/{assets → shared/assets}/release-token-list.recipe.json +0 -0
- /package/library/recipes/{perps → shared/perps}/release-market-details.recipe.json +0 -0
- /package/library/recipes/{perps → shared/perps}/release-market-search.recipe.json +0 -0
- /package/library/recipes/{swap-bridge → shared/swap-bridge}/release-custom-slippage.recipe.json +0 -0
- /package/library/recipes/{swap-bridge → shared/swap-bridge}/release-live-max-swap.recipe.json +0 -0
- /package/library/recipes/{swap-bridge → shared/swap-bridge}/release-live-swap.recipe.json +0 -0
- /package/library/recipes/{swap-bridge → shared/swap-bridge}/release-surface.recipe.json +0 -0
- /package/library/recipes/{wallet → shared/wallet}/import.recipe.json +0 -0
- /package/library/recipes/{wallet → shared/wallet}/reset-import.recipe.json +0 -0
package/docs/RECIPES.md
CHANGED
|
@@ -32,7 +32,7 @@ task proof and may call other recipes.
|
|
|
32
32
|
"paramsSchema": {
|
|
33
33
|
"type": "object",
|
|
34
34
|
"properties": {
|
|
35
|
-
"
|
|
35
|
+
"timeout_ms": { "type": "integer", "minimum": 1, "default": 15000 }
|
|
36
36
|
},
|
|
37
37
|
"additionalProperties": false
|
|
38
38
|
},
|
|
@@ -41,6 +41,7 @@ task proof and may call other recipes.
|
|
|
41
41
|
"nodes": {
|
|
42
42
|
"unlock": {
|
|
43
43
|
"action": "metamask.wallet.ensure_unlocked",
|
|
44
|
+
"timeout_ms": "{{params.timeout_ms}}",
|
|
44
45
|
"intent": "Make the fixture-backed wallet ready for use",
|
|
45
46
|
"next": "done"
|
|
46
47
|
},
|
|
@@ -54,8 +55,8 @@ Defaults are applied before validation. Root values use `key=value`; a nested
|
|
|
54
55
|
recipe call uses `action: "call"`, `ref`, and `params`.
|
|
55
56
|
|
|
56
57
|
```bash
|
|
57
|
-
mm-harness run recipe.json
|
|
58
|
-
mm-harness run recipe.json
|
|
58
|
+
mm-harness run recipe.json timeout_ms=20000 --plan
|
|
59
|
+
mm-harness run recipe.json timeout_ms=20000
|
|
59
60
|
```
|
|
60
61
|
|
|
61
62
|
Use the shared wallet setup recipe on either Mobile or Extension. `auto`
|
|
@@ -76,24 +77,53 @@ fixture. Secret values are redacted from commands and evidence. Visible imports
|
|
|
76
77
|
default to MetaMetrics off and skip Mobile's optional interests; password terms
|
|
77
78
|
are required and always accepted by the action.
|
|
78
79
|
|
|
80
|
+
### Retained-memory measurements
|
|
81
|
+
|
|
82
|
+
Budget failures retain their samples and limits in a private JSON report linked
|
|
83
|
+
from the failed node's error. Inspect that report before attributing growth or
|
|
84
|
+
changing a budget; it is supporting evidence, not a passing measurement.
|
|
85
|
+
|
|
86
|
+
Use `performance.navigation-memory` for one screen in an unlocked Extension.
|
|
87
|
+
Choose its current route/marker, sample count, and growth bounds before running:
|
|
88
|
+
|
|
89
|
+
```sh
|
|
90
|
+
mm-harness run performance.navigation-memory \
|
|
91
|
+
'screen_hash=#/settings' screen_test_id=settings-tab-bar-grouped --hud hide
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`performance.navigation-memory-suite` retains the full eight-screen audit,
|
|
95
|
+
detached-DOM check, and idle-control comparison. A one-screen slope does not
|
|
96
|
+
prove those broader claims. Check the suite's reference markers against the
|
|
97
|
+
tested build before running it.
|
|
98
|
+
|
|
79
99
|
### Extension Perps release recipes
|
|
80
100
|
|
|
81
101
|
Discover these identifiers with `run --list --adapter extension --json` and
|
|
82
102
|
inspect the exact graph with `run <id> --describe --adapter extension --json`:
|
|
83
103
|
|
|
104
|
+
Market/activity filters, visible consistency, funds previews, order-form cases,
|
|
105
|
+
live order journeys, watchlist and source-dev snapshot consistency are owned by the
|
|
106
|
+
[Perps team library](https://github.com/MetaMask/experimental-metamask-recipe-perps).
|
|
107
|
+
Register the library checkout to discover and compose those unchanged names:
|
|
108
|
+
|
|
109
|
+
```sh
|
|
110
|
+
export RECIPE_LIBRARY_PATH="perps=/path/to/experimental-metamask-recipe-perps"
|
|
111
|
+
mm-harness run perps.release-market-filters --describe --adapter extension --json
|
|
112
|
+
```
|
|
113
|
+
|
|
84
114
|
| Behavior | Canonical recipe |
|
|
85
115
|
| --- | --- |
|
|
86
116
|
| Activity filter routes | `perps.release-activity-filters` |
|
|
87
117
|
| Deposit and withdraw previews without submission | `perps.release-funds-flows` |
|
|
88
|
-
| Resting limit placement and exact cancellation | `perps.release-live-limit
|
|
89
|
-
| Filled market placement and exact position close | `perps.release-live-market
|
|
118
|
+
| Resting limit placement and exact cancellation | `perps.release-live-order order_type=limit limit_price='"<price>"'` |
|
|
119
|
+
| Filled market placement and exact position close | `perps.release-live-order order_type=market` |
|
|
90
120
|
| Market header and statistics | `perps.release-market-details` |
|
|
91
121
|
| Market categories | `perps.release-market-filters` |
|
|
92
122
|
| Market text search | `perps.release-market-search` |
|
|
93
|
-
| Long/Short and Market/Limit entry controls | `perps.release-order-entry` |
|
|
94
|
-
| Amount, leverage, keyboard, auto-close, and TP/SL validation | `perps.release-order-validation` |
|
|
123
|
+
| Long/Short and Market/Limit entry controls | `perps.release-order-entry case=configuration` |
|
|
124
|
+
| Amount, leverage, keyboard, auto-close, and TP/SL validation | `perps.release-order-entry case=validation` |
|
|
95
125
|
| Official-build rendered market/activity consistency | `perps.release-visible-consistency` |
|
|
96
|
-
| Favorite persistence and
|
|
126
|
+
| Favorite add/remove persistence and observed-baseline restoration | `perps.release-watchlist` |
|
|
97
127
|
| Hook-backed controller snapshot consistency | `perps.source-dev-snapshot-consistency` |
|
|
98
128
|
|
|
99
129
|
The source-development snapshot recipe is compatibility evidence only and must
|
|
@@ -102,11 +132,22 @@ Extension-scoped because their semantic actions are not shared release
|
|
|
102
132
|
capabilities. Keep ticket-specific orchestration task-local and compose these
|
|
103
133
|
identifiers with `call` instead of copying their graphs.
|
|
104
134
|
|
|
105
|
-
|
|
135
|
+
Watchlist proof requires a visible market-bound favorite baseline before any
|
|
136
|
+
toggle. Unknown is not false. Restoration is verified on the audited Extension
|
|
137
|
+
runtime; the audited Mobile reader cannot establish that baseline, so it must
|
|
138
|
+
stop before mutation rather than infer identity from unrelated market text.
|
|
139
|
+
|
|
140
|
+
The order-form recipe is in the registered Perps team library. Both cases require a market with no existing
|
|
106
141
|
position; the product exposes Modify/Close instead of New Long/Short for an
|
|
107
|
-
already-open market.
|
|
142
|
+
already-open market. Validation prices and leverage must match the selected
|
|
143
|
+
market's current rules; the examples are not universal defaults. Give each
|
|
144
|
+
composed case a distinct `capture_path`.
|
|
145
|
+
Source-development snapshot callers may opt into a
|
|
108
146
|
positive `minimum_changed_market_count`; the default does not require a price
|
|
109
147
|
tick, while typed-history and visible-activity agreement remain strict.
|
|
148
|
+
Use the snapshot recipe's `history` object to bound existing record dates and
|
|
149
|
+
`capture_path` to separate evidence when composing calls. Market sampling does
|
|
150
|
+
not repeatedly fetch history.
|
|
110
151
|
|
|
111
152
|
The two live recipes require explicit `confirm_live=true`. The trusted runner
|
|
112
153
|
must also verify the exact consented funding request, preflight, release proof,
|
|
@@ -126,15 +167,56 @@ using a generic UI press invalidates the cached identity proof; a fresh visible
|
|
|
126
167
|
`ensure_positions`, `ensure_orders`, and `teardown_state` only observe and assert
|
|
127
168
|
state that already exists; their schemas reject creation and cleanup inputs.
|
|
128
169
|
Extension does not expose `start_state`, readiness claims, multi-position
|
|
129
|
-
background closure
|
|
170
|
+
background closure. The former `runner.action-validation` is now a repository-only
|
|
171
|
+
maintainer exercise at `scripts/qa/mobile-action-validation.recipe.json`, outside
|
|
172
|
+
the installed catalog. It requires explicit disposable setup and reserved testnet
|
|
173
|
+
account/market inputs; it is not a full action audit or a normal task recipe. Financial
|
|
130
174
|
transitions must use an explicitly funded leaf action.
|
|
131
|
-
The
|
|
132
|
-
|
|
175
|
+
The Perps team library owns `perps.clean-market-testnet` for Mobile and Core,
|
|
176
|
+
not Extension. Register that library before discovery or execution.
|
|
177
|
+
They require explicit `account`, `market` and `exclusive_testnet_market=true`.
|
|
178
|
+
That confirmation does not reserve the market or authorize deleting someone
|
|
179
|
+
else's trades. Plan first and execute only with actual task ownership.
|
|
133
180
|
Planning or calling a recipe never weakens those gates.
|
|
134
181
|
|
|
182
|
+
### Mobile advanced-order setup
|
|
183
|
+
|
|
184
|
+
These recipes belong to the Perps team library. Register it once:
|
|
185
|
+
|
|
186
|
+
```sh
|
|
187
|
+
export RECIPE_LIBRARY_PATH="perps=/path/to/experimental-metamask-recipe-perps"
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
`perps.pro-order-setup` preserves existing orders, positions, and strategies by
|
|
191
|
+
default. Its Scale, Chase, and TWAP placement callers inherit that policy.
|
|
192
|
+
When a proof requires a clean testnet market, reserve the account/market and
|
|
193
|
+
compose explicit setup with `trading_state=clean_selected_testnet_market`.
|
|
194
|
+
This parameter never authorizes cleanup of another task's trades.
|
|
195
|
+
|
|
196
|
+
`perps.scale-assert-orders` checks the complete market order count before
|
|
197
|
+
capturing three visible rows. The generic `assert_output` action compares a
|
|
198
|
+
prior node's structured field, for example `$.matchingCount` with `eq` and
|
|
199
|
+
`{{params.orders}}`; it is not limited to command output. Count and visibility
|
|
200
|
+
still need task-owned submission IDs to prove a specific ladder.
|
|
201
|
+
|
|
202
|
+
### Fixed and Max quotes
|
|
203
|
+
|
|
204
|
+
`swap-bridge.release-quote` uses one graph for both amount modes. It defaults to
|
|
205
|
+
a fixed 0.0001 ETH-to-WETH quote on Ethereum with a 60-second transition budget.
|
|
206
|
+
Max ignores `amount` and uses the selected asset's available balance. Neither
|
|
207
|
+
mode submits a transaction.
|
|
208
|
+
|
|
209
|
+
```sh
|
|
210
|
+
mm-harness run swap-bridge.release-quote amount=0.0001
|
|
211
|
+
mm-harness run swap-bridge.release-quote amount_mode=max
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Set `capture_path` to a distinct filename when composing multiple quotes in
|
|
215
|
+
one run. Pair, chain, asset identity, and timeout inputs remain explicit options.
|
|
216
|
+
|
|
135
217
|
### Extension submission boundary
|
|
136
218
|
|
|
137
|
-
Extension currently exposes
|
|
219
|
+
Extension currently exposes non-submitting Assets and Swap/Bridge evidence plus send
|
|
138
220
|
preparation/review, quote, Max, and slippage actions. It does not expose
|
|
139
221
|
`metamask.assets.finish_send`, `metamask.swap_bridge.submit_transaction`, or
|
|
140
222
|
generic `ui.activate_and_observe`. Those operations remain unavailable until a
|
|
@@ -178,12 +260,13 @@ as supporting stills.
|
|
|
178
260
|
|
|
179
261
|
### Perps loading lifecycle validation
|
|
180
262
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
263
|
+
The Mobile loading recipe shares one graph across Android and iOS. It requires
|
|
264
|
+
the product's source-dev instrumentation; platform parity still needs live
|
|
265
|
+
validation. Register the Perps team library, then pin the device:
|
|
184
266
|
|
|
185
267
|
```bash
|
|
186
268
|
mm-harness run perps.performance \
|
|
269
|
+
--library perps=/path/to/experimental-metamask-recipe-perps \
|
|
187
270
|
--device <android-serial-or-ios-udid> \
|
|
188
271
|
account=<fixture-account-name> \
|
|
189
272
|
content_variant=trending \
|
|
@@ -196,10 +279,10 @@ The Mobile recipe HUD is shown by default, including during performance
|
|
|
196
279
|
validation. Use `--hud hide` only when a specific opaque/release proof requires
|
|
197
280
|
an unobstructed product surface; do not hide it merely to collect timings.
|
|
198
281
|
|
|
199
|
-
|
|
200
|
-
`
|
|
201
|
-
|
|
202
|
-
never
|
|
282
|
+
Declared lifecycle values are `navigate_return`, `cold_no_cache`,
|
|
283
|
+
`background_short`, `background_reconnect`, `account_switch`, and
|
|
284
|
+
`network_switch`. The graph requires an existing position or an account with
|
|
285
|
+
no positions or orders; it never trades to create either prerequisite. It checks native
|
|
203
286
|
visible content with `ui.wait_for`, captures production `[PerpsLoadProof]` and
|
|
204
287
|
WebSocket milestones, and leaves detailed TTC/DFD to the app's Sentry traces.
|
|
205
288
|
It never relabels Metro, fixture, or process-start duration as TTC.
|
|
@@ -207,6 +290,12 @@ The recipe does not label a checkout as main or candidate; pin and record the
|
|
|
207
290
|
exact checkout commit externally, then use captured source milestones to verify
|
|
208
291
|
which data path actually ran.
|
|
209
292
|
|
|
293
|
+
Warm return and short resume require resident-content milestones. The other
|
|
294
|
+
lifecycles also require live positions, orders and account streams. Each group
|
|
295
|
+
shares one capture ending, with the lifecycle and content identity preserved.
|
|
296
|
+
Cold preparation clears Perps disk/controller caches and disconnects its
|
|
297
|
+
channels before capture; an explicit process restart begins the proof.
|
|
298
|
+
|
|
210
299
|
Rules:
|
|
211
300
|
|
|
212
301
|
- Validate with `--plan` before side effects.
|
|
@@ -237,8 +326,9 @@ Locators, labels, routes, and screen structure can legitimately change, so a
|
|
|
237
326
|
failed recipe is not by itself evidence of either recipe drift or a product
|
|
238
327
|
regression.
|
|
239
328
|
|
|
240
|
-
1.
|
|
241
|
-
criteria, product contracts, or known-good evidence.
|
|
329
|
+
1. Preserve the first failure and compare it with authoritative acceptance
|
|
330
|
+
criteria, product contracts, or known-good evidence. Reproduce only after
|
|
331
|
+
confirming the action is safe to repeat.
|
|
242
332
|
2. Inspect the current product surface and resolved recipe source, then classify
|
|
243
333
|
the cause as product regression, recipe drift, fixture/runtime drift, or
|
|
244
334
|
harness defect.
|
|
@@ -268,6 +358,23 @@ If changing a value would violate the recipe's postcondition, keep it as a
|
|
|
268
358
|
documented recipe invariant rather than a parameter. Add a shared action only
|
|
269
359
|
when repeated direct access has a stable cross-task contract.
|
|
270
360
|
|
|
361
|
+
Core actions construct isolated controllers. Pass account and network into
|
|
362
|
+
each action; a separate initialization node cannot prepare the next process.
|
|
363
|
+
Skip setup that does no work, while retaining the independent read assertions.
|
|
364
|
+
Signing requires a non-empty fixture account name. Address-only inputs support
|
|
365
|
+
reads; they must not silently choose the default signing account.
|
|
366
|
+
|
|
367
|
+
The Perps team library's `perps.trading-lifecycle` combines the Core long market
|
|
368
|
+
and limit examples. The harness retains read-only `perps.smoke` and
|
|
369
|
+
`perps.snapshot` examples; trading policy belongs to the team library.
|
|
370
|
+
It requires an explicit fixture account, market, and confirmation of exclusive
|
|
371
|
+
testnet ownership. Initial assertions refuse non-flat state rather than clearing
|
|
372
|
+
it. Failure-path cleanup is guarded by completed initial assertions and remains
|
|
373
|
+
market-scoped, so it cannot safely share that account/market with another task.
|
|
374
|
+
The limit branch uses the original 30%-below-mid offset; it must prove the order
|
|
375
|
+
rests rather than assume it cannot fill. These trading paths still require live
|
|
376
|
+
validation with a reserved funded testnet account.
|
|
377
|
+
|
|
271
378
|
The protocol is authoritative:
|
|
272
379
|
<https://farmslot.io/docs/reference/recipe-protocol-v1>.
|
|
273
380
|
|
|
@@ -307,7 +414,11 @@ mm-harness run onboarding.smoke --plan
|
|
|
307
414
|
Use `--library wallet=/path/to/library` for one command. The alias is the
|
|
308
415
|
source name; without one, the directory name is used. Resolution follows the
|
|
309
416
|
explicit library order, then bundled MetaMask. Adapter-specific variants are
|
|
310
|
-
selected deterministically from `recipes/<adapter>/<domain>/`.
|
|
417
|
+
selected deterministically from `recipes/<adapter>/<domain>/`. Identical
|
|
418
|
+
cross-adapter graphs live once in `recipes/shared/<domain>/`; an adapter-specific
|
|
419
|
+
variant takes precedence. Both resolve as `<domain>.<recipe>`, without a platform
|
|
420
|
+
or `shared` prefix. Keep domains below `core`, `extension`, `mobile`, or `shared`.
|
|
421
|
+
Legacy
|
|
311
422
|
`*.<adapter>.recipe.json` paths remain readable during migration, but new and
|
|
312
423
|
migrated libraries use the adapter-first layout. A file cannot declare its
|
|
313
424
|
adapter through both forms. Every run records the root recipe, exact resolved
|
|
@@ -22,8 +22,9 @@ and trusted SHA-256. mm-harness never discovers or downloads a release.
|
|
|
22
22
|
|
|
23
23
|
For Extension, `runtime-launch` verifies the ZIP digest and manifest version,
|
|
24
24
|
extracts it safely, records archive and tree provenance, copies the exact tree
|
|
25
|
-
to an isolated runtime directory,
|
|
26
|
-
launches without building or patching the checkout.
|
|
25
|
+
to an isolated runtime directory, preserves the selected Chrome profile, and
|
|
26
|
+
launches without building or patching the checkout. A profile reset requires
|
|
27
|
+
explicit `--reset-profile`; an ordinary launch does not apply a wallet fixture.
|
|
27
28
|
|
|
28
29
|
For Android, `runtime-launch` requires one physical-device serial, verifies the
|
|
29
30
|
local APK digest, package ID, version name, and version code, installs it, and
|
|
@@ -1,26 +1,14 @@
|
|
|
1
|
+
// Load the target checkout's Perps controller and resolve fixture-backed accounts.
|
|
2
|
+
// Share headless selection, signing, formatting and adapter-output helpers.
|
|
3
|
+
|
|
1
4
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
5
|
import path from 'node:path';
|
|
3
6
|
import { pathToFileURL } from 'node:url';
|
|
4
7
|
|
|
5
8
|
import { mnemonicToAccount, privateKeyToAccount } from 'viem/accounts';
|
|
6
9
|
|
|
7
|
-
// Importing a runner .ts source signals the live-adapter contract to execute
|
|
8
|
-
// this adapter under the bundled tsx (see commandFor/importsSourceTypescript in
|
|
9
|
-
// src/live-adapter-contract.ts). The core adapter dynamic-imports the perps
|
|
10
|
-
// controller TypeScript at runtime, so it MUST run under tsx, not plain node.
|
|
11
10
|
import { walletFixturePath } from '../../harness-exports.mjs';
|
|
12
11
|
|
|
13
|
-
// Shared headless instantiation for the MetaMask `core` adapter.
|
|
14
|
-
//
|
|
15
|
-
// Slice 1: read-only. We instantiate @metamask/perps-controller against a
|
|
16
|
-
// resolved MetaMask/core checkout (context.projectRoot) and drive its standalone
|
|
17
|
-
// read path, which talks to HyperLiquid testnet over HTTP — no CDP, no bridge,
|
|
18
|
-
// no UI, no signer. The controller's standalone read methods
|
|
19
|
-
// (getPositions/getOpenOrders/getAccountState with { standalone: true,
|
|
20
|
-
// userAddress }) create their own InfoClient and never touch the messenger, so
|
|
21
|
-
// reads need ZERO external action handlers. Signing/account-resolution through
|
|
22
|
-
// the messenger is Slice 2.
|
|
23
|
-
|
|
24
12
|
function controllerEntry(projectRoot) {
|
|
25
13
|
return pathToFileURL(path.join(projectRoot, 'packages/perps-controller/src/index.ts')).href;
|
|
26
14
|
}
|
|
@@ -498,6 +486,16 @@ function registerSignerHandlers(rootMessenger, childMessenger, account) {
|
|
|
498
486
|
* @returns { controller, projectRoot, network, accountAddress, signerAddress }.
|
|
499
487
|
*/
|
|
500
488
|
export async function getCoreControllerWithSigner(input) {
|
|
489
|
+
const requestedName = input.node?.account_name ?? input.node?.account;
|
|
490
|
+
if (requestedName !== undefined && (
|
|
491
|
+
typeof requestedName !== 'string' || !requestedName.trim() ||
|
|
492
|
+
/^0x[0-9a-fA-F]{40}$/u.test(requestedName.trim())
|
|
493
|
+
)) {
|
|
494
|
+
throw new Error(
|
|
495
|
+
'Core signing requires a non-empty fixture account name, not an address.\n' +
|
|
496
|
+
'Next: pass account=<reserved-fixture-name>; use addresses only for read-only actions.',
|
|
497
|
+
);
|
|
498
|
+
}
|
|
501
499
|
const base = await getCoreController(input);
|
|
502
500
|
const { controller, messenger: childMessenger, rootMessenger, accountAddress } = base;
|
|
503
501
|
if (!rootMessenger || !childMessenger) {
|
|
@@ -580,12 +578,7 @@ export function symbolForItem(item) {
|
|
|
580
578
|
return normalizeMarketSymbol(item?.symbol ?? item?.coin ?? '');
|
|
581
579
|
}
|
|
582
580
|
|
|
583
|
-
//
|
|
584
|
-
// Positions report side as long/short already; OPEN ORDERS report it as buy/sell
|
|
585
|
-
// (a resting BUY = long direction, a resting SELL = short). Mapping both onto
|
|
586
|
-
// long/short lets the shared `side` selector filter positions and orders alike —
|
|
587
|
-
// without this, requesting side:"long" silently drops every order (whose side is
|
|
588
|
-
// "buy"), which is the bug this normalization fixes.
|
|
581
|
+
// Orders carry buy/sell sides; Position.size is signed and authoritative.
|
|
589
582
|
function normalizeSide(rawSide) {
|
|
590
583
|
const side = String(rawSide ?? '').toLowerCase();
|
|
591
584
|
if (side === 'buy' || side === 'b') return 'long';
|
|
@@ -593,7 +586,14 @@ function normalizeSide(rawSide) {
|
|
|
593
586
|
return side;
|
|
594
587
|
}
|
|
595
588
|
|
|
596
|
-
function sideForItem(item) {
|
|
589
|
+
function sideForItem(item, itemType) {
|
|
590
|
+
if (itemType === 'position') {
|
|
591
|
+
const rawSize = item?.size ?? item?.szi;
|
|
592
|
+
if (typeof rawSize !== 'string' && typeof rawSize !== 'number') return '';
|
|
593
|
+
const size = Number(rawSize);
|
|
594
|
+
if (!Number.isFinite(size) || size === 0) return '';
|
|
595
|
+
return size > 0 ? 'long' : 'short';
|
|
596
|
+
}
|
|
597
597
|
return normalizeSide(item?.side ?? item?.direction ?? '');
|
|
598
598
|
}
|
|
599
599
|
|
|
@@ -636,7 +636,7 @@ export function requireExplicitSelection(input) {
|
|
|
636
636
|
* vocabulary as the extension perps adapter. With no selector and no
|
|
637
637
|
* mode=all, returns every item (read defaults to showing all live state).
|
|
638
638
|
*/
|
|
639
|
-
export function selectedItems(input, items) {
|
|
639
|
+
export function selectedItems(input, items, itemType = 'order') {
|
|
640
640
|
const requested = configuredSymbols(input, items);
|
|
641
641
|
const selector =
|
|
642
642
|
input.node?.selector && typeof input.node.selector === 'object' ? input.node.selector : {};
|
|
@@ -645,7 +645,7 @@ export function selectedItems(input, items) {
|
|
|
645
645
|
const symbols = requested.length > 0 ? new Set(requested) : null;
|
|
646
646
|
return items.filter((item) => {
|
|
647
647
|
if (symbols && !symbols.has(symbolForItem(item))) return false;
|
|
648
|
-
if (side && sideForItem(item
|
|
648
|
+
if (side && sideForItem(item, itemType) !== side) return false;
|
|
649
649
|
return true;
|
|
650
650
|
});
|
|
651
651
|
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// Assert selected standalone orders, trigger fields and parent/child protection links.
|
|
2
|
+
// Checks observed state without placing or canceling orders.
|
|
3
|
+
|
|
1
4
|
import {
|
|
2
5
|
getCoreController,
|
|
3
6
|
isDirectRun,
|
|
@@ -8,11 +11,6 @@ import {
|
|
|
8
11
|
selectedItems,
|
|
9
12
|
} from './_controller.mjs';
|
|
10
13
|
|
|
11
|
-
// core Assert selected live Perps open orders are present or absent. Pure read
|
|
12
|
-
// over the controller's standalone getOpenOrders path (no signer / provider init
|
|
13
|
-
// needed) — throws on mismatch so the recipe fails loudly. Mirrors
|
|
14
|
-
// assert_positions.mjs.
|
|
15
|
-
//
|
|
16
14
|
// Optional expect_* fields additionally assert the TRIGGER DATA the exchange
|
|
17
15
|
// round-tripped for every matching order: expect_trigger_order_type (the
|
|
18
16
|
// normalized placement type, e.g. stop_market), expect_trigger_price,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Assert required presence, absence or count of selected standalone Perps positions.
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
getCoreController,
|
|
3
5
|
isDirectRun,
|
|
@@ -8,10 +10,6 @@ import {
|
|
|
8
10
|
selectedItems,
|
|
9
11
|
} from './_controller.mjs';
|
|
10
12
|
|
|
11
|
-
// core Assert selected live Perps positions are present or absent. Pure read
|
|
12
|
-
// over the controller's standalone path (no signer / provider init needed) —
|
|
13
|
-
// throws on mismatch so the recipe fails loudly.
|
|
14
|
-
|
|
15
13
|
export function expectedOpen(input) {
|
|
16
14
|
if (input.node?.state == null) throw new Error('metamask.perps.assert_positions requires state=open or state=none.');
|
|
17
15
|
const state = String(input.node.state).toLowerCase();
|
|
@@ -39,7 +37,7 @@ export async function assertPositions(input, expectOpen = expectedOpen(input)) {
|
|
|
39
37
|
standalone: true,
|
|
40
38
|
userAddress: accountAddress,
|
|
41
39
|
});
|
|
42
|
-
matching = selectedItems(input, positions);
|
|
40
|
+
matching = selectedItems(input, positions, 'position');
|
|
43
41
|
const reached = expectedCount === undefined
|
|
44
42
|
? matching.length > 0
|
|
45
43
|
: matching.length === Number(expectedCount);
|
|
@@ -1,5 +1,9 @@
|
|
|
1
|
+
// Cancel selected testnet orders by a fixed ID set and verify their absence.
|
|
2
|
+
// Optional expected IDs reject changed selection before signing and dispatch.
|
|
3
|
+
|
|
1
4
|
import {
|
|
2
5
|
controllerRejection,
|
|
6
|
+
getCoreController,
|
|
3
7
|
getCoreControllerWithSigner,
|
|
4
8
|
isDirectRun,
|
|
5
9
|
redactOrder,
|
|
@@ -10,21 +14,11 @@ import {
|
|
|
10
14
|
optionalParam,
|
|
11
15
|
} from './_controller.mjs';
|
|
12
16
|
|
|
13
|
-
// core Cancel selected live Perps open orders on HyperLiquid testnet by driving
|
|
14
|
-
// the headless PerpsController.cancelOrders() through the full signing/provider
|
|
15
|
-
// path (Slice 2). Mirrors close_positions.mjs: read open orders → cancel the
|
|
16
|
-
// selected subset via the controller → verify the selected orders are gone.
|
|
17
|
-
//
|
|
18
|
-
// cancelOrders() resolves the concrete orders to cancel via the ACTIVE provider's
|
|
19
|
-
// getOpenOrders() (PerpsController wires getOpenOrders: () => this.getOpenOrders()
|
|
20
|
-
// into the cancel context) and then batch-cancels by symbol. We pass the selected
|
|
21
|
-
// symbols; with mode=all (no explicit market) we cancel every open order.
|
|
22
|
-
|
|
23
17
|
function sleep(ms) {
|
|
24
18
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
25
19
|
}
|
|
26
20
|
|
|
27
|
-
async function waitForOrdersAbsent(controller, accountAddress,
|
|
21
|
+
async function waitForOrdersAbsent(controller, accountAddress, orderIds, timeoutMs) {
|
|
28
22
|
const deadline = Date.now() + timeoutMs;
|
|
29
23
|
let orders = [];
|
|
30
24
|
for (;;) {
|
|
@@ -32,7 +26,7 @@ async function waitForOrdersAbsent(controller, accountAddress, symbols, timeoutM
|
|
|
32
26
|
standalone: true,
|
|
33
27
|
userAddress: accountAddress,
|
|
34
28
|
});
|
|
35
|
-
const remaining = orders.filter((order) =>
|
|
29
|
+
const remaining = orders.filter((order) => orderIds.includes(order.orderId));
|
|
36
30
|
if (remaining.length === 0) return orders;
|
|
37
31
|
if (Date.now() >= deadline) return orders;
|
|
38
32
|
await sleep(1000);
|
|
@@ -53,7 +47,15 @@ export function cancelOrdersFailure(result) {
|
|
|
53
47
|
|
|
54
48
|
export async function closeOrders(input) {
|
|
55
49
|
requireExplicitSelection(input);
|
|
56
|
-
const
|
|
50
|
+
const expectedIds = input.node?.expected_order_ids;
|
|
51
|
+
if (expectedIds !== undefined && (!Array.isArray(expectedIds) ||
|
|
52
|
+
expectedIds.some((id) => typeof id !== 'string' || !id.trim()) ||
|
|
53
|
+
new Set(expectedIds).size !== expectedIds.length)) {
|
|
54
|
+
throw new Error('expected_order_ids must contain unique non-empty receipt IDs.');
|
|
55
|
+
}
|
|
56
|
+
const { controller, accountAddress, network } = await (expectedIds === undefined
|
|
57
|
+
? getCoreControllerWithSigner(input)
|
|
58
|
+
: getCoreController(input));
|
|
57
59
|
const timeoutMs = Number(
|
|
58
60
|
optionalParam(input.node ?? {}, 'timeout_ms', 'timeoutMs') ?? 30000,
|
|
59
61
|
);
|
|
@@ -63,6 +65,14 @@ export async function closeOrders(input) {
|
|
|
63
65
|
userAddress: accountAddress,
|
|
64
66
|
});
|
|
65
67
|
const matching = selectedItems(input, orders);
|
|
68
|
+
const checkExpectedIds = (selected) => {
|
|
69
|
+
if (expectedIds === undefined) return;
|
|
70
|
+
const actual = selected.map((order) => order.orderId);
|
|
71
|
+
if (actual.length !== expectedIds.length || actual.some((id) => !expectedIds.includes(id))) {
|
|
72
|
+
throw new Error('CORE_ORDER_RECEIPT_MISMATCH: selected live orders do not match expected_order_ids; cancellation was not dispatched.');
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
checkExpectedIds(matching);
|
|
66
76
|
const symbols = Array.from(new Set(matching.map(symbolForItem)));
|
|
67
77
|
|
|
68
78
|
if (symbols.length === 0) {
|
|
@@ -79,20 +89,29 @@ export async function closeOrders(input) {
|
|
|
79
89
|
};
|
|
80
90
|
}
|
|
81
91
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
92
|
+
const orderIds = matching.map((order) => order.orderId);
|
|
93
|
+
if (orderIds.some((id) => typeof id !== 'string' || !id.trim()) || new Set(orderIds).size !== orderIds.length) {
|
|
94
|
+
throw new Error('Selected Core orders require unique non-empty orderId values before cancellation.\nNext: mm-harness actions metamask.perps.read_orders --adapter core --json');
|
|
95
|
+
}
|
|
96
|
+
if (expectedIds !== undefined) {
|
|
97
|
+
await getCoreControllerWithSigner(input);
|
|
98
|
+
checkExpectedIds(selectedItems(input, await controller.getOpenOrders({
|
|
99
|
+
standalone: true,
|
|
100
|
+
userAddress: accountAddress,
|
|
101
|
+
})));
|
|
102
|
+
}
|
|
103
|
+
const result = await controller.cancelOrders({ orderIds });
|
|
85
104
|
const failure = cancelOrdersFailure(result);
|
|
86
105
|
if (failure !== undefined) {
|
|
87
106
|
throw controllerRejection({
|
|
88
107
|
action: 'core cancelOrders',
|
|
89
|
-
detail: `${JSON.stringify(
|
|
108
|
+
detail: `${JSON.stringify(orderIds)}: ${JSON.stringify(result)}`,
|
|
90
109
|
code: failure,
|
|
91
110
|
});
|
|
92
111
|
}
|
|
93
112
|
|
|
94
|
-
const after = await waitForOrdersAbsent(controller, accountAddress,
|
|
95
|
-
const stillOpen = after.filter((order) =>
|
|
113
|
+
const after = await waitForOrdersAbsent(controller, accountAddress, orderIds, timeoutMs);
|
|
114
|
+
const stillOpen = after.filter((order) => orderIds.includes(order.orderId));
|
|
96
115
|
if (stillOpen.length > 0) {
|
|
97
116
|
throw new Error(
|
|
98
117
|
`Expected selected orders to cancel, but ${stillOpen.length} remain: ${JSON.stringify(result)}`,
|
|
@@ -109,6 +128,7 @@ export async function closeOrders(input) {
|
|
|
109
128
|
successCount: result.successCount,
|
|
110
129
|
failureCount: result.failureCount,
|
|
111
130
|
symbols,
|
|
131
|
+
orderIds,
|
|
112
132
|
results: result.results,
|
|
113
133
|
orders: stillOpen.map(redactOrder),
|
|
114
134
|
proofPath: 'perps-controller-cancelOrders',
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
// Close selected testnet positions and verify their absence.
|
|
2
|
+
// Optional receipt guards bind the observed position, reducing side and maximum quantity.
|
|
3
|
+
|
|
1
4
|
import {
|
|
2
5
|
controllerRejection,
|
|
3
6
|
currentMarketPrice,
|
|
7
|
+
getCoreController,
|
|
4
8
|
getCoreControllerWithSigner,
|
|
5
9
|
isDirectRun,
|
|
6
10
|
redactPosition,
|
|
@@ -11,12 +15,6 @@ import {
|
|
|
11
15
|
optionalParam,
|
|
12
16
|
} from './_controller.mjs';
|
|
13
17
|
|
|
14
|
-
// core Close selected live Perps positions on HyperLiquid testnet by driving the
|
|
15
|
-
// headless PerpsController.closePosition() per matching position (full close)
|
|
16
|
-
// through the full signing/provider path (Slice 2). Mirrors the extension
|
|
17
|
-
// adapter: read → close each matching position with a fresh price → verify the
|
|
18
|
-
// selected positions are gone.
|
|
19
|
-
|
|
20
18
|
function sleep(ms) {
|
|
21
19
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
22
20
|
}
|
|
@@ -45,7 +43,17 @@ export function closePositionsFailure(results) {
|
|
|
45
43
|
|
|
46
44
|
export async function closePositions(input) {
|
|
47
45
|
requireExplicitSelection(input);
|
|
48
|
-
const
|
|
46
|
+
const expectedPositions = input.node?.expected_positions;
|
|
47
|
+
if (expectedPositions !== undefined && (!Array.isArray(expectedPositions) || expectedPositions.length > 1 ||
|
|
48
|
+
expectedPositions.some((position) => !position || typeof position.coin !== 'string' ||
|
|
49
|
+
!position.coin.trim() || !Number.isFinite(Number(position.size)) || Number(position.size) === 0 ||
|
|
50
|
+
!Number.isFinite(Number(position.entryPrice)) || Number(position.entryPrice) <= 0) ||
|
|
51
|
+
new Set(expectedPositions.map((position) => position.coin)).size !== expectedPositions.length)) {
|
|
52
|
+
throw new Error('expected_positions must contain at most one market with nonzero signed size and positive entryPrice.');
|
|
53
|
+
}
|
|
54
|
+
const { controller, accountAddress, network } = await (expectedPositions === undefined
|
|
55
|
+
? getCoreControllerWithSigner(input)
|
|
56
|
+
: getCoreController(input));
|
|
49
57
|
const timeoutMs = Number(
|
|
50
58
|
optionalParam(input.node ?? {}, 'timeout_ms', 'timeoutMs') ?? 30000,
|
|
51
59
|
);
|
|
@@ -61,7 +69,20 @@ export async function closePositions(input) {
|
|
|
61
69
|
standalone: true,
|
|
62
70
|
userAddress: accountAddress,
|
|
63
71
|
});
|
|
64
|
-
const matching = selectedItems(input, positions);
|
|
72
|
+
const matching = selectedItems(input, positions, 'position');
|
|
73
|
+
const checkExpectedPositions = (selected) => {
|
|
74
|
+
if (expectedPositions === undefined) return;
|
|
75
|
+
if (selected.length !== expectedPositions.length || selected.some((position) => {
|
|
76
|
+
const actual = redactPosition(position);
|
|
77
|
+
const expected = expectedPositions.find((item) => item.coin === actual.coin);
|
|
78
|
+
return !expected || Number(expected.size) !== Number(actual.size) ||
|
|
79
|
+
Number(expected.entryPrice) !== Number(actual.entryPrice) ||
|
|
80
|
+
(expected.side != null && expected.side !== actual.side);
|
|
81
|
+
})) {
|
|
82
|
+
throw new Error('CORE_POSITION_RECEIPT_MISMATCH: selected live position size, side or entry price changed; close was not dispatched.');
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
checkExpectedPositions(matching);
|
|
65
86
|
const symbols = Array.from(new Set(matching.map(symbolForItem)));
|
|
66
87
|
|
|
67
88
|
if (symbols.length === 0) {
|
|
@@ -80,13 +101,29 @@ export async function closePositions(input) {
|
|
|
80
101
|
|
|
81
102
|
const results = [];
|
|
82
103
|
let successCount = 0;
|
|
104
|
+
if (expectedPositions !== undefined) await getCoreControllerWithSigner(input);
|
|
83
105
|
for (const position of matching) {
|
|
84
106
|
const symbol = symbolForItem(position);
|
|
85
107
|
const currentPrice = await currentMarketPrice(controller, symbol);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
108
|
+
if (expectedPositions !== undefined) {
|
|
109
|
+
checkExpectedPositions(selectedItems(input, await controller.getPositions({
|
|
110
|
+
standalone: true,
|
|
111
|
+
userAddress: accountAddress,
|
|
112
|
+
}), 'position'));
|
|
113
|
+
}
|
|
114
|
+
// Guarded closes keep receipt direction and quantity fixed. A venue position
|
|
115
|
+
// is netted, so matching fields cannot distinguish an identical replacement.
|
|
116
|
+
const expected = expectedPositions?.find((item) => item.coin === symbol);
|
|
117
|
+
const result = expected ? await controller.placeOrder({
|
|
118
|
+
symbol,
|
|
119
|
+
isBuy: Number(expected.size) < 0,
|
|
120
|
+
size: String(Math.abs(Number(expected.size))),
|
|
121
|
+
orderType: 'market',
|
|
122
|
+
reduceOnly: true,
|
|
123
|
+
currentPrice,
|
|
124
|
+
priceAtCalculation: currentPrice,
|
|
125
|
+
maxSlippageBps,
|
|
126
|
+
}) : await controller.closePosition({
|
|
90
127
|
symbol,
|
|
91
128
|
orderType: 'market',
|
|
92
129
|
currentPrice,
|
|
@@ -128,6 +165,10 @@ export async function closePositions(input) {
|
|
|
128
165
|
results,
|
|
129
166
|
positions: stillOpen.map(redactPosition),
|
|
130
167
|
proofPath: 'perps-controller-closePosition',
|
|
168
|
+
...(expectedPositions === undefined ? {} : {
|
|
169
|
+
expectedPositions,
|
|
170
|
+
proofPath: 'perps-controller-placeOrder-reduce-only-receipt-size',
|
|
171
|
+
}),
|
|
131
172
|
};
|
|
132
173
|
}
|
|
133
174
|
|