@deeeed/metamask-harness 0.34.4 → 0.36.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 +25 -0
- package/README.md +9 -1
- package/adapters/core/inject.sh +3 -3
- package/adapters/extension/check-infura-readiness.cjs +102 -0
- package/adapters/extension/inject.mjs +4 -4
- package/adapters/extension/live.sh +25 -10
- package/adapters/extension/start-watch.sh +15 -0
- package/adapters/extension/wallet-fixture-state.cjs +3 -1
- package/adapters/manifest.json +16 -0
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +73 -42
- package/adapters/mobile/inject.sh +4 -3
- package/adapters/mobile/reset-app-data.sh +154 -0
- package/adapters/mobile/verify.sh +37 -0
- package/dist/adapters/extension/product-config.js +29 -1
- package/dist/adapters/extension/runtime.js +60 -15
- package/dist/adapters/extension/surface.js +6 -1
- package/dist/adapters/mobile/surface.js +6 -1
- package/dist/adapters.js +18 -0
- package/dist/cli-commands.js +1 -1
- package/dist/cli.js +2 -2
- package/dist/command-contract.js +1 -1
- package/dist/commands/device-target.js +5 -0
- package/dist/commands/fixtures.js +106 -31
- package/dist/commands/launch/extension.js +39 -3
- package/dist/commands/launch/index.js +13 -0
- package/dist/doctor.js +30 -1
- package/dist/mm-harness-cli.js +6 -3
- package/dist/recipe-security.js +3 -0
- package/docs/RECIPES.md +29 -0
- package/library/actions/extension/wallet/import.mjs +234 -0
- package/library/actions/extension/wallet/reset.mjs +98 -0
- package/library/actions/extension/wallet/state.mjs +1 -0
- package/library/actions/mobile/analytics/consent-settings.mjs +112 -0
- package/library/actions/mobile/analytics/set_consent.mjs +4 -112
- package/library/actions/mobile/platform/bridge.mjs +8 -0
- package/library/actions/mobile/wallet/import.mjs +259 -0
- package/library/actions/mobile/wallet/reset-helper.mjs +99 -0
- package/library/actions/mobile/wallet/reset.mjs +7 -0
- package/library/actions/shared/wallet/import-source.mjs +101 -0
- package/library/manifests/extension.action-manifest.json +112 -0
- package/library/manifests/mobile.action-manifest.json +108 -0
- package/library/recipes/wallet/import.recipe.json +83 -0
- package/library/recipes/wallet/reset-import.recipe.json +88 -0
- package/package.json +1 -1
- package/scripts/completions.sh +2 -2
- package/scripts/site-contrast.mjs +7 -0
- package/site/architecture.html +14 -7
- package/site/assets/metamask-fox.svg +24 -0
- package/site/assets/progress.mjs +6 -0
- package/site/assets/style.css +88 -29
- package/site/cheatsheet.html +5 -6
- package/site/ecosystem.html +162 -0
- package/site/how-it-works.html +8 -9
- package/site/index.html +17 -12
- package/site/perps.html +103 -45
- package/site/recipes.html +5 -6
- package/site/reviewers.html +5 -6
- package/site/tutorials/index.html +5 -6
- package/site/tutorials/v1.html +5 -6
- package/site/tutorials/v2.html +6 -8
- package/site/tutorials/v3.html +5 -6
- package/site/tutorials/v4.html +5 -6
- package/site/tutorials/v5.html +5 -6
- package/site/tutorials/v6.html +5 -6
- package/site/tutorials/v7.html +5 -6
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Reset one installed MetaMask Mobile app without rebuilding it.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
PLATFORM="${PLATFORM:-ios}"
|
|
6
|
+
TARGET="$PWD"
|
|
7
|
+
SIMULATOR="${SIM_UDID:-${IOS_SIMULATOR:-booted}}"
|
|
8
|
+
ADB_SERIAL_ARG="${ADB_SERIAL:-${ANDROID_SERIAL:-}}"
|
|
9
|
+
ADB_BIN="${MM_HARNESS_ADB_PATH:-}"
|
|
10
|
+
MM_RESET_TEMP_ROOT=""
|
|
11
|
+
MM_RESET_STAGING_DIR=""
|
|
12
|
+
|
|
13
|
+
while [ "$#" -gt 0 ]; do
|
|
14
|
+
case "$1" in
|
|
15
|
+
--platform) PLATFORM="$2"; shift 2 ;;
|
|
16
|
+
--target) TARGET="$2"; shift 2 ;;
|
|
17
|
+
--simulator) SIMULATOR="$2"; shift 2 ;;
|
|
18
|
+
--adb-serial) ADB_SERIAL_ARG="$2"; shift 2 ;;
|
|
19
|
+
-h|--help)
|
|
20
|
+
printf 'Usage: reset-app-data.sh --platform ios|android [--target <dir>] [--simulator <udid|name>] [--adb-serial <serial>]\n'
|
|
21
|
+
exit 0
|
|
22
|
+
;;
|
|
23
|
+
*) printf 'reset-app-data: unknown arg: %s\n' "$1" >&2; exit 2 ;;
|
|
24
|
+
esac
|
|
25
|
+
done
|
|
26
|
+
|
|
27
|
+
TARGET="$(cd "$TARGET" && pwd -P)"
|
|
28
|
+
|
|
29
|
+
reset_ios() {
|
|
30
|
+
local bundle_id="" app_source="" candidate="" staged_app=""
|
|
31
|
+
local temp_root="${TMPDIR:-/tmp}" staging_dir=""
|
|
32
|
+
local -a candidates installed_ids installed_paths
|
|
33
|
+
|
|
34
|
+
temp_root="${temp_root%/}"
|
|
35
|
+
[ -n "$temp_root" ] && [ "$temp_root" != "/" ] && [ -d "$temp_root" ] && [ ! -L "$temp_root" ] || {
|
|
36
|
+
printf 'reset-app-data: unsafe temporary directory: %s\n' "$temp_root" >&2
|
|
37
|
+
return 1
|
|
38
|
+
}
|
|
39
|
+
if [ -n "${IOS_BUNDLE_ID:-}" ]; then
|
|
40
|
+
candidates=("$IOS_BUNDLE_ID")
|
|
41
|
+
else
|
|
42
|
+
candidates=("io.metamask.MetaMask" "io.metamask" "io.metamask.MetaMask-Flask" "io.metamask.qa")
|
|
43
|
+
fi
|
|
44
|
+
for candidate in "${candidates[@]}"; do
|
|
45
|
+
if app_source="$(xcrun simctl get_app_container "$SIMULATOR" "$candidate" app 2>/dev/null)"; then
|
|
46
|
+
installed_ids+=("$candidate")
|
|
47
|
+
installed_paths+=("$app_source")
|
|
48
|
+
fi
|
|
49
|
+
done
|
|
50
|
+
if [ "${#installed_ids[@]}" -gt 1 ]; then
|
|
51
|
+
printf 'reset-app-data: multiple MetaMask iOS variants are installed on simulator %s: %s.\n' "$SIMULATOR" "${installed_ids[*]}" >&2
|
|
52
|
+
printf 'Next: set IOS_BUNDLE_ID to the exact variant and retry.\n' >&2
|
|
53
|
+
return 1
|
|
54
|
+
fi
|
|
55
|
+
bundle_id="${installed_ids[0]:-}"
|
|
56
|
+
app_source="${installed_paths[0]:-}"
|
|
57
|
+
[ -n "$bundle_id" ] && [ -n "$app_source" ] || {
|
|
58
|
+
printf 'reset-app-data: no installed MetaMask app was found on iOS simulator %s.\n' "$SIMULATOR" >&2
|
|
59
|
+
printf "Next: mm-harness launch --adapter mobile --platform ios --target '%s'\n" "$TARGET" >&2
|
|
60
|
+
return 1
|
|
61
|
+
}
|
|
62
|
+
case "$app_source" in
|
|
63
|
+
*/Library/Developer/CoreSimulator/Devices/*/data/Containers/Bundle/Application/*/*.app) ;;
|
|
64
|
+
*) printf 'reset-app-data: refusing unexpected iOS app path: %s\n' "$app_source" >&2; return 1 ;;
|
|
65
|
+
esac
|
|
66
|
+
[ -d "$app_source" ] && [ ! -L "$app_source" ] || {
|
|
67
|
+
printf 'reset-app-data: iOS app is not a real directory: %s\n' "$app_source" >&2
|
|
68
|
+
return 1
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
staging_dir="$(mktemp -d "$temp_root/mm-harness-ios-reset.XXXXXX")"
|
|
72
|
+
case "$staging_dir" in
|
|
73
|
+
"$temp_root"/mm-harness-ios-reset.*) ;;
|
|
74
|
+
*) printf 'reset-app-data: refusing unexpected staging path: %s\n' "$staging_dir" >&2; return 1 ;;
|
|
75
|
+
esac
|
|
76
|
+
[ -d "$staging_dir" ] && [ ! -L "$staging_dir" ] || return 1
|
|
77
|
+
staged_app="$staging_dir/$(basename "$app_source")"
|
|
78
|
+
MM_RESET_TEMP_ROOT="$temp_root"
|
|
79
|
+
MM_RESET_STAGING_DIR="$staging_dir"
|
|
80
|
+
cleanup_staging() {
|
|
81
|
+
case "$MM_RESET_STAGING_DIR" in
|
|
82
|
+
"$MM_RESET_TEMP_ROOT"/mm-harness-ios-reset.*)
|
|
83
|
+
[ -d "$MM_RESET_STAGING_DIR" ] && [ ! -L "$MM_RESET_STAGING_DIR" ] && rm -r -- "$MM_RESET_STAGING_DIR"
|
|
84
|
+
;;
|
|
85
|
+
*) printf 'reset-app-data: staging cleanup refused: %s\n' "$MM_RESET_STAGING_DIR" >&2 ;;
|
|
86
|
+
esac
|
|
87
|
+
}
|
|
88
|
+
trap cleanup_staging EXIT HUP INT TERM
|
|
89
|
+
|
|
90
|
+
ditto "$app_source" "$staged_app"
|
|
91
|
+
[ -d "$staged_app" ] && [ ! -L "$staged_app" ] || {
|
|
92
|
+
printf 'reset-app-data: failed to preserve the installed iOS app.\n' >&2
|
|
93
|
+
return 1
|
|
94
|
+
}
|
|
95
|
+
printf 'Resetting iOS app data: simulator=%s bundle=%s\n' "$SIMULATOR" "$bundle_id" >&2
|
|
96
|
+
xcrun simctl terminate "$SIMULATOR" "$bundle_id" >/dev/null 2>&1 || true
|
|
97
|
+
xcrun simctl uninstall "$SIMULATOR" "$bundle_id"
|
|
98
|
+
xcrun simctl install "$SIMULATOR" "$staged_app"
|
|
99
|
+
xcrun simctl get_app_container "$SIMULATOR" "$bundle_id" app >/dev/null
|
|
100
|
+
printf 'Mobile app data reset; preserved installed iOS build %s.\n' "$bundle_id" >&2
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
reset_android() {
|
|
104
|
+
local package_id="" candidate="" clear_output=""
|
|
105
|
+
local -a candidates adb_target installed_ids
|
|
106
|
+
if [ -z "$ADB_BIN" ]; then
|
|
107
|
+
ADB_BIN="$(command -v adb 2>/dev/null || true)"
|
|
108
|
+
fi
|
|
109
|
+
[ -n "$ADB_BIN" ] && [ "${ADB_BIN#/}" != "$ADB_BIN" ] && [ -x "$ADB_BIN" ] || {
|
|
110
|
+
printf 'reset-app-data: Android SDK Platform-Tools (adb) is required.\n' >&2
|
|
111
|
+
printf 'Next: brew install android-platform-tools\n' >&2
|
|
112
|
+
return 1
|
|
113
|
+
}
|
|
114
|
+
[ -n "$ADB_SERIAL_ARG" ] || {
|
|
115
|
+
printf 'reset-app-data: an Android serial is required.\n' >&2
|
|
116
|
+
printf 'Next: mm-harness devices --adapter mobile --json\n' >&2
|
|
117
|
+
return 1
|
|
118
|
+
}
|
|
119
|
+
adb_target=(-s "$ADB_SERIAL_ARG")
|
|
120
|
+
if [ -n "${ANDROID_PACKAGE_ID:-}" ]; then
|
|
121
|
+
candidates=("$ANDROID_PACKAGE_ID")
|
|
122
|
+
else
|
|
123
|
+
candidates=("io.metamask" "io.metamask.flask" "io.metamask.qa")
|
|
124
|
+
fi
|
|
125
|
+
for candidate in "${candidates[@]}"; do
|
|
126
|
+
if "$ADB_BIN" "${adb_target[@]}" shell pm path "$candidate" 2>/dev/null | grep -q '^package:'; then
|
|
127
|
+
installed_ids+=("$candidate")
|
|
128
|
+
fi
|
|
129
|
+
done
|
|
130
|
+
if [ "${#installed_ids[@]}" -gt 1 ]; then
|
|
131
|
+
printf 'reset-app-data: multiple MetaMask Android variants are installed on device %s: %s.\n' "$ADB_SERIAL_ARG" "${installed_ids[*]}" >&2
|
|
132
|
+
printf 'Next: set ANDROID_PACKAGE_ID to the exact variant and retry.\n' >&2
|
|
133
|
+
return 1
|
|
134
|
+
fi
|
|
135
|
+
package_id="${installed_ids[0]:-}"
|
|
136
|
+
[ -n "$package_id" ] || {
|
|
137
|
+
printf 'reset-app-data: no installed MetaMask app was found on Android device %s.\n' "$ADB_SERIAL_ARG" >&2
|
|
138
|
+
printf "Next: mm-harness launch --adapter mobile --platform android --target '%s' --device '%s'\n" "$TARGET" "$ADB_SERIAL_ARG" >&2
|
|
139
|
+
return 1
|
|
140
|
+
}
|
|
141
|
+
printf 'Resetting Android app data: device=%s package=%s\n' "$ADB_SERIAL_ARG" "$package_id" >&2
|
|
142
|
+
clear_output="$("$ADB_BIN" "${adb_target[@]}" shell pm clear "$package_id")"
|
|
143
|
+
[ "$clear_output" = "Success" ] || {
|
|
144
|
+
printf 'reset-app-data: Android package clear failed: %s\n' "$clear_output" >&2
|
|
145
|
+
return 1
|
|
146
|
+
}
|
|
147
|
+
printf 'Mobile app data reset; preserved installed Android build %s.\n' "$package_id" >&2
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
case "$PLATFORM" in
|
|
151
|
+
ios) reset_ios ;;
|
|
152
|
+
android) reset_android ;;
|
|
153
|
+
*) printf 'reset-app-data: --platform must be ios or android (got: %s)\n' "$PLATFORM" >&2; exit 2 ;;
|
|
154
|
+
esac
|
|
@@ -325,6 +325,35 @@ JSON
|
|
|
325
325
|
) > "$log_path" 2>&1
|
|
326
326
|
}
|
|
327
327
|
|
|
328
|
+
evm_rpc_ready() {
|
|
329
|
+
local log_path="$1"
|
|
330
|
+
local bridge="$HARNESS_DIR/bridge-runtime/cdp-bridge.cjs"
|
|
331
|
+
local expression
|
|
332
|
+
expression="(async function(){try{var engine=globalThis.Engine;var controller=engine&&engine.context&&engine.context.NetworkController;if(!controller||typeof controller.getSelectedNetworkClient!=='function')throw new Error('NetworkController selected client is unavailable');var client=controller.getSelectedNetworkClient();if(!client||!client.provider||typeof client.provider.request!=='function')throw new Error('selected EVM provider is unavailable');var result=await client.provider.request({method:'eth_blockNumber'});if(typeof result!=='string'||!/^0x[0-9a-f]+$/i.test(result))throw new Error('eth_blockNumber returned an invalid result');return {ok:true,result:result};}catch(error){return {ok:false,error:error&&(error.message||String(error))};}})()"
|
|
333
|
+
[ -f "$bridge" ] || {
|
|
334
|
+
printf 'Mobile bridge is missing at %s\n' "$bridge" > "$log_path"
|
|
335
|
+
return 1
|
|
336
|
+
}
|
|
337
|
+
if ! run_with_timeout "$log_path" 25 env \
|
|
338
|
+
APP_ROOT="$TARGET" \
|
|
339
|
+
WATCHER_PORT="$port" \
|
|
340
|
+
IOS_SIMULATOR="${IOS_SIMULATOR:-$ios_simulator_resolved}" \
|
|
341
|
+
ADB_SERIAL="${ADB_SERIAL:-$adb_serial_resolved}" \
|
|
342
|
+
ANDROID_SERIAL="${ANDROID_SERIAL:-$adb_serial_resolved}" \
|
|
343
|
+
node "$bridge" eval-async "$expression"; then
|
|
344
|
+
return 1
|
|
345
|
+
fi
|
|
346
|
+
node - "$log_path" <<'NODE'
|
|
347
|
+
const fs = require('fs');
|
|
348
|
+
try {
|
|
349
|
+
const result = JSON.parse(fs.readFileSync(process.argv[2], 'utf8'));
|
|
350
|
+
process.exit(result.ok === true ? 0 : 1);
|
|
351
|
+
} catch {
|
|
352
|
+
process.exit(1);
|
|
353
|
+
}
|
|
354
|
+
NODE
|
|
355
|
+
}
|
|
356
|
+
|
|
328
357
|
ensure_live_runtime() {
|
|
329
358
|
local device_target="$1"
|
|
330
359
|
local attempt
|
|
@@ -419,6 +448,14 @@ JSON
|
|
|
419
448
|
add_note "Runner v1 live bridge smoke failed; inspect logs/runner-live-smoke.log and runner-live-smoke/trace.json."
|
|
420
449
|
status="fail"
|
|
421
450
|
fi
|
|
451
|
+
|
|
452
|
+
if evm_rpc_ready "$ARTIFACTS/logs/evm-rpc-readiness.log"; then
|
|
453
|
+
checks+=("{\"name\":\"live EVM RPC readiness\",\"status\":\"pass\"}")
|
|
454
|
+
else
|
|
455
|
+
checks+=("{\"name\":\"live EVM RPC readiness\",\"status\":\"fail\",\"detail\":\"see logs/evm-rpc-readiness.log\"}")
|
|
456
|
+
add_note "EVM RPC readiness probe failed. Infura configured ≠ RPC reachable. Next: confirm $TARGET/.js.env has a real MM_INFURA_PROJECT_ID (not 00000000000), then rerun mm-harness launch $PLATFORM --verify --target '$TARGET'."
|
|
457
|
+
status="fail"
|
|
458
|
+
fi
|
|
422
459
|
fi
|
|
423
460
|
|
|
424
461
|
RECIPE_HARNESS_PREFLIGHT_MODE="$PREFLIGHT_MODE" RECIPE_HARNESS_ROOT_EXCLUDE="$HARNESS_ROOT" node - "$ARTIFACTS" "$TARGET" "$status" "${checks[@]}" <<'NODE'
|
|
@@ -1,6 +1,31 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import fs from "node:fs";
|
|
1
3
|
import path from "node:path";
|
|
2
4
|
import productConfig from "../../../adapters/extension/lib/product-config.cjs";
|
|
3
5
|
import { shellQuote } from "../../commands/parse-args.js";
|
|
6
|
+
const EXTENSION_PRODUCT_CONFIG_FINGERPRINT_FILENAME = "extension-product-config.sha256";
|
|
7
|
+
function extensionProductConfigFingerprint(target, environment = process.env) {
|
|
8
|
+
const resolution = productConfig.resolveExtensionInfuraProjectId(target, environment);
|
|
9
|
+
if (resolution.kind !== "configured") return null;
|
|
10
|
+
return createHash("sha256").update(resolution.value).digest("hex");
|
|
11
|
+
}
|
|
12
|
+
function extensionCompiledScriptsMatchProductConfig(target, dist, environment = process.env) {
|
|
13
|
+
const resolution = productConfig.resolveExtensionInfuraProjectId(target, environment);
|
|
14
|
+
if (resolution.kind !== "configured") return true;
|
|
15
|
+
const pending = [dist];
|
|
16
|
+
while (pending.length > 0) {
|
|
17
|
+
const current = pending.pop();
|
|
18
|
+
if (!current || !fs.existsSync(current)) continue;
|
|
19
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
20
|
+
const file = path.join(current, entry.name);
|
|
21
|
+
if (entry.isDirectory()) pending.push(file);
|
|
22
|
+
else if (entry.isFile() && entry.name.endsWith(".js") && fs.readFileSync(file, "utf8").includes(resolution.value)) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
4
29
|
function extensionProductConfigBlock(target) {
|
|
5
30
|
const resolution = productConfig.resolveExtensionInfuraProjectId(
|
|
6
31
|
target,
|
|
@@ -55,5 +80,8 @@ function invalidConfigBlock(target, name, reason) {
|
|
|
55
80
|
};
|
|
56
81
|
}
|
|
57
82
|
export {
|
|
58
|
-
|
|
83
|
+
EXTENSION_PRODUCT_CONFIG_FINGERPRINT_FILENAME,
|
|
84
|
+
extensionCompiledScriptsMatchProductConfig,
|
|
85
|
+
extensionProductConfigBlock,
|
|
86
|
+
extensionProductConfigFingerprint
|
|
59
87
|
};
|
|
@@ -153,12 +153,26 @@ async function checkExtensionRuntimeHealth(projectRoot, cdpPort, options = {}) {
|
|
|
153
153
|
if (runtime.backgroundUnresponsive === true) {
|
|
154
154
|
findings.push("Extension UI reports background connection unresponsive.");
|
|
155
155
|
}
|
|
156
|
+
if (runtime.ethereumConnectionUnavailable === true) {
|
|
157
|
+
findings.push('Extension UI reports "Unable to connect to Ethereum".');
|
|
158
|
+
}
|
|
159
|
+
if (runtime.evmRpcProbeOk !== true) {
|
|
160
|
+
const probeError = String(runtime.evmRpcProbeError ?? "unknown error").replace(/[.\s]+$/u, "");
|
|
161
|
+
findings.push(
|
|
162
|
+
`EVM RPC readiness probe failed: ${probeError}. Infura configured \u2260 RPC reachable.`
|
|
163
|
+
);
|
|
164
|
+
}
|
|
156
165
|
if (runtime.hasSubmitRequest === true && runtime.backgroundProbeOk !== true) {
|
|
157
166
|
warnings.push(`Perps background read probe failed: ${runtime.backgroundProbeError ?? "unknown error"}.`);
|
|
158
167
|
}
|
|
159
168
|
const extensionId = safeExtensionId(target);
|
|
169
|
+
const evmRpcUnreachable = runtime.ethereumConnectionUnavailable === true || runtime.evmRpcProbeOk !== true;
|
|
160
170
|
return {
|
|
161
171
|
status: findings.length === 0 ? "PASS" : "FAIL",
|
|
172
|
+
...evmRpcUnreachable ? {
|
|
173
|
+
errorCode: "EVM_RPC_UNREACHABLE",
|
|
174
|
+
userAction: evmRpcRecoveryAction(projectRoot)
|
|
175
|
+
} : {},
|
|
162
176
|
warnings,
|
|
163
177
|
cdpPort,
|
|
164
178
|
targetUrl: target.url,
|
|
@@ -219,21 +233,42 @@ async function evaluateHealth(session, timeoutMs) {
|
|
|
219
233
|
const storeState = hooks.store?.getState?.() || {};
|
|
220
234
|
const manager = hooks.getPerpsStreamManager?.();
|
|
221
235
|
const accountCache = manager?.account?.cache;
|
|
236
|
+
const networkConnectionBanner = storeState.metamask?.networkConnectionBanner;
|
|
237
|
+
const networkClientId = storeState.metamask?.selectedNetworkClientId || networkConnectionBanner?.networkClientId;
|
|
222
238
|
return Promise.race([
|
|
223
239
|
(async () => {
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
240
|
+
const [backgroundProbe, evmRpcProbe] = await Promise.all([
|
|
241
|
+
(async () => {
|
|
242
|
+
if (typeof hooks.submitRequestToBackground !== 'function') {
|
|
243
|
+
return { ok: false, error: 'submitRequestToBackground is not a function' };
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
const accountState = await hooks.submitRequestToBackground('perpsGetAccountState', []);
|
|
247
|
+
const ok = Boolean(accountState && typeof accountState === 'object');
|
|
248
|
+
return { ok, error: ok ? null : 'perpsGetAccountState returned an empty result' };
|
|
249
|
+
} catch (error) {
|
|
250
|
+
return { ok: false, error: String(error?.message || error) };
|
|
251
|
+
}
|
|
252
|
+
})(),
|
|
253
|
+
(async () => {
|
|
254
|
+
if (typeof hooks.submitRequestToBackground !== 'function') {
|
|
255
|
+
return { ok: false, error: 'submitRequestToBackground is not a function' };
|
|
256
|
+
}
|
|
257
|
+
if (typeof networkClientId !== 'string' || networkClientId.length === 0) {
|
|
258
|
+
return { ok: false, error: 'selected EVM network client is unavailable' };
|
|
259
|
+
}
|
|
260
|
+
try {
|
|
261
|
+
const code = await hooks.submitRequestToBackground('getCode', [
|
|
262
|
+
'0x0000000000000000000000000000000000000000',
|
|
263
|
+
networkClientId,
|
|
264
|
+
]);
|
|
265
|
+
const ok = typeof code === 'string' && code.startsWith('0x');
|
|
266
|
+
return { ok, error: ok ? null : 'background EVM RPC returned an invalid result' };
|
|
267
|
+
} catch (error) {
|
|
268
|
+
return { ok: false, error: String(error?.message || error) };
|
|
269
|
+
}
|
|
270
|
+
})(),
|
|
271
|
+
]);
|
|
237
272
|
return {
|
|
238
273
|
href: location.href,
|
|
239
274
|
title: document.title,
|
|
@@ -242,14 +277,17 @@ async function evaluateHealth(session, timeoutMs) {
|
|
|
242
277
|
hasStore: Boolean(hooks.store),
|
|
243
278
|
hasPerpsStreamManager: typeof hooks.getPerpsStreamManager === 'function',
|
|
244
279
|
backgroundUnresponsive: bodyText.includes('Background connection unresponsive') || bodyText.includes('MetaMask had trouble starting'),
|
|
280
|
+
ethereumConnectionUnavailable: bodyText.includes('Unable to connect to Ethereum'),
|
|
245
281
|
activeProvider: storeState.metamask?.activeProvider || null,
|
|
246
282
|
isTestnet: Boolean(storeState.metamask?.isTestnet),
|
|
247
283
|
perpsManagerInitialized: Boolean(manager?.isInitialized?.()),
|
|
248
284
|
positionsCacheIsArray: Array.isArray(manager?.positions?.cache),
|
|
249
285
|
ordersCacheIsArray: Array.isArray(manager?.orders?.cache),
|
|
250
286
|
accountCachePresent: Boolean(accountCache && typeof accountCache === 'object'),
|
|
251
|
-
backgroundProbeOk,
|
|
252
|
-
backgroundProbeError,
|
|
287
|
+
backgroundProbeOk: backgroundProbe.ok,
|
|
288
|
+
backgroundProbeError: backgroundProbe.error,
|
|
289
|
+
evmRpcProbeOk: evmRpcProbe.ok,
|
|
290
|
+
evmRpcProbeError: evmRpcProbe.error,
|
|
253
291
|
};
|
|
254
292
|
})(),
|
|
255
293
|
new Promise((resolve) => setTimeout(() => resolve({
|
|
@@ -260,8 +298,11 @@ async function evaluateHealth(session, timeoutMs) {
|
|
|
260
298
|
hasStore: Boolean(hooks.store),
|
|
261
299
|
hasPerpsStreamManager: typeof hooks.getPerpsStreamManager === 'function',
|
|
262
300
|
backgroundUnresponsive: bodyText.includes('Background connection unresponsive') || bodyText.includes('MetaMask had trouble starting'),
|
|
301
|
+
ethereumConnectionUnavailable: bodyText.includes('Unable to connect to Ethereum'),
|
|
263
302
|
backgroundProbeOk: false,
|
|
264
303
|
backgroundProbeError: 'perpsGetAccountState timed out after ${backgroundProbeTimeoutMs}ms',
|
|
304
|
+
evmRpcProbeOk: false,
|
|
305
|
+
evmRpcProbeError: 'background EVM RPC timed out after ${backgroundProbeTimeoutMs}ms',
|
|
265
306
|
}), ${backgroundProbeTimeoutMs})),
|
|
266
307
|
]);
|
|
267
308
|
})()`,
|
|
@@ -293,6 +334,9 @@ function compositorRecoveryAction(projectRoot) {
|
|
|
293
334
|
const recovery = `mm-harness launch --verify --target ${JSON.stringify(projectRoot)}`;
|
|
294
335
|
return process.platform === "darwin" ? `Unlock the macOS session, then rerun: ${recovery}` : `Restore an active display compositor, then rerun: ${recovery}`;
|
|
295
336
|
}
|
|
337
|
+
function evmRpcRecoveryAction(projectRoot) {
|
|
338
|
+
return `Confirm ${shellQuote(path.join(projectRoot, ".metamaskrc"))} has a real INFURA_PROJECT_ID (not 00000000000), then rerun: mm-harness launch --verify --target ${shellQuote(projectRoot)}`;
|
|
339
|
+
}
|
|
296
340
|
function resolveCdpPort(rawPort, slot) {
|
|
297
341
|
const raw = rawPort ?? process.env.CDP_PORT ?? process.env.RECIPE_CDP_PORT ?? slot?.cdpPort;
|
|
298
342
|
const port = Number(raw);
|
|
@@ -759,6 +803,7 @@ export {
|
|
|
759
803
|
assertHealthyExtensionRuntime,
|
|
760
804
|
checkExtensionRuntimeHealth,
|
|
761
805
|
compositorRecoveryAction,
|
|
806
|
+
evmRpcRecoveryAction,
|
|
762
807
|
extensionBackgroundProbeTimeoutMs,
|
|
763
808
|
formatHealthFailure,
|
|
764
809
|
prepareExtensionRuntime
|
|
@@ -14,6 +14,10 @@ function watcherStatus(buildLog) {
|
|
|
14
14
|
if (buildLog === "no-watch") return "down";
|
|
15
15
|
return buildLog;
|
|
16
16
|
}
|
|
17
|
+
function extensionRuntimeNextAction(reasonCode, target) {
|
|
18
|
+
const resolved = shellQuote(path.resolve(target));
|
|
19
|
+
return reasonCode === "deps-missing" || reasonCode === "deps-stale" ? `cd ${resolved} && yarn install --immutable` : `mm-harness launch --adapter extension --target ${resolved}`;
|
|
20
|
+
}
|
|
17
21
|
const extensionSurface = {
|
|
18
22
|
adapter: "extension",
|
|
19
23
|
headless: false,
|
|
@@ -42,7 +46,7 @@ const extensionSurface = {
|
|
|
42
46
|
decision: report.decision,
|
|
43
47
|
reasonCode: report.reasonCode,
|
|
44
48
|
reasons: report.reasons,
|
|
45
|
-
nextAction: report.decision === "ready" ? void 0 :
|
|
49
|
+
nextAction: report.decision === "ready" ? void 0 : extensionRuntimeNextAction(report.reasonCode, target),
|
|
46
50
|
deps: report.checks.deps.status,
|
|
47
51
|
devServer: {
|
|
48
52
|
label: "webpack",
|
|
@@ -96,5 +100,6 @@ const extensionSurface = {
|
|
|
96
100
|
}
|
|
97
101
|
};
|
|
98
102
|
export {
|
|
103
|
+
extensionRuntimeNextAction,
|
|
99
104
|
extensionSurface
|
|
100
105
|
};
|
|
@@ -5,6 +5,10 @@ import { shellQuote } from "../../commands/parse-args.js";
|
|
|
5
5
|
import { resolveMobileSlotPorts } from "../slot-ports.js";
|
|
6
6
|
import { mobileRuntimeStatus } from "./prepare.js";
|
|
7
7
|
import { hasRunwayProvisionBaseline, provisionRunwayMobile } from "./provision.js";
|
|
8
|
+
function mobileRuntimeNextAction(reasonCode, target, platform) {
|
|
9
|
+
const resolved = shellQuote(path.resolve(target));
|
|
10
|
+
return reasonCode === "deps-missing" || reasonCode === "deps-partial" || reasonCode === "deps-stale" ? `cd ${resolved} && yarn setup` : `mm-harness launch ${platform} --adapter mobile --target ${resolved}`;
|
|
11
|
+
}
|
|
8
12
|
const mobileSurface = {
|
|
9
13
|
adapter: "mobile",
|
|
10
14
|
headless: false,
|
|
@@ -21,7 +25,7 @@ const mobileSurface = {
|
|
|
21
25
|
decision: depsPending ? "launch" : report.decision,
|
|
22
26
|
reasonCode: depsPending ? "app-installed-deps-pending" : report.reasonCode,
|
|
23
27
|
reasons: depsPending ? ["Runway app is installed; JavaScript dependencies are pending until dispatch-time launch."] : report.reasons,
|
|
24
|
-
nextAction: report.decision === "ready" && !depsPending ? void 0 :
|
|
28
|
+
nextAction: report.decision === "ready" && !depsPending ? void 0 : mobileRuntimeNextAction(report.reasonCode, target, platform),
|
|
25
29
|
deps: runwayProvisioned && report.checks?.deps?.status !== "current" ? "pending" : report.checks?.deps?.status,
|
|
26
30
|
devServer: { label: "metro", status: report.checks?.metro?.status ?? "unprobed" }
|
|
27
31
|
};
|
|
@@ -58,5 +62,6 @@ const mobileSurface = {
|
|
|
58
62
|
}
|
|
59
63
|
};
|
|
60
64
|
export {
|
|
65
|
+
mobileRuntimeNextAction,
|
|
61
66
|
mobileSurface
|
|
62
67
|
};
|
package/dist/adapters.js
CHANGED
|
@@ -9,6 +9,7 @@ import { withExtensionPage } from "../library/actions/extension/platform/cdp.mjs
|
|
|
9
9
|
import { bridgeCommand, evalAsync, evalSync, MOBILE_BRIDGE_ERROR_CODES, selectBridgeStatusEntry, simulatorScreenshot } from "../library/actions/mobile/platform/bridge.mjs";
|
|
10
10
|
import { observeNativeUi } from "../library/actions/mobile/platform/observe-ui.mjs";
|
|
11
11
|
import { resolveMobileToolPath } from "../library/actions/mobile/platform/tool-paths.mjs";
|
|
12
|
+
import { resolveWalletImportCredentials, validateWalletImportOptions } from "../library/actions/shared/wallet/import-source.mjs";
|
|
12
13
|
const execFileAsync = promisify(execFile);
|
|
13
14
|
const NATIVE_PROVIDER_UI_ACTIONS = /* @__PURE__ */ new Set([
|
|
14
15
|
"ui.swipe",
|
|
@@ -51,6 +52,8 @@ const CORE_ONLY_PERPS_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
51
52
|
]);
|
|
52
53
|
const LIVE_ONLY_WALLET_ACTIONS = /* @__PURE__ */ new Set([
|
|
53
54
|
"metamask.wallet.setup",
|
|
55
|
+
"metamask.wallet.import",
|
|
56
|
+
"metamask.wallet.reset",
|
|
54
57
|
"metamask.wallet.ensure_unlocked",
|
|
55
58
|
"metamask.wallet.select_account",
|
|
56
59
|
"metamask.wallet.list_accounts",
|
|
@@ -110,6 +113,18 @@ async function semanticResult(platform, action, node, context, forceLive = false
|
|
|
110
113
|
}
|
|
111
114
|
const output = { platform, action, redacted: true };
|
|
112
115
|
if (action === "metamask.wallet.fixture_status") return { output: fixtureSummary(context.projectRoot) };
|
|
116
|
+
if (action === "metamask.wallet.validate_import") {
|
|
117
|
+
validateWalletImportOptions(platform, node);
|
|
118
|
+
const credentials = await resolveWalletImportCredentials({ node, context });
|
|
119
|
+
return {
|
|
120
|
+
output: {
|
|
121
|
+
...output,
|
|
122
|
+
credentialSource: credentials.source,
|
|
123
|
+
credentialSourceName: credentials.sourceName,
|
|
124
|
+
expectedAddress: credentials.expectedAddress
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
}
|
|
113
128
|
return {
|
|
114
129
|
output: {
|
|
115
130
|
...output,
|
|
@@ -183,7 +198,10 @@ function probeHttpJson(url, timeoutMs = 1e3) {
|
|
|
183
198
|
function createMetaMaskSemanticAdapters(platform, declaredCustomActions = [], preparedLiveAdapters) {
|
|
184
199
|
const walletActions = [
|
|
185
200
|
"metamask.wallet.fixture_status",
|
|
201
|
+
"metamask.wallet.validate_import",
|
|
186
202
|
"metamask.wallet.setup",
|
|
203
|
+
"metamask.wallet.import",
|
|
204
|
+
"metamask.wallet.reset",
|
|
187
205
|
"metamask.wallet.ensure_unlocked",
|
|
188
206
|
"metamask.wallet.select_account",
|
|
189
207
|
"metamask.wallet.list_accounts",
|
package/dist/cli-commands.js
CHANGED
|
@@ -15,7 +15,7 @@ const SPEC = {
|
|
|
15
15
|
{ name: "sync", desc: "Refresh harness + canonicalize wallet fixture", flags: ["--json"] },
|
|
16
16
|
{ name: "logs", aliases: ["tail"], desc: "Compact build events or full log", flags: ["--full", "-f", "--window", "--events", "--source", "--json"] },
|
|
17
17
|
{ name: "debug", aliases: ["devtools", "inspect"], desc: "Open DevTools UI", flags: ["--json", "--no-open"] },
|
|
18
|
-
{ name: "fixtures", desc: "Manage the canonical wallet fixture (sync/set/generate)", args: ["sync", "set", "generate"], flags: ["--fixture", "--out", "--adapter", "--target", "--device", "--json"] },
|
|
18
|
+
{ name: "fixtures", desc: "Manage the canonical wallet fixture (sync/set/reset/generate)", args: ["sync", "set", "reset", "generate"], flags: ["--fixture", "--out", "--adapter", "--target", "--device", "--json"] },
|
|
19
19
|
{ name: "actions", desc: "List runnable recipe actions", flags: ["--json", "--matrix", "--categories", "--category", "--action", "--library"] },
|
|
20
20
|
{ name: "doctor", desc: "Check harness/orchestration health", flags: ["--json", "--target", "--adapter", "--runtime-dir", "--expect-live", "--print-ready", "--cdp-port", "--device"] },
|
|
21
21
|
{ name: "run", desc: "Execute a proof recipe (path or library name, e.g. run perps.smoke)", args: ["recipe.json|name"], flags: ["--list", "--device"] },
|
package/dist/cli.js
CHANGED
|
@@ -47,8 +47,8 @@ DAILY LOOP \u2014 what a teammate runs many times a day:
|
|
|
47
47
|
mm-harness logs
|
|
48
48
|
debug Open the debug console (extension DevTools / mobile RN).
|
|
49
49
|
mm-harness debug
|
|
50
|
-
fixtures Sync files
|
|
51
|
-
mm-harness fixtures sync # or: set | generate --fixture <f> --out <o> | finalize \u2026
|
|
50
|
+
fixtures Sync files, set/reset the wallet, generate fixture-state, or finalize labels over CDP.
|
|
51
|
+
mm-harness fixtures sync # or: set | reset | generate --fixture <f> --out <o> | finalize \u2026
|
|
52
52
|
|
|
53
53
|
PROVE \u2014 run recipes and inspect capabilities:
|
|
54
54
|
run Run a recipe and write evidence (summary/trace/artifacts).
|
package/dist/command-contract.js
CHANGED
|
@@ -270,7 +270,7 @@ const PUBLIC_COMMAND_CONTRACTS = {
|
|
|
270
270
|
"--extension-id-file": value(),
|
|
271
271
|
"--action-manifest": value()
|
|
272
272
|
}),
|
|
273
|
-
positionals: [{ label: "action", choices: ["init", "sync", "set", "generate", "finalize"] }],
|
|
273
|
+
positionals: [{ label: "action", choices: ["init", "sync", "set", "reset", "generate", "finalize"] }],
|
|
274
274
|
minimumPositionals: 0
|
|
275
275
|
}
|
|
276
276
|
};
|
|
@@ -256,6 +256,11 @@ ${formatConnectedDevices(connected)}
|
|
|
256
256
|
userAction: deviceRecovery(options, opts.rerun)
|
|
257
257
|
};
|
|
258
258
|
}
|
|
259
|
+
if (targetable.length === 1) {
|
|
260
|
+
const selected = targetable[0];
|
|
261
|
+
if (selected.platform === "android") setAndroidDeviceEnv(selected.id, selected.name);
|
|
262
|
+
else setIosDeviceEnv(selected.id, selected.name);
|
|
263
|
+
}
|
|
259
264
|
return { ok: true };
|
|
260
265
|
}
|
|
261
266
|
function scopedDevices(devices, allDevices = false) {
|