@deeeed/metamask-harness 0.37.0 → 0.38.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 +27 -0
- package/adapters/extension/artifact-runtime-state.cjs +13 -3
- package/adapters/extension/inject.mjs +1 -0
- package/adapters/extension/launch-browser.cjs +2 -0
- package/adapters/extension/live.sh +1 -1
- package/adapters/extension/readiness.mjs +1 -1
- package/adapters/manifest.json +8 -0
- package/dist/adapters/extension/runtime-decision.js +4 -1
- package/dist/adapters/extension/runtime.js +42 -3
- package/dist/adapters/mobile/provision.js +38 -3
- package/dist/adapters.js +68 -3
- package/dist/app-lifecycle.js +4 -3
- package/dist/commands/call.js +4 -1
- package/dist/commands/launch/extension.js +21 -5
- package/dist/commands/launch/index.js +3 -0
- package/dist/commands/run-engine.js +40 -29
- package/dist/commands/run.js +1 -1
- package/dist/recipe-security.js +40 -0
- package/dist/runner.js +3 -0
- package/docs/RELEASE-QA-CAPABILITY-MAP.md +116 -141
- package/library/actions/extension/assets/finish_send.mjs +128 -0
- package/library/actions/extension/assets/import_custom_token.mjs +227 -0
- package/library/actions/extension/assets/prepare_send.mjs +53 -0
- package/library/actions/extension/assets/read_details.mjs +57 -0
- package/library/actions/extension/assets/read_visible_state.mjs +50 -0
- package/library/actions/extension/assets/review_send.mjs +23 -0
- package/library/actions/extension/assets/select_network_scope.mjs +30 -0
- package/library/actions/extension/assets/send.mjs +104 -0
- package/library/actions/extension/assets/set_custom_gas.mjs +56 -0
- package/library/actions/extension/assets/set_token_visibility.mjs +129 -0
- package/library/actions/extension/assets/verify_sorting.mjs +89 -0
- package/library/actions/extension/networks/add_chainlist.mjs +94 -0
- package/library/actions/extension/networks/add_custom.mjs +123 -0
- package/library/actions/extension/networks/custom_network.mjs +103 -0
- package/library/actions/extension/networks/read_visible_state.mjs +50 -0
- package/library/actions/extension/networks/remove_custom.mjs +68 -0
- package/library/actions/extension/perps/close_orders.mjs +1 -0
- package/library/actions/extension/perps/close_visible_position.mjs +133 -0
- package/library/actions/extension/perps/compare_provider_market.mjs +109 -0
- package/library/actions/extension/perps/edit_margin.mjs +82 -0
- package/library/actions/extension/perps/open_balance_action.mjs +58 -0
- package/library/actions/extension/perps/open_position_action.mjs +80 -0
- package/library/actions/extension/perps/perps.mjs +194 -2
- package/library/actions/extension/perps/place_order.mjs +5 -2
- package/library/actions/extension/perps/read_eligibility.mjs +34 -0
- package/library/actions/extension/perps/read_funds_confirmation.mjs +69 -0
- package/library/actions/extension/perps/read_visible_state.mjs +98 -0
- package/library/actions/extension/perps/select_activity_filter.mjs +44 -0
- package/library/actions/extension/perps/select_market_filter.mjs +71 -0
- package/library/actions/extension/perps/set_market_favorite.mjs +47 -0
- package/library/actions/extension/perps/update_position_tpsl.mjs +106 -0
- package/library/actions/extension/platform/cdp.mjs +104 -1
- package/library/actions/extension/settings/set_basic_functionality.mjs +58 -0
- package/library/actions/extension/swap_bridge/read_visible_state.mjs +71 -0
- package/library/actions/extension/swap_bridge/select_assets.mjs +103 -0
- package/library/actions/extension/swap_bridge/set_amount.mjs +36 -0
- package/library/actions/extension/swap_bridge/set_max_amount.mjs +58 -0
- package/library/actions/extension/swap_bridge/set_slippage.mjs +121 -0
- package/library/actions/extension/swap_bridge/submit_transaction.mjs +363 -0
- package/library/actions/extension/ui/activate_and_observe.mjs +96 -0
- package/library/actions/extension/ui/navigate.mjs +264 -8
- package/library/actions/extension/wallet/ensure_unlocked.mjs +2 -6
- package/library/actions/extension/wallet/lock.mjs +35 -0
- package/library/actions/extension/wallet/select_account.mjs +71 -7
- package/library/actions/mobile/assets/read_visible_state.mjs +37 -0
- package/library/actions/mobile/perps/read_visible_state.mjs +31 -0
- package/library/actions/mobile/platform/native-session-name.mjs +5 -0
- package/library/actions/mobile/platform/observe-ui.mjs +5 -3
- package/library/actions/mobile/swap_bridge/native-session.mjs +45 -0
- package/library/actions/mobile/swap_bridge/read_visible_state.mjs +31 -0
- package/library/actions/mobile/swap_bridge/select_assets.mjs +94 -0
- package/library/actions/mobile/swap_bridge/set_amount.mjs +73 -0
- package/library/actions/mobile/ui/native-navigation.mjs +45 -5
- package/library/actions/mobile/ui/navigate.mjs +3 -1
- package/library/actions/mobile/wallet/ensure_unlocked.mjs +17 -0
- package/library/actions/mobile/wallet/native-ui.mjs +21 -2
- package/library/actions/shared/assets/visible-state.mjs +176 -0
- package/library/actions/shared/networks/visible-state.mjs +70 -0
- package/library/actions/shared/perps/visible-state.mjs +354 -0
- package/library/actions/shared/swap-bridge/transaction.mjs +54 -0
- package/library/actions/shared/swap-bridge/visible-state.mjs +97 -0
- package/library/manifests/extension.action-manifest.json +1255 -189
- package/library/manifests/mobile.action-manifest.json +219 -1
- package/library/recipes/assets/release-custom-gas-send.recipe.json +88 -0
- package/library/recipes/assets/release-custom-network-token-persistence.recipe.json +106 -0
- package/library/recipes/assets/release-native-max-cancel.recipe.json +70 -0
- package/library/recipes/assets/release-native-max-send.recipe.json +78 -0
- package/library/recipes/assets/release-own-account-send.recipe.json +80 -0
- package/library/recipes/assets/release-token-address-import.recipe.json +47 -0
- package/library/recipes/assets/release-token-details.recipe.json +48 -0
- package/library/recipes/assets/release-token-hide-readd.recipe.json +55 -0
- package/library/recipes/assets/release-token-list.recipe.json +75 -0
- package/library/recipes/assets/release-token-persistence.recipe.json +73 -0
- package/library/recipes/assets/release-token-search-import.recipe.json +40 -0
- package/library/recipes/assets/release-token-sorting.recipe.json +29 -0
- package/library/recipes/extension/networks/release-balance-stability.recipe.json +51 -0
- package/library/recipes/extension/perps/release-live-limit-order.recipe.json +104 -0
- package/library/recipes/extension/perps/release-order-entry.recipe.json +155 -0
- package/library/recipes/extension/perps/release-order-validation.recipe.json +220 -0
- package/library/recipes/perps/release-activity-filters.recipe.json +51 -0
- package/library/recipes/perps/release-funds-flows.recipe.json +76 -0
- package/library/recipes/perps/release-live-market-order.recipe.json +122 -0
- package/library/recipes/perps/release-market-details.recipe.json +83 -0
- package/library/recipes/perps/release-market-search.recipe.json +79 -0
- package/library/recipes/perps/release-watchlist.recipe.json +99 -0
- package/library/recipes/swap-bridge/release-custom-slippage.recipe.json +90 -0
- package/library/recipes/swap-bridge/release-live-max-swap.recipe.json +99 -0
- package/library/recipes/swap-bridge/release-live-swap.recipe.json +101 -0
- package/library/recipes/swap-bridge/release-max-quote.recipe.json +72 -0
- package/library/recipes/swap-bridge/release-quote.recipe.json +72 -0
- package/library/recipes/swap-bridge/release-surface.recipe.json +43 -0
- package/package.json +6 -5
- package/scripts/validate-release-report.mjs +319 -0
package/dist/recipe-security.js
CHANGED
|
@@ -3,10 +3,19 @@ import { lstat, readFile, readdir, realpath } from "node:fs/promises";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
const READ_ONLY_CUSTOM_ACTIONS = /* @__PURE__ */ new Set([
|
|
5
5
|
"ui.locators",
|
|
6
|
+
"ui.activate_and_observe",
|
|
6
7
|
"metamask.wallet.fixture_status",
|
|
7
8
|
"metamask.wallet.validate_import",
|
|
8
9
|
"metamask.wallet.list_accounts",
|
|
9
10
|
"metamask.wallet.read_state",
|
|
11
|
+
"metamask.assets.read_visible_state",
|
|
12
|
+
"metamask.assets.read_details",
|
|
13
|
+
"metamask.networks.read_visible_state",
|
|
14
|
+
"metamask.perps.read_visible_state",
|
|
15
|
+
"metamask.perps.compare_provider_market",
|
|
16
|
+
"metamask.perps.read_eligibility",
|
|
17
|
+
"metamask.perps.read_funds_confirmation",
|
|
18
|
+
"metamask.swap_bridge.read_visible_state",
|
|
10
19
|
"metamask.perps.read_positions",
|
|
11
20
|
"metamask.perps.read_orders",
|
|
12
21
|
"metamask.perps.read_account",
|
|
@@ -19,15 +28,37 @@ const READ_ONLY_CUSTOM_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
19
28
|
"metamask.analytics.assert_events"
|
|
20
29
|
]);
|
|
21
30
|
const APP_MUTATION_CUSTOM_ACTIONS = /* @__PURE__ */ new Set([
|
|
31
|
+
"ui.activate_and_observe",
|
|
32
|
+
"metamask.assets.verify_sorting",
|
|
33
|
+
"metamask.assets.set_token_visibility",
|
|
34
|
+
"metamask.assets.import_custom_token",
|
|
35
|
+
"metamask.assets.prepare_send",
|
|
36
|
+
"metamask.assets.review_send",
|
|
37
|
+
"metamask.assets.finish_send",
|
|
38
|
+
"metamask.assets.set_custom_gas",
|
|
39
|
+
"metamask.assets.select_network_scope",
|
|
40
|
+
"metamask.wallet.lock",
|
|
41
|
+
"metamask.networks.add_custom",
|
|
42
|
+
"metamask.networks.add_chainlist",
|
|
43
|
+
"metamask.networks.remove_custom",
|
|
22
44
|
"metamask.wallet.setup",
|
|
23
45
|
"metamask.wallet.import",
|
|
24
46
|
"metamask.wallet.reset",
|
|
25
47
|
"metamask.wallet.ensure_unlocked",
|
|
26
48
|
"metamask.wallet.select_account",
|
|
49
|
+
"metamask.swap_bridge.select_assets",
|
|
50
|
+
"metamask.swap_bridge.set_amount",
|
|
51
|
+
"metamask.swap_bridge.set_max_amount",
|
|
52
|
+
"metamask.swap_bridge.set_slippage",
|
|
53
|
+
"metamask.swap_bridge.submit_transaction",
|
|
54
|
+
"metamask.settings.set_basic_functionality",
|
|
27
55
|
"metamask.deeplink.open",
|
|
28
56
|
"metamask.perps.close_positions",
|
|
29
57
|
"metamask.perps.close_orders",
|
|
30
58
|
"metamask.perps.place_order",
|
|
59
|
+
"metamask.perps.close_visible_position",
|
|
60
|
+
"metamask.perps.open_position_action",
|
|
61
|
+
"metamask.perps.edit_margin",
|
|
31
62
|
"metamask.perps.edit_order",
|
|
32
63
|
"metamask.perps.update_position_tpsl",
|
|
33
64
|
"metamask.perps.ensure_positions",
|
|
@@ -35,15 +66,24 @@ const APP_MUTATION_CUSTOM_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
35
66
|
"metamask.perps.clear_performance_caches",
|
|
36
67
|
"metamask.perps.measure_homepage_visible",
|
|
37
68
|
"metamask.perps.prepare_local_snapshot_endpoint",
|
|
69
|
+
"metamask.perps.set_market_favorite",
|
|
70
|
+
"metamask.perps.select_activity_filter",
|
|
71
|
+
"metamask.perps.select_market_filter",
|
|
72
|
+
"metamask.perps.open_balance_action",
|
|
38
73
|
"metamask.perps.start_state",
|
|
39
74
|
"metamask.perps.teardown_state",
|
|
40
75
|
// Consent changes wallet state and is therefore an app mutation.
|
|
41
76
|
"metamask.analytics.set_consent"
|
|
42
77
|
]);
|
|
43
78
|
const EXTERNAL_MUTATION_CUSTOM_ACTIONS = /* @__PURE__ */ new Set([
|
|
79
|
+
"ui.activate_and_observe",
|
|
80
|
+
"metamask.assets.finish_send",
|
|
81
|
+
"metamask.swap_bridge.submit_transaction",
|
|
44
82
|
"metamask.perps.close_positions",
|
|
45
83
|
"metamask.perps.close_orders",
|
|
46
84
|
"metamask.perps.place_order",
|
|
85
|
+
"metamask.perps.close_visible_position",
|
|
86
|
+
"metamask.perps.edit_margin",
|
|
47
87
|
"metamask.perps.edit_order",
|
|
48
88
|
"metamask.perps.update_position_tpsl",
|
|
49
89
|
"metamask.perps.ensure_positions",
|
package/dist/runner.js
CHANGED
|
@@ -246,6 +246,9 @@ function mobileSourceAwareLifecycleAdapters(adapter, lifecycle, readinessProbe =
|
|
|
246
246
|
return lifecycle.map((entry) => ({
|
|
247
247
|
...entry,
|
|
248
248
|
async execute(node, context) {
|
|
249
|
+
if (context.env?.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME === "1") {
|
|
250
|
+
return entry.execute(node, context);
|
|
251
|
+
}
|
|
249
252
|
const command = node.command ?? node.event ?? node.state;
|
|
250
253
|
const reloadsSource = command === "launch" || command === "foreground" || command === "restart";
|
|
251
254
|
const checksIosContinuity = command === "foreground" && !isAndroidLifecycle(node, context.env ?? {});
|
|
@@ -1,150 +1,125 @@
|
|
|
1
1
|
# Release QA capability map
|
|
2
2
|
|
|
3
|
-
This document maps the
|
|
4
|
-
|
|
5
|
-
a claim that the listed release cases currently pass.
|
|
6
|
-
|
|
7
|
-
Source baseline: QA repository commit
|
|
3
|
+
This document maps the current mm-harness release-validation surface to the
|
|
4
|
+
MetaMask Extension QA checklists at commit
|
|
8
5
|
[`77591c5`](https://github.com/MetaMask/experimental-mm-qa-ai-tasks/tree/77591c5998fcf6368417fd5be01871e6c9c3dc5b/tasks/rc-extension).
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
[
|
|
17
|
-
Swap and Bridge
|
|
18
|
-
[
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
6
|
+
It describes reusable capabilities, not a release verdict. A release verdict
|
|
7
|
+
must come from the case inventory, terminal traces, and completion gate for that
|
|
8
|
+
specific artifact.
|
|
9
|
+
|
|
10
|
+
The referenced plans contain 135 cases:
|
|
11
|
+
|
|
12
|
+
- Perps: [64 cases](https://github.com/MetaMask/experimental-mm-qa-ai-tasks/blob/77591c5998fcf6368417fd5be01871e6c9c3dc5b/tasks/rc-extension/perps-rc/perps-rc-regression.md)
|
|
13
|
+
- Assets: [22 cases](https://github.com/MetaMask/experimental-mm-qa-ai-tasks/blob/77591c5998fcf6368417fd5be01871e6c9c3dc5b/tasks/rc-extension/assets-rc/assets-rc-regression.md)
|
|
14
|
+
- Swap and Bridge: [39 cases](https://github.com/MetaMask/experimental-mm-qa-ai-tasks/blob/77591c5998fcf6368417fd5be01871e6c9c3dc5b/tasks/rc-extension/swap-bridge-rc/swap-bridge-rc-regression.md)
|
|
15
|
+
- Networks: [10 cases](https://github.com/MetaMask/experimental-mm-qa-ai-tasks/blob/77591c5998fcf6368417fd5be01871e6c9c3dc5b/tasks/rc-extension/networks-regression-rc/networks-regression-rc.md)
|
|
16
|
+
|
|
17
|
+
## Artifact boundary
|
|
18
|
+
|
|
19
|
+
Artifact discovery and download belong to the release-validation workflow. The
|
|
20
|
+
workflow supplies mm-harness with a local artifact, expected product identity,
|
|
21
|
+
and trusted SHA-256. mm-harness never discovers or downloads a release.
|
|
22
|
+
|
|
23
|
+
For Extension, `runtime-launch` verifies the ZIP digest and manifest version,
|
|
24
|
+
extracts it safely, records archive and tree provenance, copies the exact tree
|
|
25
|
+
to an isolated runtime directory, resets only the selected Chrome profile, and
|
|
26
|
+
launches without building or patching the checkout.
|
|
27
|
+
|
|
28
|
+
For Android, `runtime-launch` requires one physical-device serial, verifies the
|
|
29
|
+
local APK digest, package ID, version name, and version code, installs it, and
|
|
30
|
+
binds subsequent recipes to the installed APK digest and selected device. The
|
|
31
|
+
release path does not use Metro, a development bridge, or the HUD.
|
|
32
|
+
|
|
33
|
+
```sh
|
|
34
|
+
mm-harness runtime-launch \
|
|
35
|
+
--adapter extension \
|
|
36
|
+
--target /path/to/metamask-extension \
|
|
37
|
+
--cdp-port 9222 \
|
|
38
|
+
--artifact-file /path/to/metamask-chrome-X.Y.Z.zip \
|
|
39
|
+
--artifact-version X.Y.Z \
|
|
40
|
+
--artifact-sha256 <trusted-digest>
|
|
41
|
+
|
|
42
|
+
mm-harness runtime-launch \
|
|
43
|
+
--adapter mobile \
|
|
44
|
+
--platform android \
|
|
45
|
+
--target /path/to/metamask-mobile \
|
|
46
|
+
--device <physical-device-serial> \
|
|
47
|
+
--artifact-file /path/to/app.apk \
|
|
48
|
+
--artifact-version X.Y.Z \
|
|
49
|
+
--artifact-build-id <version-code> \
|
|
50
|
+
--artifact-package-id io.metamask \
|
|
51
|
+
--artifact-sha256 <trusted-digest>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Completion gate
|
|
55
|
+
|
|
56
|
+
[`scripts/validate-release-report.mjs`](../scripts/validate-release-report.mjs)
|
|
57
|
+
compares a canonical case inventory with terminal verdicts. It rejects missing
|
|
58
|
+
or unknown IDs, duplicate IDs, non-terminal statuses, absent evidence, evidence
|
|
59
|
+
outside the declared root, incomplete run directories, and any non-empty
|
|
60
|
+
`remainingCases`. A passing gate writes `metadata.json`,
|
|
61
|
+
`report-quality.json`, and `report.md`.
|
|
62
|
+
|
|
63
|
+
Supported verdicts are `PASS`, `FAIL`, `BLOCKED_FUNDS`, `BLOCKED_COST`, and
|
|
64
|
+
`BLOCKED_ENVIRONMENT`. Product failures must remain failures; infrastructure,
|
|
65
|
+
funding, and cost limits must not be reported as passes.
|
|
66
|
+
|
|
67
|
+
## Capability matrix
|
|
68
|
+
|
|
69
|
+
`AVAILABLE` means a typed action or bundled recipe covers the reusable
|
|
70
|
+
operation and produces machine-readable evidence. `PARTIAL` means the generic
|
|
71
|
+
operation exists but the complete checklist family still depends on task-local
|
|
72
|
+
composition, live funds, or deterministic external state. `MISSING` means the
|
|
73
|
+
current manifests do not expose the required durable capability.
|
|
74
|
+
|
|
75
|
+
### Perps
|
|
76
|
+
|
|
77
|
+
| Case family | Required state and behavior | Current capability | Status |
|
|
57
78
|
| --- | --- | --- | --- |
|
|
58
|
-
|
|
|
59
|
-
|
|
|
60
|
-
|
|
|
61
|
-
|
|
|
62
|
-
|
|
|
63
|
-
|
|
|
64
|
-
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
|
69
|
-
| Responsive presentation (1) | Popup-width Perps views with populated content | Traverse core views and capture surfaces; assert no clipping or overlap | **PARTIAL**: screenshots and generic UI assertions exist; no reusable Perps presentation assertion. |
|
|
70
|
-
|
|
71
|
-
The aggregate Extension Perps snapshot is a **TARGET**. It exposes account,
|
|
72
|
-
market, position, open-order, activity, and watchlist observations in one typed,
|
|
73
|
-
read-only result when the Extension background test hook is present. An
|
|
74
|
-
untouched production artifact does not expose that hook, so official-artifact
|
|
75
|
-
Perps assertions still require a product-supported observation boundary. The
|
|
76
|
-
snapshot also does not create the required start state.
|
|
77
|
-
|
|
78
|
-
## Assets: 22 Critical cases
|
|
79
|
-
|
|
80
|
-
| Case family | Required start state | Required operations and assertions | 0.36.0 coverage |
|
|
79
|
+
| Navigation, home, markets, and details | Eligible account, live catalog, market summaries, chart, statistics, and route recovery | Visible-state reads, market navigation/search/filter, provider comparison, eligibility reads, and market-detail recipes | **AVAILABLE** for visible live flows |
|
|
80
|
+
| Watchlist and activity | Existing favorites and typed activity rows | Favorite mutation, activity-filter selection, and release recipes | **AVAILABLE** |
|
|
81
|
+
| Order entry | Funded Perps balance, market/limit inputs, validation, preview, fill, and duplicate protection | Parameterized order placement plus visible order-entry and validation recipes | **PARTIAL**: live state and provider outcomes remain release-dependent |
|
|
82
|
+
| Position management | Open position, close, margin, and TP/SL changes | Visible position actions, close, edit-margin, and TP/SL actions | **PARTIAL**: exact outcome depends on live account state |
|
|
83
|
+
| Deposit and withdrawal | Wallet funds, Perps balance, confirmations, and resulting activity | Balance actions and confirmation reads with a funds-flow recipe | **PARTIAL**: funds and provider availability are external |
|
|
84
|
+
| Snapshot assertions | Positions, orders, markets, history, and watchlist | Bounded redacted snapshot when the background hook exists; visible-state reads on untouched releases | **AVAILABLE** with runtime-specific observation paths |
|
|
85
|
+
| Deterministic seeded provider state | Balance, positions, limit orders, TP/SL, history, and controlled REST/stream behavior | Existing setup fixtures do not reproduce the complete QA provider model inside an untouched release artifact | **MISSING** |
|
|
86
|
+
|
|
87
|
+
### Assets
|
|
88
|
+
|
|
89
|
+
| Case family | Required state and behavior | Current capability | Status |
|
|
81
90
|
| --- | --- | --- | --- |
|
|
82
|
-
| Token list, prices, details, sorting
|
|
83
|
-
| Token import
|
|
84
|
-
|
|
|
85
|
-
|
|
|
86
|
-
|
|
|
87
|
-
| DeFi
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
These cases intentionally form a sequential, live-funds journey. A successful
|
|
94
|
-
step changes the next step's network and balances, so generic navigation alone
|
|
95
|
-
cannot establish or prove the required state transitions.
|
|
96
|
-
|
|
97
|
-
| Case family | Required start state | Required operations and assertions | 0.36.0 coverage |
|
|
91
|
+
| Token list, prices, details, and sorting | Funded account with known holdings and price data | Visible token-list/details reads, network scope, sorting assertions, and bundled recipes | **AVAILABLE** for EVM token UI |
|
|
92
|
+
| Token search/import/hide/persistence | Known contracts and a persistent profile | Custom-token import, visibility changes, restart-safe recipes, and custom-network composition | **AVAILABLE** for Extension EVM tokens |
|
|
93
|
+
| Native/token send and gas | Funded owned accounts and live gas | Prepare, review, custom-gas, finish, and Max/cancel/send recipes | **PARTIAL**: terminal submission is gated by funds and cost policy |
|
|
94
|
+
| Aggregate Android holdings | Imported wallet with multi-chain assets | Native-accessibility token-list read on an untouched physical-device release | **AVAILABLE** |
|
|
95
|
+
| Balance streaming and aggregation arithmetic | Triggered inbound update and expected cross-network totals | Visible snapshots exist; deterministic inbound trigger and tolerance assertions are not bundled | **PARTIAL** |
|
|
96
|
+
| NFTs, DeFi, Solana send, and Bitcoin details | Indexed owned positions/assets and chain-specific transaction flows | Generic visible UI only | **MISSING** |
|
|
97
|
+
|
|
98
|
+
### Swap and Bridge
|
|
99
|
+
|
|
100
|
+
| Case family | Required state and behavior | Current capability | Status |
|
|
98
101
|
| --- | --- | --- | --- |
|
|
99
|
-
|
|
|
100
|
-
|
|
|
101
|
-
|
|
|
102
|
-
|
|
|
103
|
-
|
|
|
104
|
-
| Sequential onward bridges (5 Critical, 4 Non-Critical) | Source balance produced by the preceding swap | Bridge Max to the specified next network and await completion; assert source/destination assets, balances, and activity | **MISSING** |
|
|
105
|
-
| Quote-only networks (2 Non-Critical) | Selected network and funded source asset | Request but do not submit a quote; assert supported routes, fees, and destination | **MISSING** |
|
|
102
|
+
| Surface, asset, network, and amount selection | Supported source/destination pair | Exact asset/network selection, amount/Max input, and visible-state reads on Extension; exact native selection and amount input on Android | **AVAILABLE** |
|
|
103
|
+
| Quote and slippage | Live quote services and supported route | Surface, quote, Max-quote, and custom-slippage recipes | **AVAILABLE** |
|
|
104
|
+
| Submit and confirmation | Sufficient source balance and gas | Typed submit action with explicit confirmation policy | **PARTIAL**: live funds and cost policy decide whether submission is allowed |
|
|
105
|
+
| Sequential multi-network journey | Balances delivered by preceding swaps and bridges | Individual steps are composable, but the complete live-funds journey remains task-local and resumable by its case inventory | **PARTIAL** |
|
|
106
|
+
| Activity, balance deltas, and completed bridge tracking | Confirmed transactions and indexed activity | Visible-state evidence exists; reusable terminal activity and arithmetic assertions are incomplete | **PARTIAL** |
|
|
106
107
|
|
|
107
|
-
|
|
108
|
+
### Networks
|
|
108
109
|
|
|
109
|
-
| Case family | Required
|
|
110
|
+
| Case family | Required state and behavior | Current capability | Status |
|
|
110
111
|
| --- | --- | --- | --- |
|
|
111
|
-
|
|
|
112
|
-
|
|
|
113
|
-
|
|
|
114
|
-
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
read-only result for account, markets, positions, open orders, activity, and
|
|
126
|
-
watchlist, with selectors suitable for machine assertions. It is usable in
|
|
127
|
-
source-built runtimes that expose the background test hook; an untouched
|
|
128
|
-
production artifact must fail explicitly until it has a supported read-only
|
|
129
|
-
observation boundary.
|
|
130
|
-
3. **Deterministic Perps start state.** Add setup-time, trace-visible provider
|
|
131
|
-
doubles and seeded balance, positions, limit orders, TP/SL, activity, and
|
|
132
|
-
watchlist. Keep setup outside the proof window and never mutate product
|
|
133
|
-
state to manufacture an assertion.
|
|
134
|
-
4. **Complete Perps operations and assertions.** Add parameterized feature and
|
|
135
|
-
eligibility control, watchlist, rich order entry, position changes, margin,
|
|
136
|
-
TP/SL, deposits, withdrawals, activity filters, and stream recovery. Extend
|
|
137
|
-
shared Mobile/Extension vocabulary where both clients support the behavior.
|
|
138
|
-
5. **Release-plan completion gate.** Validate canonical case IDs, verdict
|
|
139
|
-
counts, required evidence, and explicit remaining cases before reporting a
|
|
140
|
-
terminal result.
|
|
141
|
-
6. **Assets and Networks domain surfaces.** Add shared network selection and
|
|
142
|
-
custom-network state first, then typed assets, balances, token import,
|
|
143
|
-
transaction/activity, NFT, DeFi, and non-EVM observations.
|
|
144
|
-
7. **Swap and Bridge domain surface.** Add quote, route, submit, status,
|
|
145
|
-
balance-delta, and activity actions with explicit live-funds safeguards and
|
|
146
|
-
resumable sequential state.
|
|
147
|
-
|
|
148
|
-
Completing the two current targets does **not** provide full Perps parity. The
|
|
149
|
-
64-case plan still depends on deterministic domain state, controlled provider
|
|
150
|
-
behavior, richer mutations and assertions, and a canonical completion gate.
|
|
112
|
+
| Add, select, and remove custom networks | Known RPC configuration and chain identity | Typed custom-network and Chainlist add, visible-state read, select, and remove actions | **AVAILABLE** on Extension |
|
|
113
|
+
| Custom-network tokens and persistence | Known token contract and persistent profile | Network actions compose with Assets import/visibility/persistence recipes | **AVAILABLE** on Extension |
|
|
114
|
+
| Balance stability and fiat visibility | Funded custom network and live price APIs | Visible-state reads and a bounded balance-stability recipe | **PARTIAL**: external price availability remains uncontrolled |
|
|
115
|
+
| Send Max and activity | Funded custom network and recipient | Assets send actions cover the transaction path | **PARTIAL**: exact activity and balance-delta assertions remain task-local |
|
|
116
|
+
| Bridge tracking and details | Completed supported bridge route | Swap/Bridge composition exists; reusable terminal tracking assertions are incomplete | **PARTIAL** |
|
|
117
|
+
| Android multi-network visibility | Imported wallet with holdings on multiple chains | Native Assets read reports aggregate visible holdings without Metro or a development bridge | **AVAILABLE** |
|
|
118
|
+
|
|
119
|
+
## Platform scope
|
|
120
|
+
|
|
121
|
+
The 135-case source inventory targets Extension. It must not be relabeled as an
|
|
122
|
+
Android checklist. Android release validation uses a separate case inventory
|
|
123
|
+
for the product behaviors exercised on a physical device, while reusing the
|
|
124
|
+
same action vocabulary and report gate where the clients expose equivalent
|
|
125
|
+
flows. Simulator-only evidence is not accepted for Android release claims.
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
|
+
import { activityItem, bodyState, selectors, tokenRow, transactionState, visible, waitFor } from './send.mjs';
|
|
3
|
+
|
|
4
|
+
runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
5
|
+
const decision = String(input.node?.decision || '').trim();
|
|
6
|
+
const symbol = String(input.node?.symbol || '').trim().toUpperCase();
|
|
7
|
+
const amount = String(input.node?.amount || '').trim();
|
|
8
|
+
const resumeSubmitted = input.node?.resume_submitted === true;
|
|
9
|
+
const resumedHash = String(input.node?.transaction_hash || '').trim();
|
|
10
|
+
const timeoutMs = Number(input.node?.timeout_ms ?? 120_000);
|
|
11
|
+
if (!['confirm', 'cancel'].includes(decision) || !symbol || !amount) {
|
|
12
|
+
throw new Error('metamask.assets.finish_send requires decision=confirm|cancel, symbol, and amount.');
|
|
13
|
+
}
|
|
14
|
+
if (decision === 'confirm' && input.node?.confirm_live !== true) {
|
|
15
|
+
throw new Error('metamask.assets.finish_send decision=confirm requires confirm_live=true.');
|
|
16
|
+
}
|
|
17
|
+
const control = decision === 'confirm' ? selectors.confirm : selectors.cancel;
|
|
18
|
+
let state = await bodyState(page);
|
|
19
|
+
let submittedTransaction = null;
|
|
20
|
+
let submittedNow = false;
|
|
21
|
+
if (await visible(page, control)) {
|
|
22
|
+
const beforeTransactions = decision === 'confirm' ? await transactionState(page) : null;
|
|
23
|
+
if (decision === 'confirm' && !beforeTransactions?.available) {
|
|
24
|
+
throw new Error('Transaction state is unavailable; refusing to submit without proof binding.');
|
|
25
|
+
}
|
|
26
|
+
const beforeById = new Map((beforeTransactions?.transactions ?? []).map((transaction) => [transaction.id, transaction]));
|
|
27
|
+
await page.click(control);
|
|
28
|
+
submittedNow = decision === 'confirm';
|
|
29
|
+
state = await waitFor(page, async () => {
|
|
30
|
+
const current = await bodyState(page);
|
|
31
|
+
return current.route === '' || current.route === '#/' ? current : null;
|
|
32
|
+
}, 'Wallet did not return after send decision.', timeoutMs);
|
|
33
|
+
if (decision === 'confirm') {
|
|
34
|
+
submittedTransaction = await waitFor(page, async () => {
|
|
35
|
+
const current = await transactionState(page);
|
|
36
|
+
const changed = current.transactions.filter((transaction) => {
|
|
37
|
+
const before = beforeById.get(transaction.id);
|
|
38
|
+
if (!before) return true;
|
|
39
|
+
const wasAwaitingApproval = ['unapproved', 'approved'].includes(String(before.status).toLowerCase());
|
|
40
|
+
return wasAwaitingApproval && (before.status !== transaction.status || before.hash !== transaction.hash);
|
|
41
|
+
});
|
|
42
|
+
if (changed.length > 1) {
|
|
43
|
+
throw new Error(`Send submission changed multiple transaction records: ${changed.map((transaction) => transaction.id).join(', ')}.`);
|
|
44
|
+
}
|
|
45
|
+
return changed[0] ?? null;
|
|
46
|
+
}, 'Send confirmation did not change one transaction record.', timeoutMs);
|
|
47
|
+
}
|
|
48
|
+
} else if (decision === 'cancel') {
|
|
49
|
+
throw new Error('Send cancel control was not visible.');
|
|
50
|
+
} else if (!resumeSubmitted) {
|
|
51
|
+
throw new Error('Send confirm control was not visible. Next: rerun with resume_submitted=true only if this exact transaction was already submitted.');
|
|
52
|
+
} else if (!resumedHash) {
|
|
53
|
+
throw new Error('metamask.assets.finish_send resume_submitted=true requires transaction_hash.');
|
|
54
|
+
} else if (!/^0x[0-9a-f]{64}$/iu.test(resumedHash)) {
|
|
55
|
+
throw new Error('metamask.assets.finish_send transaction_hash must be a full EVM transaction hash.');
|
|
56
|
+
}
|
|
57
|
+
if (decision === 'cancel') {
|
|
58
|
+
return { action: input.action, decision, cancelled: true, proofPath: 'trusted-pointer-visible-ui' };
|
|
59
|
+
}
|
|
60
|
+
submittedTransaction = await waitFor(page, async () => {
|
|
61
|
+
const current = await transactionState(page);
|
|
62
|
+
const transaction = submittedNow
|
|
63
|
+
? current.transactions.find((entry) => entry.id === submittedTransaction?.id)
|
|
64
|
+
: current.transactions.find((entry) => entry.hash?.toLowerCase() === resumedHash.toLowerCase());
|
|
65
|
+
if (!transaction) return null;
|
|
66
|
+
if (['failed', 'rejected', 'dropped'].includes(String(transaction.status).toLowerCase())) {
|
|
67
|
+
throw new Error(`Send transaction ${transaction.id} reached ${transaction.status}.`);
|
|
68
|
+
}
|
|
69
|
+
return String(transaction.status).toLowerCase() === 'confirmed' && /^0x[0-9a-f]{64}$/iu.test(transaction.hash || '')
|
|
70
|
+
? transaction
|
|
71
|
+
: null;
|
|
72
|
+
}, `Sent ${amount} ${symbol} did not produce one confirmed transaction record.`, timeoutMs);
|
|
73
|
+
let tokenAfter = null;
|
|
74
|
+
if (!state.route.includes('/tx/')) {
|
|
75
|
+
if (state.route.includes('/activity')) {
|
|
76
|
+
await page.click(selectors.home);
|
|
77
|
+
}
|
|
78
|
+
tokenAfter = await waitFor(page, () => tokenRow(page, symbol), `${symbol} did not reappear after send.`, timeoutMs);
|
|
79
|
+
await waitFor(page, async () => {
|
|
80
|
+
const current = await bodyState(page);
|
|
81
|
+
if (current.route.includes('/activity') || current.route.includes('/tx/')) return current;
|
|
82
|
+
await page.click(selectors.activity);
|
|
83
|
+
return null;
|
|
84
|
+
}, 'Activity did not open after send.', timeoutMs);
|
|
85
|
+
}
|
|
86
|
+
let activity = null;
|
|
87
|
+
if (!state.route.includes('/tx/')) {
|
|
88
|
+
activity = await waitFor(page, () => activityItem(page, symbol, amount), `Sent ${amount} ${symbol} did not appear in Activity.`, timeoutMs);
|
|
89
|
+
await waitFor(page, async () => {
|
|
90
|
+
const current = await bodyState(page);
|
|
91
|
+
if (current.route.includes('/tx/')) return current;
|
|
92
|
+
const currentActivity = await activityItem(page, symbol, amount);
|
|
93
|
+
if (currentActivity) {
|
|
94
|
+
activity = currentActivity;
|
|
95
|
+
await page.click(currentActivity.selector);
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}, `Sent ${amount} ${symbol} transaction details did not open.`, timeoutMs);
|
|
99
|
+
}
|
|
100
|
+
const details = await waitFor(page, async () => {
|
|
101
|
+
const current = await bodyState(page);
|
|
102
|
+
if (!current.route.includes('/tx/') || !/\bConfirmed\b/u.test(current.text)) return null;
|
|
103
|
+
return current;
|
|
104
|
+
}, `Sent ${amount} ${symbol} did not reach Confirmed.`, timeoutMs);
|
|
105
|
+
const explorerUrl = await page.evaluate(`document.querySelector('[data-testid="transaction-details-block-explorer"]')?.getAttribute('data-explorer-url') || null`);
|
|
106
|
+
const transactionHash = details.route.match(/\/(0x[0-9a-f]{64})(?:\?|$)/iu)?.[1]
|
|
107
|
+
?? details.text.match(/\b0x[0-9a-f]{64}\b/iu)?.[0]
|
|
108
|
+
?? String(explorerUrl || '').match(/\/(0x[0-9a-f]{64})(?:[/?#]|$)/iu)?.[1]
|
|
109
|
+
?? null;
|
|
110
|
+
if (!transactionHash) throw new Error('Confirmed transaction details did not expose a full transaction hash.');
|
|
111
|
+
if (transactionHash.toLowerCase() !== submittedTransaction.hash.toLowerCase()) {
|
|
112
|
+
throw new Error(`Confirmed Activity details opened ${transactionHash}, expected ${submittedTransaction.hash}.`);
|
|
113
|
+
}
|
|
114
|
+
return {
|
|
115
|
+
action: input.action,
|
|
116
|
+
decision,
|
|
117
|
+
symbol,
|
|
118
|
+
amount,
|
|
119
|
+
activity,
|
|
120
|
+
tokenAfter,
|
|
121
|
+
details,
|
|
122
|
+
explorerUrl,
|
|
123
|
+
transactionHash,
|
|
124
|
+
transactionId: submittedTransaction.id,
|
|
125
|
+
resumed: !submittedNow,
|
|
126
|
+
proofPath: 'trusted-pointer-visible-ui',
|
|
127
|
+
};
|
|
128
|
+
}));
|