@deeeed/metamask-harness 0.82.1 → 0.84.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 +48 -0
- package/README.md +8 -1
- package/adapters/core/cleanup.sh +5 -29
- package/adapters/core/controller-import-probe.mjs +26 -0
- package/adapters/manifest.json +22 -102
- package/adapters/shared/session-lock.mjs +43 -0
- package/adapters/web-dapp/metamask-signer.mjs +212 -0
- package/dist/adapters/core/actions.js +0 -3
- package/dist/adapters/core/overlay.js +1 -5
- package/dist/adapters/core/readiness.js +133 -2
- package/dist/adapters/core/surface.js +53 -41
- package/dist/adapters/extension/extension-id.js +1 -0
- package/dist/adapters/extension/fixtures.js +60 -1
- package/dist/adapters/extension/network-observer.js +19 -206
- package/dist/adapters/extension/run.js +5 -3
- package/dist/adapters/extension/runtime.js +112 -20
- package/dist/adapters/mobile/android-gfxinfo-performance.js +3 -1
- package/dist/adapters/mobile/devices.js +5 -1
- package/dist/adapters/mobile/performance-observer.js +9 -6
- package/dist/adapters/mobile/run.js +1 -1
- package/dist/adapters/mobile/surface.js +1 -2
- package/dist/adapters/performance/trace-kinds.js +36 -0
- package/dist/adapters/plugin-surface.js +34 -34
- package/dist/adapters/session-lock.js +23 -0
- package/dist/adapters/web-dapp/console-capture.js +2 -20
- package/dist/adapters/web-dapp/run.js +31 -25
- package/dist/adapters/web-dapp/surface.js +32 -108
- package/dist/command-contract.js +1363 -630
- package/dist/commands/core-readiness.js +1 -9
- package/dist/commands/doctor.js +1 -1
- package/dist/commands/launch/device.js +1 -1
- package/dist/doctor.js +5 -2
- package/dist/mm-harness-cli.js +228 -1210
- package/dist/performance-observation.js +28 -5
- package/docs/CONTRIBUTING.md +6 -4
- package/docs/cli-contract.md +1 -1
- package/docs/feature-guard.md +1 -1
- package/library/actions/core/perps/_controller.mjs +10 -0
- package/library/actions/extension/wallet/import.mjs +21 -4
- package/library/actions/extension/wallet/visible-session.mjs +32 -12
- package/library/actions/shared/console-collector.mjs +1 -1
- package/library/actions/web-dapp/app/launch.mjs +1 -1
- package/library/actions/web-dapp/platform/adapter-web-lib.mjs +18 -0
- package/library/actions/web-dapp/platform/hud.mjs +1 -1
- package/library/actions/web-dapp/platform/page.mjs +2 -3
- package/library/actions/web-dapp/platform/runtime.mjs +29 -178
- package/package.json +7 -6
- package/scripts/completions.sh +1 -1
- package/adapters/web-dapp/cleanup.mjs +0 -69
- package/adapters/web-dapp/inject.mjs +0 -75
- package/adapters/web-dapp/launch.mjs +0 -631
- package/adapters/web-dapp/lib/blocked-hosts.mjs +0 -24
- package/adapters/web-dapp/lib/cdp-client.mjs +0 -84
- package/adapters/web-dapp/lib/display.mjs +0 -115
- package/adapters/web-dapp/lib/hud.mjs +0 -148
- package/adapters/web-dapp/lib/policy.mjs +0 -79
- package/adapters/web-dapp/lib/processes.mjs +0 -187
- package/adapters/web-dapp/lib/readiness.mjs +0 -272
- package/adapters/web-dapp/stop.mjs +0 -38
- package/adapters/web-dapp/verify.mjs +0 -51
- package/adapters/web-dapp/wallet-host.mjs +0 -520
- package/dist/adapters/core/live-script-env.js +0 -36
- package/dist/adapters/extension/browser-cdp.js +0 -174
- package/dist/adapters/extension/performance-observer.js +0 -75
- package/dist/adapters/performance/cdp-trace.js +0 -406
- package/dist/adapters/performance/js-task-metrics.js +0 -35
- package/dist/adapters/web-dapp/doctor-checks.js +0 -33
- package/dist/adapters/web-dapp/launch.js +0 -87
- package/dist/adapters/web-dapp/overlay.js +0 -21
- package/dist/adapters/web-dapp/readiness.js +0 -10
- package/dist/adapters/web-dapp/stop.js +0 -20
- package/dist/async.js +0 -19
- package/dist/cli.js +0 -202
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,54 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.84.0 - 2026-10-08
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- `mm-harness` is the MetaMask preset over `@farmslot/recipe-cli`'s `createHarnessCli`: the CLI program, help, the strict command grammar, library hydration, adapter loading and dispatch come from recipe-cli; mm-harness supplies its commands, handlers and help text. Apart from the removals under Removed and the `--json-stream` refusal change below, every command, flag, help text and JSON envelope is unchanged.
|
|
10
|
+
- `mm-harness --version` (and `-v`) still prints only the harness version. `mm-harness --version --verbose` adds a second line, `@farmslot/recipe-cli <version>`, the recipe-cli it runs on, and `doctor --json` reports it as `runner.recipeCliVersion`.
|
|
11
|
+
- `src/command-contract.ts` holds the one public command table (`PUBLIC_COMMANDS`: name, aliases, summary, example, help text, grammar) and `HIDDEN_COMMANDS`; `src/cli.ts` is gone.
|
|
12
|
+
- `mm-harness --help -- --help` prints the grouped help (it printed an older usage text), and a failure inside `update`, `pin`, `help`, `tutorial`, `setup-base`, `completions` or `call <action> --help` prints its message and exits with its code instead of a stack trace.
|
|
13
|
+
- Docs: the site's tutorial template snapshots (`site/assets/template-examples.json`) are regenerated from the recipe-cook templates at Consensys/skills b115bd2, which pass `call` inputs as bare `key=value` (`call app.lifecycle command=restart`) now that `call --arg` is refused.
|
|
14
|
+
- Docs: the README's Operate section and the feature-guard machine rules say a headful run, call or web-dapp launch on a locked macOS session refuses with `SESSION_LOCKED` (and how to proceed).
|
|
15
|
+
- `run` and `call` apply `--runtime-dir` before the slot resolves, as `doctor` does: the slot context, the checkout lock and the run's runtime state come from that directory. The flag was accepted and ignored.
|
|
16
|
+
- `call` never loads a funded mutation from its command line (it never accepted the `--funding-*` flags); funded mutations run through `run`.
|
|
17
|
+
- Depend on `@farmslot/recipe-cli` 0.10.0 and `@farmslot/adapter-sdk` 0.7.1 (were 0.8.0 and 0.6.0): `createHarnessCli`'s `afterAdapterLoad` and `explainInvalidChoice` hooks (deeeed/farmslot#846), which the preset uses for the web-dapp policy fence and the missing Terminal library, one `--watcher-port` (#806) and key=value-only `call` inputs (#808). Also `@farmslot/recipe-runner` 0.27.0, `@farmslot/adapter-web` 0.6.2, `@farmslot/adapter-rn` 0.19.1 and `@farmslot/adapter-node` 0.1.4 (were 0.26.0, 0.4.1, 0.18.0 and 0.1.2). `@farmslot/agent-runtime` 0.17.0 and `@farmslot/protocol` 0.34.0 are unchanged, and the install holds one `@farmslot/adapter-sdk` and one `@farmslot/recipe-runner`. With them, `launch` prints its setup stage on stderr (a line at least every 15 s, and a no-progress notice after 60 s); `run`, `run --plan` and `call` refuse `--record-video` on an adapter without recording before anything runs (`RECORDING_UNSUPPORTED`, exit 2); and `run` lists evidence an action produced through a fallback provider (`fallbacks` in `--json` and the `--json-stream` completion, a marked line in human output).
|
|
18
|
+
- Under `--json-stream`, a refused adapter plugin (a loader or venue-policy refusal) prints NDJSON on stdout, an `error` event then `complete` with `status: fail`, where 0.83.0 printed the human line on stderr. Human and `--json` output and the exit code (2) are unchanged.
|
|
19
|
+
- `call` writes its artifacts by `run`'s rule: inside a task (`RECIPE_TASK_DIR`/`FARMSLOT_TASK_DIR`) under `<task>/artifacts/calls/<action>-<uuid>`, else `temp/recipe/calls/<action>-<uuid>`; `--artifacts-dir` still wins.
|
|
20
|
+
- The built-in `web-dapp` is adapter-web's `createWebDappAdapter` (`@farmslot/adapter-web/web-dapp`): the slot browser, wallet host, venue policy and its fence, readiness and the launch, verify, stop, inject and cleanup leaves moved there. MetaMask is its extension signer (`adapters/web-dapp/metamask-signer.mjs`: dist and fixture import, seeding, confirmation surfaces, the two readiness checks, and the MetaMask identity of the injected wallet), loaded by every web-dapp process through `RECIPE_WEB_DAPP_SIGNER_MODULE`. The launch, verify, stop, install and cleanup flags and runtime files are unchanged.
|
|
21
|
+
|
|
22
|
+
### Removed
|
|
23
|
+
|
|
24
|
+
- **BREAKING:** the web-dapp leaf scripts (`adapters/web-dapp/{launch,verify,stop,inject,cleanup,wallet-host}.mjs`) and `adapters/web-dapp/lib/*` (policy, policy fence, readiness, processes, display, HUD, CDP client, blocked hosts) are no longer in the package. They live in `@farmslot/adapter-web/web-dapp`: use its `webDappPolicy` (same name; `POLICY_VERSION` is no longer exported, and the loader enforces the version) and `createWebDappAdapter`. `mm-harness launch|verify|stop` for web-dapp targets are unchanged. recipe-terminal's own `checks/terminal-policy.test.mjs` imports the old path before its #7 (d3a98d2).
|
|
25
|
+
- **BREAKING:** `--metro-port` is gone from `run`, `call`, `doctor`, `launch` and `verify`; pass `--watcher-port`. The CLI answers `--metro-port` with `Did you mean '--watcher-port'`. `doctor`'s `metroPort` runtime-context row reads `mirrors watcherPort`.
|
|
26
|
+
- **BREAKING:** `call` no longer takes `--arg k=v`; pass action inputs as `key=value` (the documented form, which carries every value `--arg` did, leading `-`/`--` included). The CLI answers `--arg` with `Did you mean 'key=value'`. The Mobile stale-source next step is `mm-harness call app.lifecycle command=restart …`.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- Unattended Extension and web-dapp (Terminal) runs on a Mac whose login session is locked fail fast with `SESSION_LOCKED` instead of hanging until a screenshot or unlock timeout. macOS stops painting windows while the session is locked; the probe reads the on-console session's `CGSSessionScreenIsLocked` from `ioreg` (bounded to 3 s). `run` and `call` refuse before they launch or drive a headful browser (exit 3; the Extension always, web-dapp unless its running slot browser, or else `TERMINAL_HEADLESS`, is headless; a saved `browser.json` counts only while its browser process is alive and this slot's), and `run --json-stream` carries `SESSION_LOCKED` as an `error` event ahead of `RUN_PREPARE_FAILED`. The web-dapp launcher refuses to start or reuse a headful browser on a locked session (`SESSION_LOCKED: …` in the launch error), so a recipe launch step can't start one either. The Extension health check reports a stalled compositor as `SESSION_LOCKED` rather than `UI_COMPOSITOR_SUSPENDED` when the session is locked. The next step says to unlock the Mac; caffeinate keeps it awake but does not unlock it. Nothing changes when the session is unlocked, off macOS, or when the lock state can't be read.
|
|
31
|
+
- Extension validation now stops a CDP listener that is this slot's own Chrome when no runtime identity proves it (for example after a reload loses CDP): a single Chrome listener whose `--user-data-dir` resolves, by real path, inside the checkout's recipe runtime dir gets the same TERM-then-KILL as an identity-proven one, after its command line is re-read just before the kill. Any other listener (a profile outside the checkout, a non-Chrome process, several listeners, or a command line whose `--user-data-dir` can't be parsed unambiguously) is still refused, and the refusal now names each pid and the `--user-data-dir` it saw, with the next step: stop that pid if it is this slot's browser, or free the port.
|
|
32
|
+
|
|
33
|
+
## 0.83.0 - 2026-10-07
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- Site: a zoomable platform map (`site/platform.html`) of the recipe platform layers, from the Farmslot runner and adapter packages to mm-harness, recipe libraries and plugins and the Farmslot pieces that call it, with packages, versions and four request flows; linked from the front page, Architecture and Repositories.
|
|
38
|
+
|
|
39
|
+
### Changed
|
|
40
|
+
|
|
41
|
+
- The Core adapter is a MetaMask preset over `@farmslot/adapter-node` 0.1.2. The headless lifecycle, the runtime context and the overlay cleanup leaf come from the package (`adapters/core/cleanup.sh` runs its `scripts/cleanup.sh --adapter core`). Live scripts import every workspace package the checkout declares from source, through the package's tsconfig paths; that replaces four listed packages, so a stale `dist/` or a package that moved into the monorepo no longer breaks `metamask.perps.*`. mm-harness keeps Core's detection, wording, actions, install leaf, fixtures, and its dependency gate (`run`, `call`, `doctor` and `verify` still agree on `core-controller-actions`). Messages and codes are unchanged.
|
|
42
|
+
- Depend on `@farmslot/recipe-cli` 0.8.0 and `@farmslot/adapter-sdk` 0.6.0 (were 0.6.0 and 0.5.0), which classify an adapter's declared environment failures and forward a parent signal that arrives while a script's leaf starts, on `@farmslot/recipe-runner` 0.26.0, `@farmslot/adapter-rn` 0.18.0 and `@farmslot/adapter-web` 0.4.1 (were 0.25.0, 0.17.1 and 0.3.1), and on `@farmslot/adapter-node` 0.1.2 (new). The frame-metric helpers now come from `@farmslot/recipe-runner/runtime/cdp-trace`, where adapter-rn 0.18.0 moved them. `@farmslot/agent-runtime` 0.17.0 and `@farmslot/protocol` 0.34.0 are unchanged, and the install still holds one `@farmslot/recipe-runner`.
|
|
43
|
+
- Performance and network observation run on Farmslot (Farmslot #830, #834): the CDP trace collector and parser and the JavaScript-task and frame summaries come from `@farmslot/recipe-runner/runtime/cdp-trace`, and the Extension performance and network observers from `@farmslot/adapter-web/performance-observer` and `/network-observer`. `src/adapters/extension/{browser-cdp,performance-observer}.ts`, `src/adapters/performance/{cdp-trace,js-task-metrics}.ts` and `src/async.ts` are gone. mm-harness keeps the MetaMask parts: `TRACE_KINDS` per platform (`src/adapters/performance/trace-kinds.ts`), the `mmh-clock-` marker prefix, the traced UI pages (`/home.html`, then `/sidepanel.html`), `CDP_PORT` and the extension id. The network observer now gets that id too (on `web-dapp`, from the manifest key of the dist the browser loaded), so a component extension that Chrome lists first is never captured instead of MetaMask. Artifacts are unchanged. Two Extension performance errors now say "extension UI target" instead of "MetaMask UI target", and a CDP port that does not answer fails with adapter-web's connection error after retrying for 10 seconds.
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
|
|
47
|
+
- A plugin that extends `web-dapp` can no longer bind a venue policy outside its own code. The policy module and every file it imports must be files the plugin digest covers (the loader's file list for the plugin, by real path: the module's directory and the library's `actions/`), imported by literal relative path or as node built-ins. Anything else refuses the plugin with `ADAPTER_PLUGIN_INVALID` before any policy code runs. The digest of those files goes in `RECIPE_WEB_DAPP_POLICY_DIGEST`, and every process that loads the policy refuses files that changed since the command bound it. A refusal from a plugin's own checks is no longer mistaken for one from a built-in.
|
|
48
|
+
- `--adapter terminal` (or `--platform terminal`) without the Terminal recipe library is no longer reported as a typo. It fails with `CLI_ADAPTER_LIBRARY_MISSING`, names `MetaMask/experimental-metamask-recipe-terminal` (at a revision whose `recipe-library.json` declares `adapters.terminal`) and gives the command again with the library on `RECIPE_LIBRARY_PATH`. When `RECIPE_LIBRARY_PATH` already holds an older checkout of that library (named `terminal`, or holding `recipes/terminal`), it says to update that checkout. Other unknown ids are still `CLI_INVALID_OPTION_VALUE`.
|
|
49
|
+
- Core `doctor` and `verify` import the Perps controller the way `metamask.perps.*` actions do (a new required `core-controller-import` check, bounded to 60 s, run once the dependency gate passes). A partial workspace build (a package whose `dist/` is missing) used to pass readiness and then fail the first action; it now fails readiness, naming the missing module, with `cd <core> && yarn build`, and Core's runtime status reports `blocked` (`CORE_CONTROLLER_IMPORT_FAILED`) so `--print-ready`, `--expect-live` and `--fix` agree. The probe runs once per command, in its own process group, and a timeout, or a signal that ends the command, kills the whole tree. When an action still hits a missing module, Core's live adapter prints `[core/perps] environment: cannot resolve …` with the same build step, and `run`/`call` classify it `ENVIRONMENT_NOT_READY` (exit 4) instead of `APP_LOGIC_FAILURE`.
|
|
50
|
+
- The Extension validation browser that `run --launch-existing-dist` opens now seeds the slot's wallet fixture (it launches its fresh profile with `--reset-profile`); it used to open on onboarding with no wallet. A seeded launch still on onboarding after a 5-second re-check fails with `FIXTURE_SEED_NOT_APPLIED` instead of running on an empty wallet. Runs that attach to an existing runtime, and `fixtures set`, are not gated; under `--launch-existing-dist` an onboarding recipe now starts from the seeded wallet.
|
|
51
|
+
- Extension `fixtures set|reset` no longer swaps a selected release artifact for the checkout build. It used to report pass after relaunching `dist/chrome`, and the next action failed with `EXTENSION_RUNTIME_NOT_CURRENT` and a checkout-rebuild hint. While an artifact is recorded, valid or not, it now refuses before touching the browser with `FIXTURES_ARTIFACT_ACTIVE`, exit 1. For a valid artifact, `Next:` re-selects it (archive, version, SHA-256, cache, profile) with `--reset-profile`, then runs `wallet.import method=ui credential_source=fixture`, so exported `MM_HARNESS_WALLET_*` values can't import another wallet. A `--fixture`/`RECIPE_WALLET_FIXTURE` that isn't the canonical fixture is published first with `fixtures init --from <path> --force`, and the message names the slot's canonical fixture that it replaces. When the recorded archive no longer exists, `Next:` asks for it (`--artifact-file <path-to-the-artifact-zip>`) instead of printing a command that would fail. `userAction` and `Next:` are each one runnable command line. For a valid artifact, the checkout route (`runtime-launch` without an artifact, which drops the artifact record, then `fixtures set`) is offered separately, as `error.alternatives` in JSON and an `Or:` line in human output; for an invalid record it is the `Next:` itself. Commands are shell-quoted. Checkout launches are unchanged. Extension `wallet.import method=ui` also recovers the wallet locking right after onboarding (seen on a fresh 13.51.0 artifact profile while the sidepanel attaches). It used to fail the account search with `could not enter the derived release identity (route "#/unlock")`. After the usual unlock of a profile that starts locked, a relock during the account proof is unlocked once more with the fixture password and reported as `relockRecovered: true`; a further lock still fails with that route error. The visible account search also re-enters a search value that the account list drops right after onboarding (it failed with `could not enter the alternate-address control (route "#/account-list")`), in place and within the same deadline. It stops at once if the page leaves the list, and the error now names the last input failure, with addresses redacted. The no-match control and the exact match are still required.
|
|
52
|
+
|
|
5
53
|
## 0.82.1 - 2026-10-07
|
|
6
54
|
|
|
7
55
|
### Fixed
|
package/README.md
CHANGED
|
@@ -113,6 +113,13 @@ Extension `launch` keeps its incremental watcher running. Refresh the active
|
|
|
113
113
|
page after a successful rebuild; use `launch --build` only for an explicit
|
|
114
114
|
production-like LavaMoat rebuild.
|
|
115
115
|
|
|
116
|
+
macOS stops painting visible windows while the login session is locked. On a locked Mac:
|
|
117
|
+
|
|
118
|
+
- a headful Extension or web-dapp (Terminal) `run`/`call` refuses with `SESSION_LOCKED` at its runtime check, before it launches or drives the browser, instead of stalling on screenshots and UI actions;
|
|
119
|
+
- a headful web-dapp launch, including reuse of a running browser, refuses the same way.
|
|
120
|
+
|
|
121
|
+
The Extension has no headless mode, so unlock the Mac. For the web-dapp, unlock, or relaunch its browser headless (`mm-harness launch --adapter terminal --headless`, or set `TERMINAL_HEADLESS=1` before launching): `TERMINAL_HEADLESS=1` alone doesn't help while a headful slot browser is still running. caffeinate keeps a Mac awake but does not unlock it.
|
|
122
|
+
|
|
116
123
|
### Extension browser
|
|
117
124
|
|
|
118
125
|
`RECIPE_HARNESS_BROWSER` picks the browser that hosts the unpacked extension:
|
|
@@ -259,7 +266,7 @@ how `prepare --remote-flag` keeps its pin through the fixtures step.
|
|
|
259
266
|
|
|
260
267
|
```bash
|
|
261
268
|
mm-harness call metamask.feature_flags.read
|
|
262
|
-
mm-harness call metamask.feature_flags.set
|
|
269
|
+
mm-harness call metamask.feature_flags.set 'flags={"perpsEnabled":true}' # mobile
|
|
263
270
|
mm-harness call metamask.feature_flags.clear # mobile
|
|
264
271
|
```
|
|
265
272
|
|
package/adapters/core/cleanup.sh
CHANGED
|
@@ -3,35 +3,11 @@
|
|
|
3
3
|
#
|
|
4
4
|
# Inputs: --target <metamask-core> (default $PWD); env RECIPE_HARNESS_ROOT.
|
|
5
5
|
# Outputs: removes <harness>/core. Exit 0 — cleaned (idempotent); 2 — bad args.
|
|
6
|
-
#
|
|
7
|
-
#
|
|
8
|
-
# Core injects nothing into the product checkout, so cleanup only removes the
|
|
9
|
-
# ignored overlay directory temp/recipe/harness/core. There are no backups to
|
|
10
|
-
# restore and no patched product files. Idempotent: a missing overlay is a
|
|
11
|
-
# success, not an error.
|
|
6
|
+
# Binding: runs @farmslot/adapter-node's scripts/cleanup.sh --adapter core.
|
|
12
7
|
set -euo pipefail
|
|
13
|
-
|
|
14
|
-
TARGET="$PWD"
|
|
15
|
-
while [ "$#" -gt 0 ]; do
|
|
16
|
-
case "$1" in
|
|
17
|
-
--target) TARGET="$2"; shift 2 ;;
|
|
18
|
-
-h|--help) echo "Usage: cleanup.sh (core) [--target <metamask-core>]"; exit 0 ;;
|
|
19
|
-
*) echo "Unknown arg: $1" >&2; exit 2 ;;
|
|
20
|
-
esac
|
|
21
|
-
done
|
|
22
|
-
|
|
23
8
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
for _hp in "$SCRIPT_DIR/../shared/harness-path.sh"; do
|
|
27
|
-
[ -f "$_hp" ] && { . "$_hp"; break; }
|
|
28
|
-
done
|
|
29
|
-
unset _hp
|
|
30
|
-
if ! command -v harness_root >/dev/null 2>&1; then
|
|
31
|
-
echo "mm-harness: shared lib adapters/shared/harness-path.sh not found; reinstall the runner." >&2
|
|
9
|
+
NODE_CLEANUP="$(cd "$SCRIPT_DIR" && node -p "require.resolve('@farmslot/adapter-node/scripts/cleanup.sh')")" || {
|
|
10
|
+
echo "cleanup.sh: @farmslot/adapter-node is not installed next to the runner; reinstall the runner." >&2
|
|
32
11
|
exit 1
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
rm -rf "$HARNESS_DIR"
|
|
37
|
-
echo "Cleaned core recipe harness from $TARGET"
|
|
12
|
+
}
|
|
13
|
+
exec bash "$NODE_CLEANUP" --adapter core "$@"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// controller-import-probe — import what Core's metamask.perps.* live scripts
|
|
2
|
+
// import (the checkout's perps-controller and messenger sources), under the same
|
|
3
|
+
// tsx environment, and report the outcome as one JSON line on stdout.
|
|
4
|
+
//
|
|
5
|
+
// Inputs: argv[2] = the core checkout. Run by `doctor` (readiness), never by a recipe.
|
|
6
|
+
// Outputs: {"ok":true} or {"ok":false,"code":…,"url":…,"message":…}; exit 0 either way (2: no checkout).
|
|
7
|
+
// Never touches: the checkout (import only, no controller is constructed).
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { pathToFileURL } from 'node:url';
|
|
10
|
+
|
|
11
|
+
const root = process.argv[2];
|
|
12
|
+
if (!root || root === '--help' || root === '-h') {
|
|
13
|
+
console.log('Usage: controller-import-probe.mjs <core-checkout> (run under the actions\' tsx env by doctor)');
|
|
14
|
+
process.exit(root ? 0 : 2);
|
|
15
|
+
}
|
|
16
|
+
try {
|
|
17
|
+
await Promise.all([
|
|
18
|
+
import(pathToFileURL(path.join(root, 'packages/perps-controller/src/index.ts')).href),
|
|
19
|
+
import(pathToFileURL(path.join(root, 'packages/messenger/src/index.ts')).href),
|
|
20
|
+
]);
|
|
21
|
+
process.stdout.write(`${JSON.stringify({ ok: true })}\n`);
|
|
22
|
+
} catch (error) {
|
|
23
|
+
process.stdout.write(
|
|
24
|
+
`${JSON.stringify({ ok: false, code: error?.code ?? null, url: error?.url ?? null, message: String(error?.message ?? error) })}\n`,
|
|
25
|
+
);
|
|
26
|
+
}
|
package/adapters/manifest.json
CHANGED
|
@@ -386,6 +386,14 @@
|
|
|
386
386
|
"inputs": "--target; env RECIPE_HARNESS_ROOT",
|
|
387
387
|
"outputs": "removes <harness>/core; exit 0/2"
|
|
388
388
|
},
|
|
389
|
+
{
|
|
390
|
+
"id": "core/controller-import-probe",
|
|
391
|
+
"entry": "adapters/core/controller-import-probe.mjs",
|
|
392
|
+
"kind": "node",
|
|
393
|
+
"purpose": "Import the checkout's Perps controller and messenger sources the way metamask.perps.* actions do; run by doctor's core-controller-import check under the actions' tsx env.",
|
|
394
|
+
"inputs": "<core-checkout>",
|
|
395
|
+
"outputs": "one JSON line {ok,code,url,message} on stdout; exit 0 (2: no checkout)"
|
|
396
|
+
},
|
|
389
397
|
{
|
|
390
398
|
"id": "lib/harness-path",
|
|
391
399
|
"entry": "adapters/shared/harness-path.sh",
|
|
@@ -490,6 +498,14 @@
|
|
|
490
498
|
"inputs": "ESM import; env RECIPE_HARNESS_ROOT optional override",
|
|
491
499
|
"outputs": "recipeHarnessRoot() relative path string"
|
|
492
500
|
},
|
|
501
|
+
{
|
|
502
|
+
"id": "lib/session-lock",
|
|
503
|
+
"entry": "adapters/shared/session-lock.mjs",
|
|
504
|
+
"kind": "lib",
|
|
505
|
+
"purpose": "Bounded macOS locked-session probe (ioreg CGSSessionScreenIsLocked) and the SESSION_LOCKED wording, shared by the run/call preflight and the Extension health check.",
|
|
506
|
+
"inputs": "ESM import from src/adapters/session-lock.ts or src/adapters/extension/runtime.ts",
|
|
507
|
+
"outputs": "macosSessionLocked() true/false/null; parseSessionLocked(); SESSION_LOCKED_MESSAGE and SESSION_LOCKED_USER_ACTION"
|
|
508
|
+
},
|
|
493
509
|
{
|
|
494
510
|
"id": "lib/resolve-slot-ports-sh",
|
|
495
511
|
"entry": "adapters/shared/resolve-slot-ports.sh",
|
|
@@ -584,114 +600,18 @@
|
|
|
584
600
|
"outputs": "numbered checkouts, saved preferences, and run summary"
|
|
585
601
|
},
|
|
586
602
|
{
|
|
587
|
-
"id": "web-dapp/
|
|
588
|
-
"entry": "adapters/web-dapp/
|
|
589
|
-
"kind": "node",
|
|
590
|
-
"purpose": "Start or reuse the web-dapp slot browser (Chrome for Testing unless TERMINAL_CHROME_BIN; MetaMask extension with the fixture imported, or the injected strict wallet) and its wallet host. On macOS the browser starts through LaunchServices with no startup window; in testnet mode the hosts the venue policy blocks are unresolvable for the whole browser.",
|
|
591
|
-
"inputs": "--target --cdp-port --app-port [--signer extension|injected] [--account] [--fresh-profile] [--headless|--headful] [--slow-mo] [--network mainnet --mainnet-confirmation REAL_FUNDS] [--json]; env TERMINAL_CHROME_BIN, TERMINAL_EXTENSION_DIR, TERMINAL_EXTENSION_CHECKOUT, RECIPE_WALLET_FIXTURE, TERMINAL_HEADLESS, TERMINAL_SLOW_MO, TERMINAL_WINDOW, TERMINAL_SCREEN",
|
|
592
|
-
"outputs": "<runtime>/<adapter id>/browser.json + pid/log files; JSON on stdout; exit 0/1/2"
|
|
593
|
-
},
|
|
594
|
-
{
|
|
595
|
-
"id": "web-dapp/wallet-host",
|
|
596
|
-
"entry": "adapters/web-dapp/wallet-host.mjs",
|
|
597
|
-
"kind": "node",
|
|
598
|
-
"purpose": "Long-running CDP observer: opens the app tab blank, hooks it once per target, navigates it to the app and verifies the log binding before ready; logs every wallet request from the app top frame and every MetaMask confirmation shown; answers the injected strict wallet in injected mode (other frames and blank popups get 4100). The page script, the frame judgement and the strict wallet are @farmslot/adapter-web/dapp's; the venue policy (RECIPE_WEB_DAPP_POLICY) gives the testnet refusal, hosts, probe and start page.",
|
|
599
|
-
"inputs": "--cdp-port --runtime-dir --signer --app-origin [--extension-id] [--target --account --start-chain] [--browser-app <.app>]",
|
|
600
|
-
"outputs": "<runtime>/<adapter id>/wallet-requests.jsonl, wallet-host.ready"
|
|
601
|
-
},
|
|
602
|
-
{
|
|
603
|
-
"id": "web-dapp/stop",
|
|
604
|
-
"entry": "adapters/web-dapp/stop.mjs",
|
|
605
|
-
"kind": "node",
|
|
606
|
-
"purpose": "Stop the web-dapp slot browser, wallet host and console collector and prove the CDP port is free.",
|
|
607
|
-
"inputs": "--target [--cdp-port]",
|
|
608
|
-
"outputs": "JSON on stdout; exit 0/1"
|
|
609
|
-
},
|
|
610
|
-
{
|
|
611
|
-
"id": "web-dapp/inject",
|
|
612
|
-
"entry": "adapters/web-dapp/inject.mjs",
|
|
613
|
-
"kind": "node",
|
|
614
|
-
"purpose": "web-dapp install: overlay marker, runtime dir and wallet fixture (0600); refuses a checkout the venue policy does not match; no product writes.",
|
|
615
|
-
"inputs": "--target [--fixture] [--force]; env RECIPE_WALLET_FIXTURE",
|
|
616
|
-
"outputs": "<harness>/<adapter id>/install.json, <runtime>/wallet-fixture.json; exit 0/1"
|
|
617
|
-
},
|
|
618
|
-
{
|
|
619
|
-
"id": "web-dapp/cleanup",
|
|
620
|
-
"entry": "adapters/web-dapp/cleanup.mjs",
|
|
621
|
-
"kind": "node",
|
|
622
|
-
"purpose": "web-dapp cleanup: stop browser/host, remove the slot's runtime state, overlay marker and installed fixture; profiles kept unless --reset-profile.",
|
|
623
|
-
"inputs": "--target [--cdp-port] [--reset-profile]",
|
|
624
|
-
"outputs": "JSON on stdout; exit 0/1"
|
|
625
|
-
},
|
|
626
|
-
{
|
|
627
|
-
"id": "web-dapp/verify",
|
|
628
|
-
"entry": "adapters/web-dapp/verify.mjs",
|
|
629
|
-
"kind": "node",
|
|
630
|
-
"purpose": "Read-only web-dapp slot readiness: checkout, deps, dev server, forced testnet, fixture, browser start probe, extension copy, CDP port.",
|
|
631
|
-
"inputs": "--target [--cdp-port] [--watcher-port] [--signer] [--account] [--json]",
|
|
632
|
-
"outputs": "readiness report; exit 0 ready / 1 not ready"
|
|
633
|
-
},
|
|
634
|
-
{
|
|
635
|
-
"id": "web-dapp/lib-cdp-client",
|
|
636
|
-
"entry": "adapters/web-dapp/lib/cdp-client.mjs",
|
|
637
|
-
"kind": "lib",
|
|
638
|
-
"purpose": "Browser-level CDP client with flattened target sessions for the wallet host.",
|
|
639
|
-
"inputs": "CdpClient.connect(url)",
|
|
640
|
-
"outputs": "send/on"
|
|
641
|
-
},
|
|
642
|
-
{
|
|
643
|
-
"id": "web-dapp/lib-processes",
|
|
644
|
-
"entry": "adapters/web-dapp/lib/processes.mjs",
|
|
645
|
-
"kind": "lib",
|
|
646
|
-
"purpose": "Pid-file process stop with owner-marker checks (--mm-harness-owner=<kind>-<id>, exact token; live foreign pids kept and reported, dead ones dropped), process-group termination, CDP port release check.",
|
|
647
|
-
"inputs": "stopWebDappBrowser(target, { cdpPort }), stopConsoleCollector(target), ownerMarker(kind, runtimeDir), ownsProcess(kind, pid, runtimeDir)",
|
|
648
|
-
"outputs": "stopped processes"
|
|
649
|
-
},
|
|
650
|
-
{
|
|
651
|
-
"id": "web-dapp/lib-readiness",
|
|
652
|
-
"entry": "adapters/web-dapp/lib/readiness.mjs",
|
|
653
|
-
"kind": "lib",
|
|
654
|
-
"purpose": "web-dapp slot readiness checks shared by verify, doctor and the surface.",
|
|
655
|
-
"inputs": "webDappReadiness({ policy, target, appPort, cdpPort, signer }), assertLaunchNetwork(target, { network, mainnetConfirmation }), devServerTestnetDiagnostic(target, { appPort, variable }) (advisory)",
|
|
656
|
-
"outputs": "checks[]"
|
|
657
|
-
},
|
|
658
|
-
{
|
|
659
|
-
"id": "web-dapp/lib-display",
|
|
660
|
-
"entry": "adapters/web-dapp/lib/display.mjs",
|
|
661
|
-
"kind": "lib",
|
|
662
|
-
"purpose": "Headless/headful and slow-mo resolution, per-slot window placement and the profile zoom that keeps the desktop layout.",
|
|
663
|
-
"inputs": "resolveHeadless, resolveSlowMo, windowPlacement, writeProfileZoom",
|
|
664
|
-
"outputs": "browser display args"
|
|
665
|
-
},
|
|
666
|
-
{
|
|
667
|
-
"id": "web-dapp/lib-hud",
|
|
668
|
-
"entry": "adapters/web-dapp/lib/hud.mjs",
|
|
669
|
-
"kind": "lib",
|
|
670
|
-
"purpose": "Recipe HUD state (<runtime>/hud.json) and the page expression that draws it on the app origin only.",
|
|
671
|
-
"inputs": "nextHudState, hudRenderExpression",
|
|
672
|
-
"outputs": "HUD state, expression source"
|
|
673
|
-
},
|
|
674
|
-
{
|
|
675
|
-
"id": "web-dapp/lib-blocked-hosts",
|
|
676
|
-
"entry": "adapters/web-dapp/lib/blocked-hosts.mjs",
|
|
677
|
-
"kind": "lib",
|
|
678
|
-
"purpose": "Testnet enforcement mechanics: the host-resolver rule that makes the policy's blocked hosts unresolvable, and the canonical host check the wallet host's request block uses.",
|
|
679
|
-
"inputs": "hostResolverRules(hosts), hostOf(url), isBlockedUrl(url, hosts)",
|
|
680
|
-
"outputs": "launch switch, booleans"
|
|
681
|
-
},
|
|
682
|
-
{
|
|
683
|
-
"id": "web-dapp/lib-policy",
|
|
684
|
-
"entry": "adapters/web-dapp/lib/policy.mjs",
|
|
603
|
+
"id": "web-dapp/metamask-signer",
|
|
604
|
+
"entry": "adapters/web-dapp/metamask-signer.mjs",
|
|
685
605
|
"kind": "lib",
|
|
686
|
-
"purpose": "
|
|
687
|
-
"inputs": "env
|
|
688
|
-
"outputs": "
|
|
606
|
+
"purpose": "MetaMask as @farmslot/adapter-web web-dapp's extension signer (load the slot's MetaMask dist, import the fixture account through wallet-fixture-state.cjs generate, prefill-profile and seed-cdp, judge MetaMask's confirmation surfaces, readiness checks for the dist and fixture tooling) and the MetaMask identity the injected strict wallet presents.",
|
|
607
|
+
"inputs": "loaded by the web-dapp leaves through RECIPE_WEB_DAPP_SIGNER_MODULE or --signer-module; env TERMINAL_EXTENSION_DIR, TERMINAL_EXTENSION_CHECKOUT, TERMINAL_EXTENSION_NODE",
|
|
608
|
+
"outputs": "signers.extension (prepareProfile, confirm, readinessChecks) and signers.injected.identity"
|
|
689
609
|
},
|
|
690
610
|
{
|
|
691
611
|
"id": "web-dapp/surface",
|
|
692
612
|
"entry": "src/adapters/web-dapp/surface.ts",
|
|
693
613
|
"kind": "module",
|
|
694
|
-
"purpose": "The web-dapp adapter
|
|
614
|
+
"purpose": "The web-dapp adapter: @farmslot/adapter-web's createWebDappAdapter with the MetaMask signer module, the web-dapp action set, console capture, the locked-session preflight and observation, plus the MetaMask-only surface members; bindWebDappPolicy binds the venue policy and the signer module of the adapter that extends it.",
|
|
695
615
|
"inputs": "webDappSurface",
|
|
696
616
|
"outputs": "AdapterSurface"
|
|
697
617
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// Whether the macOS login session is locked. macOS stops painting visible
|
|
2
|
+
// windows while it is, so a headful browser's screenshots, video and UI actions
|
|
3
|
+
// stall until their own timeouts. The run/call preflight (src/adapters/
|
|
4
|
+
// session-lock.ts) and the web-dapp launcher refuse up front instead.
|
|
5
|
+
|
|
6
|
+
import { spawnSync } from 'node:child_process';
|
|
7
|
+
|
|
8
|
+
export const SESSION_LOCKED_MESSAGE =
|
|
9
|
+
"The macOS login session is locked, so the headful browser can't paint (screenshots, video and UI actions stall).";
|
|
10
|
+
export const SESSION_LOCKED_USER_ACTION =
|
|
11
|
+
'Unlock the Mac and keep its login session unlocked while the run drives the browser, then rerun. ' +
|
|
12
|
+
'caffeinate keeps the Mac awake but does not unlock it.';
|
|
13
|
+
|
|
14
|
+
const LOCKED = /<key>CGSSessionScreenIsLocked<\/key>\s*<true\/>/u;
|
|
15
|
+
const ON_CONSOLE = /<key>kCGSSessionOnConsoleKey<\/key>\s*<true\/>/u;
|
|
16
|
+
|
|
17
|
+
// `ioreg -n Root -d1 -a` output: the lock flag of the on-console session in
|
|
18
|
+
// IOConsoleUsers (any session's when none is on console); false for a plist
|
|
19
|
+
// without it, null for anything else.
|
|
20
|
+
export function parseSessionLocked(output) {
|
|
21
|
+
if (!/<plist[\s>]/u.test(output)) return null;
|
|
22
|
+
const users = /<key>IOConsoleUsers<\/key>\s*<array>([\s\S]*?)<\/array>/u.exec(output)?.[1] ?? '';
|
|
23
|
+
const onConsole = users.match(/<dict>[\s\S]*?<\/dict>/gu)?.find((session) => ON_CONSOLE.test(session));
|
|
24
|
+
return LOCKED.test(onConsole ?? output);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// true or false on macOS; null when unknown (another platform, or ioreg failed
|
|
28
|
+
// or took longer than 3 s). Never throws.
|
|
29
|
+
export function macosSessionLocked() {
|
|
30
|
+
if (process.platform !== 'darwin') return null;
|
|
31
|
+
try {
|
|
32
|
+
const result = spawnSync('ioreg', ['-n', 'Root', '-d1', '-a'], {
|
|
33
|
+
encoding: 'utf8',
|
|
34
|
+
timeout: 3_000,
|
|
35
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
36
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
37
|
+
});
|
|
38
|
+
if (result.error || result.status !== 0) return null;
|
|
39
|
+
return parseSessionLocked(result.stdout);
|
|
40
|
+
} catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
// metamask-signer.mjs — MetaMask as web-dapp's extension signer, and the
|
|
2
|
+
// identity the injected strict wallet presents.
|
|
3
|
+
//
|
|
4
|
+
// Purpose:
|
|
5
|
+
// @farmslot/adapter-web/web-dapp runs the slot browser and wallet host; this
|
|
6
|
+
// module is the signer module it loads (RECIPE_WEB_DAPP_SIGNER_MODULE or
|
|
7
|
+
// --signer-module). signer=extension loads the slot's copy of the MetaMask
|
|
8
|
+
// extension and imports the fixture account through the Extension adapter's
|
|
9
|
+
// fixture flow (wallet-fixture-state.cjs generate → prefill-profile →
|
|
10
|
+
// seed-cdp); the wallet host then logs every MetaMask confirmation it sees
|
|
11
|
+
// open. signer=injected presents the strict test wallet as MetaMask, so the
|
|
12
|
+
// app connects to it the way it connects to the extension.
|
|
13
|
+
//
|
|
14
|
+
// Inputs: env TERMINAL_EXTENSION_DIR (the MetaMask dist, default
|
|
15
|
+
// <checkout>/temp/metamask-extension/chrome), TERMINAL_EXTENSION_CHECKOUT (an
|
|
16
|
+
// installed metamask-extension checkout at the dist commit),
|
|
17
|
+
// TERMINAL_EXTENSION_NODE (that checkout's node; else asdf's install for its .nvmrc).
|
|
18
|
+
// Outputs: the `signers` hooks; per-slot key files in the runtime dir, which the
|
|
19
|
+
// launcher removes when the launch ends.
|
|
20
|
+
// Never prints: mnemonics, private keys, the wallet password.
|
|
21
|
+
|
|
22
|
+
import { execFileSync } from 'node:child_process';
|
|
23
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
24
|
+
import os from 'node:os';
|
|
25
|
+
import path from 'node:path';
|
|
26
|
+
import { fileURLToPath } from 'node:url';
|
|
27
|
+
|
|
28
|
+
import { fixtureEntry, readinessCheck } from '../../library/actions/web-dapp/platform/adapter-web-lib.mjs';
|
|
29
|
+
|
|
30
|
+
const HARNESS_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
|
|
31
|
+
const FIXTURE_STATE_SCRIPT = path.join(HARNESS_ROOT, 'adapters/extension/wallet-fixture-state.cjs');
|
|
32
|
+
// adapter-web's LAUNCH_SERVICES launch method, named as the launch leaf names it.
|
|
33
|
+
const LAUNCH_SERVICES = 'launch-services';
|
|
34
|
+
|
|
35
|
+
function extensionDirFor(target, env) {
|
|
36
|
+
return path.resolve(env.TERMINAL_EXTENSION_DIR || path.join(target, 'temp/metamask-extension/chrome'));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Node of the extension checkout (its native modules, e.g. classic-level, are
|
|
40
|
+
// built for that ABI): TERMINAL_EXTENSION_NODE, else asdf's install for .nvmrc.
|
|
41
|
+
export function resolveExtensionNode(checkout, env = process.env) {
|
|
42
|
+
if (env.TERMINAL_EXTENSION_NODE) return env.TERMINAL_EXTENSION_NODE;
|
|
43
|
+
const nvmrc = path.join(checkout, '.nvmrc');
|
|
44
|
+
if (existsSync(nvmrc)) {
|
|
45
|
+
const wanted = readFileSync(nvmrc, 'utf8').trim().replace(/^v/u, '');
|
|
46
|
+
const root = path.join(os.homedir(), '.asdf/installs/nodejs');
|
|
47
|
+
if (existsSync(root)) {
|
|
48
|
+
const match = readdirSync(root)
|
|
49
|
+
.filter((version) => version === wanted || version.startsWith(`${wanted}.`))
|
|
50
|
+
.sort((a, b) => b.localeCompare(a, undefined, { numeric: true }))[0];
|
|
51
|
+
if (match) return path.join(root, match, 'bin/node');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return process.execPath;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// The Extension vault needs a mnemonic keyring, so a privateKey account is
|
|
58
|
+
// imported beside the fixture's first mnemonic account and selected.
|
|
59
|
+
export function slotWalletFixture(fixture, accountName) {
|
|
60
|
+
const entry = fixtureEntry(fixture, accountName);
|
|
61
|
+
const accounts = [];
|
|
62
|
+
if (entry.type === 'privateKey') {
|
|
63
|
+
const primary = fixture.accounts.find((account) => account?.type === 'mnemonic');
|
|
64
|
+
if (!primary) throw new Error(`fixture has no mnemonic account to host the imported key ${accountName}.`);
|
|
65
|
+
accounts.push({ ...primary, count: 1, names: [primary.name] });
|
|
66
|
+
accounts.push(entry);
|
|
67
|
+
} else {
|
|
68
|
+
accounts.push({ ...entry, count: 1, names: [entry.name] });
|
|
69
|
+
}
|
|
70
|
+
return { password: fixture.password, settings: fixture.settings ?? {}, accounts, selectedAccount: accountName };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function runFixtureState(node, phase, flags) {
|
|
74
|
+
return execFileSync(node, [FIXTURE_STATE_SCRIPT, phase, ...flags], {
|
|
75
|
+
encoding: 'utf8',
|
|
76
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
77
|
+
timeout: 240000,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function extensionIdFor(withBrowserClient, idFile) {
|
|
82
|
+
if (existsSync(idFile)) return readFileSync(idFile, 'utf8').trim();
|
|
83
|
+
return withBrowserClient(async (client) => {
|
|
84
|
+
const deadline = Date.now() + 30000;
|
|
85
|
+
while (Date.now() < deadline) {
|
|
86
|
+
const { targetInfos } = await client.send('Target.getTargets');
|
|
87
|
+
const worker = targetInfos.find((info) => info.type === 'service_worker' && info.url.startsWith('chrome-extension://'));
|
|
88
|
+
if (worker) return new URL(worker.url).host;
|
|
89
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
90
|
+
}
|
|
91
|
+
throw new Error('the MetaMask service worker did not start');
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// MetaMask shows dapp requests in its side panel (the fixture default) or a
|
|
96
|
+
// notification/popup window; only request routes count as confirmations.
|
|
97
|
+
const WALLET_SURFACES = ['sidepanel.html', 'notification.html', 'popup.html'];
|
|
98
|
+
const CONFIRMATION_ROUTE = /^\/(connect|confirm-transaction|confirmation|confirm|signature-request)(\/|$)/u;
|
|
99
|
+
|
|
100
|
+
export const signers = {
|
|
101
|
+
injected: {
|
|
102
|
+
identity: {
|
|
103
|
+
info: {
|
|
104
|
+
uuid: '5d1e5b4e-e2e0-4c3f-9a1d-000000000001',
|
|
105
|
+
name: 'MetaMask (strict test wallet)',
|
|
106
|
+
icon: "data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'/>",
|
|
107
|
+
rdns: 'io.metamask',
|
|
108
|
+
},
|
|
109
|
+
isMetaMask: true,
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
extension: {
|
|
114
|
+
async prepareProfile({ target, runtime, profile, account, fixture, env, freshProfile, log, writePrivateFile, trackSecret }) {
|
|
115
|
+
const extensionDir = extensionDirFor(target, env);
|
|
116
|
+
const checkout = env.TERMINAL_EXTENSION_CHECKOUT ? path.resolve(env.TERMINAL_EXTENSION_CHECKOUT) : null;
|
|
117
|
+
if (!existsSync(path.join(extensionDir, 'manifest.json'))) {
|
|
118
|
+
throw new Error(`MetaMask extension dist not found at ${extensionDir}.\nNext: copy a built dist/chrome there or set TERMINAL_EXTENSION_DIR.`);
|
|
119
|
+
}
|
|
120
|
+
if (!checkout || !existsSync(path.join(checkout, 'node_modules'))) {
|
|
121
|
+
throw new Error('signer=extension needs TERMINAL_EXTENSION_CHECKOUT: an installed metamask-extension checkout at the dist commit (fixture state generation).');
|
|
122
|
+
}
|
|
123
|
+
const manifest = JSON.parse(readFileSync(path.join(extensionDir, 'manifest.json'), 'utf8'));
|
|
124
|
+
const node = resolveExtensionNode(checkout, env);
|
|
125
|
+
// Key material (the slot fixture) and the encrypted vault (fixture state).
|
|
126
|
+
const slotFixture = trackSecret(path.join(runtime, `wallet-fixture.${account}.json`));
|
|
127
|
+
const fixtureState = trackSecret(path.join(runtime, `fixture-state.${account}.json`));
|
|
128
|
+
writePrivateFile(slotFixture, JSON.stringify(slotWalletFixture(fixture, account)));
|
|
129
|
+
const idFile = path.join(runtime, 'extension.id');
|
|
130
|
+
runFixtureState(node, 'generate', ['--target', checkout, '--fixture', slotFixture, '--out', fixtureState]);
|
|
131
|
+
if (freshProfile) {
|
|
132
|
+
runFixtureState(node, 'prefill-profile', ['--target', checkout, '--state', fixtureState, '--profile', profile, '--extension-dir', extensionDir, '--extension-id-file', idFile]);
|
|
133
|
+
log(`prefilled extension profile for ${account}`);
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
browserArgs: [
|
|
137
|
+
'--disable-features=ExtensionContentVerification,DisableLoadExtensionCommandLineSwitch',
|
|
138
|
+
'--disable-extensions-file-access-check',
|
|
139
|
+
`--disable-extensions-except=${extensionDir}`,
|
|
140
|
+
`--load-extension=${extensionDir}`,
|
|
141
|
+
],
|
|
142
|
+
secrets: [],
|
|
143
|
+
// browser.json's `extension`; the id is filled in once the extension runs.
|
|
144
|
+
state: { id: undefined, version: manifest.version, dir: extensionDir, checkout },
|
|
145
|
+
async afterBrowserStart({ cdpPort, launchMethod, withBrowserClient, openBackgroundWindow }) {
|
|
146
|
+
if (launchMethod === LAUNCH_SERVICES) {
|
|
147
|
+
// The seed reuses an open MetaMask page; without one it would open a
|
|
148
|
+
// tab in the foreground and activate the browser.
|
|
149
|
+
const extensionId = await extensionIdFor(withBrowserClient, idFile);
|
|
150
|
+
await openBackgroundWindow(`chrome-extension://${extensionId}/home.html`);
|
|
151
|
+
}
|
|
152
|
+
runFixtureState(node, 'seed-cdp', [
|
|
153
|
+
'--target', checkout,
|
|
154
|
+
'--fixture', slotFixture,
|
|
155
|
+
'--state', fixtureState,
|
|
156
|
+
'--cdp-port', String(cdpPort),
|
|
157
|
+
'--extension-dir', extensionDir,
|
|
158
|
+
'--extension-id-file', idFile,
|
|
159
|
+
'--out', path.join(runtime, `seed-report.${account}.json`),
|
|
160
|
+
]);
|
|
161
|
+
const id = readFileSync(idFile, 'utf8').trim();
|
|
162
|
+
log(`extension ${id} seeded and unlocked for ${account}`);
|
|
163
|
+
return { state: { id }, hostArgs: ['--extension-id', id] };
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
confirm({ args, appendEntry, say, focus }) {
|
|
169
|
+
const extensionOrigin = args['extension-id'] ? `chrome-extension://${args['extension-id']}` : null;
|
|
170
|
+
const seenConfirmationUrls = new Map();
|
|
171
|
+
const isWalletSurfaceUrl = (url) =>
|
|
172
|
+
Boolean(extensionOrigin) && WALLET_SURFACES.some((surface) => url.startsWith(`${extensionOrigin}/${surface}`));
|
|
173
|
+
const confirmationRoute = (url) => {
|
|
174
|
+
const route = (url.split('#')[1] ?? '').split('?')[0];
|
|
175
|
+
return CONFIRMATION_ROUTE.test(route) ? route.replace(/0x[0-9a-fA-F]{40}/gu, '<address>') : null;
|
|
176
|
+
};
|
|
177
|
+
return {
|
|
178
|
+
isWalletSurfaceUrl,
|
|
179
|
+
observe(targetId, url) {
|
|
180
|
+
if (!isWalletSurfaceUrl(url)) return;
|
|
181
|
+
if (focus && !url.includes('/sidepanel.html') && !seenConfirmationUrls.has(targetId)) {
|
|
182
|
+
setTimeout(() => focus.observe(url.includes('/popup.html') ? 'popup' : 'notification', 'MetaMask'), 1000);
|
|
183
|
+
}
|
|
184
|
+
const route = confirmationRoute(url);
|
|
185
|
+
if (!route || seenConfirmationUrls.get(targetId) === route) return;
|
|
186
|
+
seenConfirmationUrls.set(targetId, route);
|
|
187
|
+
const surface = WALLET_SURFACES.find((name) => url.includes(name))?.replace('.html', '');
|
|
188
|
+
appendEntry({ kind: 'confirmation-shown', t: Date.now(), surface, route });
|
|
189
|
+
say(`confirmation shown in ${surface}: ${route}`);
|
|
190
|
+
},
|
|
191
|
+
};
|
|
192
|
+
},
|
|
193
|
+
|
|
194
|
+
async readinessChecks({ target, env, required }) {
|
|
195
|
+
const checks = [];
|
|
196
|
+
const extensionDir = extensionDirFor(target, env);
|
|
197
|
+
const manifestPath = path.join(extensionDir, 'manifest.json');
|
|
198
|
+
if (existsSync(manifestPath)) {
|
|
199
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
200
|
+
checks.push(readinessCheck('metamask-extension', true, `MetaMask ${manifest.version} at ${extensionDir}`, { required }));
|
|
201
|
+
} else {
|
|
202
|
+
checks.push(readinessCheck('metamask-extension', false, `no extension dist at ${extensionDir}`, { required }));
|
|
203
|
+
}
|
|
204
|
+
const checkout = env.TERMINAL_EXTENSION_CHECKOUT;
|
|
205
|
+
const checkoutOk = Boolean(checkout) && existsSync(path.join(checkout, 'node_modules/@metamask/browser-passworder'));
|
|
206
|
+
checks.push(readinessCheck('extension-fixture-tooling', checkoutOk, checkoutOk
|
|
207
|
+
? `extension checkout ${checkout} has fixture-state dependencies`
|
|
208
|
+
: 'TERMINAL_EXTENSION_CHECKOUT unset or not installed (needed to import the wallet into the extension)', { required }));
|
|
209
|
+
return checks;
|
|
210
|
+
},
|
|
211
|
+
},
|
|
212
|
+
};
|
|
@@ -11,9 +11,6 @@ const coreActions = {
|
|
|
11
11
|
],
|
|
12
12
|
// read_account and quote_fees are Core's alone, and only its live adapter implements them.
|
|
13
13
|
liveOnly: ["metamask.perps.read_account", "metamask.perps.quote_fees"],
|
|
14
|
-
tsxLiveScripts: {
|
|
15
|
-
env: async (projectRoot, tempDir) => (await import("./live-script-env.js")).coreLiveScriptEnv(projectRoot, tempDir)
|
|
16
|
-
},
|
|
17
14
|
// Core declares no cdp.target; the probe matches the Extension's.
|
|
18
15
|
cdpTarget: { transport: "chrome-extension-cdp", probePath: "json/version" },
|
|
19
16
|
// ensure_positions and ensure_orders with an open or present state place an order.
|
|
@@ -23,12 +23,8 @@ Next: mm-harness install --platform core --target ${target}`
|
|
|
23
23
|
return { command: delegate, prefixArgs: ["doctor", "--adapter", "core"] };
|
|
24
24
|
}
|
|
25
25
|
};
|
|
26
|
-
const coreRuntimeContext = {
|
|
27
|
-
forbiddenFields: ["cdpPort", "watcherPort", "metroPort", "devServerPort", "simulator", "simulatorUdid", "adbSerial", "extensionId"]
|
|
28
|
-
};
|
|
29
26
|
const corePrepare = {};
|
|
30
27
|
export {
|
|
31
28
|
coreHarness,
|
|
32
|
-
corePrepare
|
|
33
|
-
coreRuntimeContext
|
|
29
|
+
corePrepare
|
|
34
30
|
};
|