@deeeed/metamask-harness 0.80.0 → 0.82.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 +43 -0
- package/adapters/manifest.json +48 -64
- package/adapters/{terminal → web-dapp}/cleanup.mjs +9 -9
- package/adapters/{terminal → web-dapp}/inject.mjs +14 -14
- package/adapters/{terminal → web-dapp}/launch.mjs +27 -24
- package/adapters/web-dapp/lib/blocked-hosts.mjs +24 -0
- package/adapters/{terminal → web-dapp}/lib/cdp-client.mjs +1 -1
- package/adapters/{terminal → web-dapp}/lib/display.mjs +1 -1
- package/adapters/{terminal → web-dapp}/lib/hud.mjs +1 -1
- package/adapters/web-dapp/lib/policy.mjs +79 -0
- package/adapters/{terminal → web-dapp}/lib/processes.mjs +8 -8
- package/adapters/{terminal → web-dapp}/lib/readiness.mjs +32 -34
- package/adapters/{terminal → web-dapp}/stop.mjs +5 -4
- package/adapters/{terminal → web-dapp}/verify.mjs +9 -6
- package/adapters/{terminal → web-dapp}/wallet-host.mjs +44 -227
- package/dist/adapters/core/actions.js +4 -1
- package/dist/adapters/extension/actions.js +6 -2
- package/dist/adapters/extension/network-observer.js +13 -0
- package/dist/adapters/extension/run.js +115 -31
- package/dist/adapters/mobile/actions.js +4 -1
- package/dist/adapters/mobile/network-observer.js +68 -0
- package/dist/adapters/mobile/performance-observer.js +1 -3
- package/dist/adapters/mobile/run.js +32 -9
- package/dist/adapters/perps-order-inputs.js +36 -0
- package/dist/adapters/plugin-surface.js +98 -0
- package/dist/adapters/proof-failure.js +11 -0
- package/dist/adapters/surface.js +1 -1
- package/dist/adapters/{terminal → web-dapp}/actions.js +11 -8
- package/dist/adapters/web-dapp/console-capture.js +63 -0
- package/dist/adapters/web-dapp/doctor-checks.js +33 -0
- package/dist/adapters/{terminal → web-dapp}/launch.js +21 -19
- package/dist/adapters/web-dapp/overlay.js +21 -0
- package/dist/adapters/web-dapp/readiness.js +10 -0
- package/dist/adapters/{terminal → web-dapp}/run.js +22 -17
- package/dist/adapters/{terminal → web-dapp}/stop.js +5 -4
- package/dist/adapters/web-dapp/surface.js +149 -0
- package/dist/adapters.js +20 -4
- package/dist/cli.js +34 -6
- package/dist/command-contract.js +29 -7
- package/dist/commands/check.js +32 -8
- package/dist/commands/completion-candidates.js +5 -5
- package/dist/commands/doctor.js +19 -2
- package/dist/commands/farmslot-ready.js +1 -0
- package/dist/commands/help.js +1 -1
- package/dist/commands/launch/device.js +5 -5
- package/dist/commands/self-test.js +10 -5
- package/dist/engine.js +47 -0
- package/dist/funding-execution-context.js +81 -0
- package/dist/manifest.js +48 -24
- package/dist/mm-harness-cli.js +20 -3
- package/dist/performance-observation.js +7 -15
- package/dist/recipe-assistance-context.js +6 -7
- package/dist/runner.js +28 -8
- package/docs/CONTRIBUTING.md +4 -2
- package/docs/RECIPES.md +31 -3
- package/docs/cli-contract.md +5 -3
- package/library/actions/core/perps/_controller.mjs +24 -28
- package/library/actions/core/perps/_rewards.mjs +186 -0
- package/library/actions/core/perps/close_positions.mjs +1 -1
- package/library/actions/core/perps/place_order.mjs +167 -45
- package/library/actions/core/perps/quote_fees.mjs +7 -4
- package/library/actions/core/wallet/list_accounts.mjs +1 -1
- package/library/actions/extension/perps/close_visible_position.mjs +5 -0
- package/library/actions/extension/perps/edit_margin.mjs +4 -0
- package/library/actions/extension/perps/mutation-receipt.mjs +95 -3
- package/library/actions/extension/perps/perps.mjs +320 -3
- package/library/actions/extension/perps/place_order.mjs +3 -1
- package/library/actions/extension/perps/update_position_tpsl.mjs +4 -0
- package/library/actions/extension/platform/cdp.mjs +71 -3
- package/library/actions/mobile/perps/perps.mjs +216 -11
- package/library/actions/mobile/perps/place_order.mjs +3 -1
- package/library/actions/shared/console-collector.mjs +1 -1
- package/library/actions/shared/perps/order-policy.mjs +177 -0
- package/library/actions/{core/wallet/_fixture-accounts.mjs → shared/wallet/fixture-accounts.mjs} +18 -3
- package/library/actions/{terminal → web-dapp}/app/assert_no_console_errors.mjs +4 -4
- package/library/actions/{terminal → web-dapp}/app/launch.mjs +8 -7
- package/library/actions/{terminal → web-dapp}/platform/hud.mjs +7 -6
- package/library/actions/{terminal → web-dapp}/platform/page.mjs +13 -12
- package/library/actions/{terminal → web-dapp}/platform/runtime.mjs +16 -12
- package/library/actions/web-dapp/ui/navigate.mjs +24 -0
- package/library/actions/web-dapp/wallet/_signature-log.mjs +32 -0
- package/library/actions/{terminal → web-dapp}/wallet/assert_signatures.mjs +8 -14
- package/library/actions/{terminal → web-dapp}/wallet/list_accounts.mjs +2 -2
- package/library/actions/web-dapp/wallet/read_signatures.mjs +38 -0
- package/library/manifests/core.action-manifest.json +6 -2
- package/library/manifests/extension.action-manifest.json +4 -0
- package/library/manifests/mobile.action-manifest.json +12 -0
- package/library/manifests/{terminal.action-manifest.json → web-dapp.action-manifest.json} +18 -702
- package/package.json +6 -6
- package/site/assets/help-recipes.json +1 -0
- package/adapters/terminal/lib/mainnet-guard.mjs +0 -177
- package/adapters/terminal/lib/origin.mjs +0 -30
- package/adapters/terminal/lib/page-script.mjs +0 -246
- package/adapters/terminal/lib/strict-wallet.mjs +0 -138
- package/dist/adapters/terminal/console-capture.js +0 -59
- package/dist/adapters/terminal/doctor-checks.js +0 -22
- package/dist/adapters/terminal/overlay.js +0 -18
- package/dist/adapters/terminal/readiness.js +0 -11
- package/dist/adapters/terminal/surface.js +0 -133
- package/dist/commands/call.js +0 -671
- package/dist/commands/list-executables.js +0 -245
- package/dist/commands/manifest.js +0 -477
- package/dist/commands/run-engine.js +0 -1301
- package/dist/commands/run.js +0 -730
- package/dist/discovery.js +0 -15
- package/dist/metamask-action-validation.js +0 -68
- package/dist/network-observation.js +0 -290
- package/library/actions/terminal/perps/_venue.mjs +0 -242
- package/library/actions/terminal/perps/assert_orders.mjs +0 -54
- package/library/actions/terminal/perps/assert_positions.mjs +0 -40
- package/library/actions/terminal/perps/read_orders.mjs +0 -26
- package/library/actions/terminal/perps/read_positions.mjs +0 -25
- package/library/actions/terminal/perps/read_snapshot.mjs +0 -40
- package/library/actions/terminal/perps/revoke_agent.mjs +0 -58
- package/library/actions/terminal/perps/teardown_state.mjs +0 -104
- package/library/actions/terminal/ui/navigate.mjs +0 -31
- package/library/actions/terminal/wallet/_signature-log.mjs +0 -177
- package/library/actions/terminal/wallet/read_signatures.mjs +0 -37
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { manifestPath } from "../../paths.js";
|
|
2
|
+
import { perpsOrderInputFindings } from "../perps-order-inputs.js";
|
|
2
3
|
const NATIVE_PROVIDER_UI_ACTIONS = /* @__PURE__ */ new Set([
|
|
3
4
|
"ui.press",
|
|
4
5
|
"ui.key_press",
|
|
@@ -47,7 +48,9 @@ const mobileActions = {
|
|
|
47
48
|
}
|
|
48
49
|
}),
|
|
49
50
|
wrap: async (adapters) => (await import("./runtime-identity.js")).withMobileNativeRuntimeIdentity(adapters),
|
|
50
|
-
lifecycle: async (adapters) => (await import("./lifecycle.js")).mobileSourceAwareLifecycleAdapters(adapters)
|
|
51
|
+
lifecycle: async (adapters) => (await import("./lifecycle.js")).mobileSourceAwareLifecycleAdapters(adapters),
|
|
52
|
+
// ensure_positions and ensure_orders with an open or present state place an order.
|
|
53
|
+
inputFindings: (nodeId, node) => perpsOrderInputFindings(nodeId, node, { ensureChecksOnly: false })
|
|
51
54
|
};
|
|
52
55
|
export {
|
|
53
56
|
mobileActions
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import brokerModule from "@farmslot/recipe-runner/cdp-broker";
|
|
3
|
+
import configModule from "@farmslot/adapter-rn/bridge-runtime/lib/config.cjs";
|
|
4
|
+
const { brokerSocketPath, createBrokerClient } = brokerModule;
|
|
5
|
+
const { resolvePort } = configModule;
|
|
6
|
+
async function createMobileNetworkBackend(target, env) {
|
|
7
|
+
const { client } = await connectMobileBroker(target, env);
|
|
8
|
+
return {
|
|
9
|
+
start(params) {
|
|
10
|
+
return client.control("capture-start", params, 1e4);
|
|
11
|
+
},
|
|
12
|
+
async end(id) {
|
|
13
|
+
return asRecord(await client.control("capture-end", { id }, 1e4));
|
|
14
|
+
},
|
|
15
|
+
async close() {
|
|
16
|
+
client.close();
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
async function connectMobileBroker(target, env) {
|
|
21
|
+
const runtimeDir = env.RECIPE_RUNTIME_DIR ? path.resolve(target, env.RECIPE_RUNTIME_DIR) : path.join(target, "temp", "recipe", "runtime");
|
|
22
|
+
const socketPath = brokerSocketPath(
|
|
23
|
+
runtimeDir,
|
|
24
|
+
resolvePort(env, target)
|
|
25
|
+
);
|
|
26
|
+
const discoveryClient = await createBrokerClient(socketPath, "", 1e4);
|
|
27
|
+
let targets;
|
|
28
|
+
try {
|
|
29
|
+
targets = await discoveryClient.control(
|
|
30
|
+
"resolve-targets",
|
|
31
|
+
{ nameIncludes: mobileTargetPin(env) },
|
|
32
|
+
1e4
|
|
33
|
+
);
|
|
34
|
+
} finally {
|
|
35
|
+
discoveryClient.close();
|
|
36
|
+
}
|
|
37
|
+
const deviceId = selectMobileBrokerTarget(targets, env);
|
|
38
|
+
const client = await createBrokerClient(socketPath, deviceId, 1e4);
|
|
39
|
+
return { client, deviceId };
|
|
40
|
+
}
|
|
41
|
+
function selectMobileBrokerTarget(value, env) {
|
|
42
|
+
const targets = Array.isArray(value) ? value.map(asRecord).filter((target) => target.deviceId) : [];
|
|
43
|
+
const pin = mobileTargetPin(env);
|
|
44
|
+
const candidates = pin ? targets.filter(
|
|
45
|
+
(target) => String(target.name ?? "").toLowerCase().includes(pin.toLowerCase())
|
|
46
|
+
) : targets;
|
|
47
|
+
if (candidates.length !== 1) {
|
|
48
|
+
throw new Error(
|
|
49
|
+
`Mobile network observation requires one broker target; found ${candidates.length}.`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
return String(candidates[0].deviceId);
|
|
53
|
+
}
|
|
54
|
+
function mobileTargetPin(env) {
|
|
55
|
+
const platform = String(
|
|
56
|
+
env.RECIPE_RN_EXPLICIT_PLATFORM ?? env.RECIPE_HARNESS_PLATFORM ?? ""
|
|
57
|
+
).toLowerCase();
|
|
58
|
+
return String(
|
|
59
|
+
platform === "android" ? env.ANDROID_TARGET_DEVICE_NAME ?? env.ANDROID_DEVICE ?? "" : platform === "ios" ? env.IOS_SIMULATOR ?? "" : env.IOS_SIMULATOR ?? env.ANDROID_TARGET_DEVICE_NAME ?? env.ANDROID_DEVICE ?? ""
|
|
60
|
+
).trim();
|
|
61
|
+
}
|
|
62
|
+
function asRecord(value) {
|
|
63
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
64
|
+
}
|
|
65
|
+
export {
|
|
66
|
+
connectMobileBroker,
|
|
67
|
+
createMobileNetworkBackend
|
|
68
|
+
};
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createCdpTraceCollector
|
|
3
3
|
} from "../performance/cdp-trace.js";
|
|
4
|
-
import {
|
|
5
|
-
connectMobileBroker
|
|
6
|
-
} from "../../network-observation.js";
|
|
4
|
+
import { connectMobileBroker } from "./network-observer.js";
|
|
7
5
|
import { createAndroidGfxinfoPerformanceBackend } from "./android-gfxinfo-performance.js";
|
|
8
6
|
async function createMobilePerformanceBackend(target, env) {
|
|
9
7
|
if (resolvePlatform(env) === "android") {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { isRecord } from "@farmslot/recipe-cli/harness";
|
|
1
|
+
import { EXIT, isRecord } from "@farmslot/recipe-cli/harness";
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { IOS_SIMULATOR_RECORDING_TARGET_PREFIX } from "@farmslot/adapter-rn";
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
resolveAndroidDeviceSerial,
|
|
9
9
|
resolveMobileIosSimulatorName
|
|
10
10
|
} from "./recording-target.js";
|
|
11
|
+
import { emitProofFailure } from "../proof-failure.js";
|
|
11
12
|
import { mobileAppIdentityEnv } from "./app-identity.js";
|
|
12
13
|
import { requireCurrentMobileReleaseArtifactState } from "./release-artifact-state.js";
|
|
13
14
|
const EXPLICIT_DEVICE_KEYS = [
|
|
@@ -51,7 +52,10 @@ const mobileRun = {
|
|
|
51
52
|
ADB_SERIAL: process.env.ADB_SERIAL,
|
|
52
53
|
ANDROID_SERIAL: process.env.ANDROID_SERIAL,
|
|
53
54
|
...mobileAppIdentityEnv(),
|
|
54
|
-
METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME: process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME
|
|
55
|
+
METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME: process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME,
|
|
56
|
+
// The platform `--platform`/`--device` pinned; the React Native bridge
|
|
57
|
+
// matches its target against it.
|
|
58
|
+
RECIPE_RN_EXPLICIT_PLATFORM: process.env.RECIPE_RN_EXPLICIT_PLATFORM
|
|
55
59
|
}),
|
|
56
60
|
// An installed release artifact is opaque: no harness HUD inside it.
|
|
57
61
|
autoHud: () => process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME !== "1",
|
|
@@ -65,12 +69,28 @@ const mobileRun = {
|
|
|
65
69
|
// source only matters for a development build the caller did not restart.
|
|
66
70
|
runtimeCheck({ target, heal, state, json, appRestartAuthored }) {
|
|
67
71
|
if (requireCurrentMobileReleaseArtifactState(target) !== null || appRestartAuthored) return void 0;
|
|
68
|
-
return
|
|
69
|
-
const { ensureMobileProofRuntime } = await import("../../commands/run-engine.js");
|
|
70
|
-
return ensureMobileProofRuntime(target, heal, state, json);
|
|
71
|
-
};
|
|
72
|
+
return () => ensureMobileProofRuntime(target, heal, state, json);
|
|
72
73
|
}
|
|
73
74
|
};
|
|
75
|
+
async function ensureMobileProofRuntime(target, _heal, state, json, deps = {}) {
|
|
76
|
+
const source = await import("./source-freshness.js");
|
|
77
|
+
const check = deps.check ?? (() => source.mobileSourceCheck(target));
|
|
78
|
+
const initial = check();
|
|
79
|
+
if (initial.status === "unavailable") {
|
|
80
|
+
emitProofFailure(json, { recoverable: false, error: initial.error });
|
|
81
|
+
return EXIT.usage;
|
|
82
|
+
}
|
|
83
|
+
if (initial.status === "current") return null;
|
|
84
|
+
const targetArg = JSON.stringify(path.resolve(target));
|
|
85
|
+
const userAction = `mm-harness call app.lifecycle --arg command=restart --adapter mobile --target ${targetArg}`;
|
|
86
|
+
const message = `Mobile loaded source is not current (${initial.status}).`;
|
|
87
|
+
emitProofFailure(json, {
|
|
88
|
+
recoverable: true,
|
|
89
|
+
mutations: state.mutations,
|
|
90
|
+
error: { code: "MOBILE_SOURCE_NOT_LOADED", message, userAction }
|
|
91
|
+
});
|
|
92
|
+
return EXIT.infra;
|
|
93
|
+
}
|
|
74
94
|
const mobileRecording = {
|
|
75
95
|
target: (context) => resolveMobileRecordingTarget(context),
|
|
76
96
|
// An Android device records through the mirror, with screenrecord as the
|
|
@@ -127,11 +147,13 @@ const mobileDiagnostics = {
|
|
|
127
147
|
};
|
|
128
148
|
const mobileObservation = {
|
|
129
149
|
network: {
|
|
130
|
-
backend: async (target, env) => (await import("
|
|
150
|
+
backend: async (target, env) => (await import("./network-observer.js")).createMobileNetworkBackend(target, env)
|
|
131
151
|
},
|
|
132
152
|
performance: {
|
|
133
|
-
|
|
134
|
-
|
|
153
|
+
start: async (context) => (await import("../../performance-observation.js")).startRunPerformanceObservation({
|
|
154
|
+
backend: async (target, env) => (await import("./performance-observer.js")).createMobilePerformanceBackend(target, env),
|
|
155
|
+
platform: (env) => devicePlatformFromEnv(env)
|
|
156
|
+
}, context)
|
|
135
157
|
}
|
|
136
158
|
};
|
|
137
159
|
function runMobileIssueCommand(projectRoot, command) {
|
|
@@ -150,6 +172,7 @@ function runMobileIssueCommand(projectRoot, command) {
|
|
|
150
172
|
}
|
|
151
173
|
}
|
|
152
174
|
export {
|
|
175
|
+
ensureMobileProofRuntime,
|
|
153
176
|
mobileDiagnostics,
|
|
154
177
|
mobileObservation,
|
|
155
178
|
mobileRecording,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
function perpsOrderInputFindings(nodeId, node, { ensureChecksOnly }) {
|
|
2
|
+
const action = node.action;
|
|
3
|
+
const state = typeof node.state === "string" ? node.state.toLowerCase() : void 0;
|
|
4
|
+
const createsOrder = action === "metamask.perps.place_order" || !ensureChecksOnly && (action === "metamask.perps.ensure_positions" || action === "metamask.perps.ensure_orders") && (state === "open" || state === "present");
|
|
5
|
+
if (!createsOrder) return [];
|
|
6
|
+
const findings = [];
|
|
7
|
+
const requireOneOf = (names, message) => {
|
|
8
|
+
const present = names.some((name) => {
|
|
9
|
+
const value = node[name];
|
|
10
|
+
return value !== void 0 && value !== null && value !== "";
|
|
11
|
+
});
|
|
12
|
+
if (present) return;
|
|
13
|
+
findings.push({
|
|
14
|
+
severity: "error",
|
|
15
|
+
code: "recipe.missing_param",
|
|
16
|
+
path: `workflow.nodes.${nodeId}.${names[0]}`,
|
|
17
|
+
message
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
requireOneOf(
|
|
21
|
+
["market", "symbol"],
|
|
22
|
+
`${action} requires market=<symbol> (or symbol=<symbol>) before it may create an order`
|
|
23
|
+
);
|
|
24
|
+
requireOneOf(
|
|
25
|
+
["side"],
|
|
26
|
+
`${action} requires side=long or side=short before it may create an order`
|
|
27
|
+
);
|
|
28
|
+
requireOneOf(
|
|
29
|
+
["amount", "notional"],
|
|
30
|
+
`${action} requires amount=<usd> or notional=<usd> before it may create an order`
|
|
31
|
+
);
|
|
32
|
+
return findings;
|
|
33
|
+
}
|
|
34
|
+
export {
|
|
35
|
+
perpsOrderInputFindings
|
|
36
|
+
};
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import {
|
|
3
|
+
adapterPlugin,
|
|
4
|
+
adapterSelectionFailureOut,
|
|
5
|
+
ensureAdapterLoaded,
|
|
6
|
+
selectedAdapterId
|
|
7
|
+
} from "@farmslot/recipe-cli/harness";
|
|
8
|
+
import { configPath, declaredLibraryPathEnv, readConfig } from "../review/knowledge.js";
|
|
9
|
+
import { adapterRegistry } from "./registry.js";
|
|
10
|
+
import { bindWebDappPolicy } from "./web-dapp/surface.js";
|
|
11
|
+
function configuredLibrarySources() {
|
|
12
|
+
let libraries;
|
|
13
|
+
try {
|
|
14
|
+
libraries = readConfig(configPath()).libraries;
|
|
15
|
+
} catch {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
return Object.entries(libraries ?? {}).map(([name, root]) => ({ name, root: path.resolve(root) }));
|
|
19
|
+
}
|
|
20
|
+
function operatorLibraryEnv() {
|
|
21
|
+
const declared = declaredLibraryPathEnv();
|
|
22
|
+
if (!declared) return process.env;
|
|
23
|
+
const env = { ...process.env };
|
|
24
|
+
if (declared.value === void 0) delete env.RECIPE_LIBRARY_PATH;
|
|
25
|
+
else env.RECIPE_LIBRARY_PATH = declared.value;
|
|
26
|
+
return env;
|
|
27
|
+
}
|
|
28
|
+
async function loadSelectedAdapter(command, args) {
|
|
29
|
+
const passthrough = args.indexOf("--");
|
|
30
|
+
const own = passthrough < 0 ? args : args.slice(0, passthrough);
|
|
31
|
+
const libraries = [];
|
|
32
|
+
own.forEach((arg, index) => {
|
|
33
|
+
if (arg === "--library" && own[index + 1] !== void 0) libraries.push(own[index + 1]);
|
|
34
|
+
else if (arg.startsWith("--library=")) libraries.push(arg.slice("--library=".length));
|
|
35
|
+
});
|
|
36
|
+
const selected = selectedAdapterId(args);
|
|
37
|
+
try {
|
|
38
|
+
await ensureAdapterLoaded(selected, {
|
|
39
|
+
libraries,
|
|
40
|
+
configured: configuredLibrarySources(),
|
|
41
|
+
env: operatorLibraryEnv(),
|
|
42
|
+
adopt: adoptPluginAdapter
|
|
43
|
+
});
|
|
44
|
+
return void 0;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
const refusal = error;
|
|
47
|
+
if (typeof refusal.code !== "string" || typeof refusal.userAction !== "string") throw error;
|
|
48
|
+
if (selected !== void 0 && adapterRegistry.has(selected) && refusal.code !== "ADAPTER_ID_CONFLICT") {
|
|
49
|
+
return void 0;
|
|
50
|
+
}
|
|
51
|
+
return adapterSelectionFailureOut(own.includes("--json"), command, {
|
|
52
|
+
code: refusal.code,
|
|
53
|
+
message: String(refusal.message),
|
|
54
|
+
userAction: refusal.userAction
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
const ADAPTER_PLUGIN_ENV = "METAMASK_RECIPE_ADAPTER_PLUGIN";
|
|
59
|
+
function adoptPluginAdapter(adapter) {
|
|
60
|
+
const surface = bindWebDappPolicy(adapter);
|
|
61
|
+
return {
|
|
62
|
+
...surface,
|
|
63
|
+
run: {
|
|
64
|
+
...surface.run,
|
|
65
|
+
envKeys: [...surface.run?.envKeys ?? [], ADAPTER_PLUGIN_ENV],
|
|
66
|
+
activateEnv(projectRoot, options) {
|
|
67
|
+
surface.run?.activateEnv?.(projectRoot, options);
|
|
68
|
+
process.env[ADAPTER_PLUGIN_ENV] = `${adapter.id} ${adapterPlugin(adapter.id)?.digest ?? "unknown"}`;
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
runwayProvision: surface.runwayProvision ?? {
|
|
72
|
+
async run(target) {
|
|
73
|
+
return {
|
|
74
|
+
schemaVersion: 1,
|
|
75
|
+
command: "provision",
|
|
76
|
+
adapter: adapter.id,
|
|
77
|
+
target: path.resolve(target),
|
|
78
|
+
status: "fail",
|
|
79
|
+
exitCode: 2,
|
|
80
|
+
error: {
|
|
81
|
+
code: "UNSUPPORTED_ADAPTER",
|
|
82
|
+
message: `${adapter.id} has no mobile simulator to provision.`,
|
|
83
|
+
userAction: `mm-harness run <recipe> --adapter ${adapter.id}`
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
readiness: surface.readiness ?? { compatibilityMode: () => "unsupported/no bridge" },
|
|
89
|
+
fixtures: surface.fixtures ?? { operations: [] }
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export {
|
|
93
|
+
ADAPTER_PLUGIN_ENV,
|
|
94
|
+
adoptPluginAdapter,
|
|
95
|
+
configuredLibrarySources,
|
|
96
|
+
loadSelectedAdapter,
|
|
97
|
+
operatorLibraryEnv
|
|
98
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
function emitProofFailure(json, failure) {
|
|
2
|
+
if (json) {
|
|
3
|
+
console.log(JSON.stringify({ schemaVersion: 1, status: "fail", ...failure }, null, 2));
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
console.error(`\u2717 ${failure.error.message}`);
|
|
7
|
+
console.error(` Next: ${failure.error.userAction}`);
|
|
8
|
+
}
|
|
9
|
+
export {
|
|
10
|
+
emitProofFailure
|
|
11
|
+
};
|
package/dist/adapters/surface.js
CHANGED
|
@@ -3,7 +3,7 @@ import { adapterRegistry } from "./registry.js";
|
|
|
3
3
|
import "./mobile/surface.js";
|
|
4
4
|
import "./extension/surface.js";
|
|
5
5
|
import "./core/surface.js";
|
|
6
|
-
import "./
|
|
6
|
+
import "./web-dapp/surface.js";
|
|
7
7
|
function getAdapterSurface(adapter) {
|
|
8
8
|
return adapterRegistry.get(adapter);
|
|
9
9
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { manifestPath } from "../../paths.js";
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import { perpsOrderInputFindings } from "../perps-order-inputs.js";
|
|
3
|
+
const webDappActions = {
|
|
4
|
+
manifestPath: () => manifestPath("web-dapp"),
|
|
4
5
|
semantic: [
|
|
5
6
|
"app.network_capture",
|
|
6
7
|
"app.network_assert",
|
|
@@ -11,8 +12,8 @@ const terminalActions = {
|
|
|
11
12
|
ui: (harness, { hudPolicy }) => ({
|
|
12
13
|
base: harness.createCdpWebUiTransport({
|
|
13
14
|
async withPage(input, callback) {
|
|
14
|
-
const {
|
|
15
|
-
return
|
|
15
|
+
const { withAppPage } = await import("../../../library/actions/web-dapp/platform/page.mjs");
|
|
16
|
+
return withAppPage(
|
|
16
17
|
{
|
|
17
18
|
action: input.action,
|
|
18
19
|
node: input.node,
|
|
@@ -28,16 +29,18 @@ const terminalActions = {
|
|
|
28
29
|
}),
|
|
29
30
|
async execute(action, node, context, standard) {
|
|
30
31
|
if (action === "app.hud") {
|
|
31
|
-
const {
|
|
32
|
-
return
|
|
32
|
+
const { showAppHud } = await import("../../../library/actions/web-dapp/platform/hud.mjs");
|
|
33
|
+
return showAppHud(
|
|
33
34
|
{ action, node, context: { nodeId: context.nodeId, projectRoot: context.projectRoot } },
|
|
34
35
|
{ hudPolicy }
|
|
35
36
|
);
|
|
36
37
|
}
|
|
37
38
|
return standard();
|
|
38
39
|
}
|
|
39
|
-
})
|
|
40
|
+
}),
|
|
41
|
+
// ensure_positions and ensure_orders with an open or present state place an order.
|
|
42
|
+
inputFindings: (nodeId, node) => perpsOrderInputFindings(nodeId, node, { ensureChecksOnly: false })
|
|
40
43
|
};
|
|
41
44
|
export {
|
|
42
|
-
|
|
45
|
+
webDappActions
|
|
43
46
|
};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { recipeRuntimePath } from "@farmslot/recipe-cli/harness";
|
|
4
|
+
import { verifyCapture } from "../../../library/actions/shared/console-collector.mjs";
|
|
5
|
+
import { ensureConsoleCollector } from "../console-capture.js";
|
|
6
|
+
import { webDappAdapterId } from "../../../adapters/web-dapp/lib/policy.mjs";
|
|
7
|
+
const WEB_DAPP_CONSOLE_FILES = Object.freeze({
|
|
8
|
+
app: "app-console.log",
|
|
9
|
+
extension: "extension-console.log",
|
|
10
|
+
collectorLog: "console-tail.log",
|
|
11
|
+
pidFile: "console-tail.pid"
|
|
12
|
+
});
|
|
13
|
+
function webDappRuntimeFile(target, name) {
|
|
14
|
+
let root = path.resolve(target);
|
|
15
|
+
try {
|
|
16
|
+
root = fs.realpathSync(root);
|
|
17
|
+
} catch {
|
|
18
|
+
}
|
|
19
|
+
return recipeRuntimePath(root, webDappAdapterId(), name);
|
|
20
|
+
}
|
|
21
|
+
function webDappCaptureFiles(target) {
|
|
22
|
+
return {
|
|
23
|
+
extensionLog: webDappRuntimeFile(target, WEB_DAPP_CONSOLE_FILES.extension),
|
|
24
|
+
pageLog: webDappRuntimeFile(target, WEB_DAPP_CONSOLE_FILES.app),
|
|
25
|
+
collectorLog: webDappRuntimeFile(target, WEB_DAPP_CONSOLE_FILES.collectorLog),
|
|
26
|
+
pidFile: webDappRuntimeFile(target, WEB_DAPP_CONSOLE_FILES.pidFile)
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function webDappCdpPort() {
|
|
30
|
+
return process.env.RECIPE_CDP_PORT ?? process.env.CDP_PORT;
|
|
31
|
+
}
|
|
32
|
+
async function ensureWebDappConsoleCapture(target) {
|
|
33
|
+
const cdpPort = webDappCdpPort();
|
|
34
|
+
if (!cdpPort) {
|
|
35
|
+
const id = webDappAdapterId();
|
|
36
|
+
throw new Error(`${id.charAt(0).toUpperCase()}${id.slice(1)} console capture requires the slot CDP port.`);
|
|
37
|
+
}
|
|
38
|
+
await ensureConsoleCollector({
|
|
39
|
+
cdpPort,
|
|
40
|
+
...webDappCaptureFiles(target),
|
|
41
|
+
waitForAttachment: false,
|
|
42
|
+
cwd: target
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
async function verifyWebDappConsoleControl(capture) {
|
|
46
|
+
let browser = null;
|
|
47
|
+
try {
|
|
48
|
+
const value = JSON.parse(fs.readFileSync(webDappRuntimeFile(capture.projectRoot, "browser.json"), "utf8"));
|
|
49
|
+
browser = typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
50
|
+
} catch {
|
|
51
|
+
browser = null;
|
|
52
|
+
}
|
|
53
|
+
if (typeof browser?.appOrigin !== "string") return { ok: false, detail: "no slot browser state (browser.json) to verify the page console capture against" };
|
|
54
|
+
return verifyCapture({ cdpPort: capture.cdpPort, log: capture.pageLog, target: "page", appOrigin: browser.appOrigin });
|
|
55
|
+
}
|
|
56
|
+
export {
|
|
57
|
+
WEB_DAPP_CONSOLE_FILES,
|
|
58
|
+
ensureWebDappConsoleCapture,
|
|
59
|
+
verifyWebDappConsoleControl,
|
|
60
|
+
webDappCaptureFiles,
|
|
61
|
+
webDappCdpPort,
|
|
62
|
+
webDappRuntimeFile
|
|
63
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { POLICY_ENV, webDappPolicy } from "../../../adapters/web-dapp/lib/policy.mjs";
|
|
2
|
+
import { webDappAppPort, webDappCdpPort } from "./surface.js";
|
|
3
|
+
async function webDappDoctorChecks(target) {
|
|
4
|
+
if (!process.env[POLICY_ENV]) {
|
|
5
|
+
return [{
|
|
6
|
+
id: "web-dapp-venue-policy",
|
|
7
|
+
status: "fail",
|
|
8
|
+
required: true,
|
|
9
|
+
message: "web-dapp needs a venue policy: select an adapter that extends it (e.g. --adapter terminal with its recipe library on RECIPE_LIBRARY_PATH)."
|
|
10
|
+
}];
|
|
11
|
+
}
|
|
12
|
+
const policy = webDappPolicy();
|
|
13
|
+
const { webDappReadiness } = await import("../../../adapters/web-dapp/lib/readiness.mjs");
|
|
14
|
+
const { resolveBrowser } = await import("../../../adapters/web-dapp/launch.mjs");
|
|
15
|
+
const report = await webDappReadiness({
|
|
16
|
+
policy,
|
|
17
|
+
target,
|
|
18
|
+
appPort: webDappAppPort(),
|
|
19
|
+
cdpPort: webDappCdpPort(),
|
|
20
|
+
signer: process.env.TERMINAL_SIGNER || "extension",
|
|
21
|
+
account: process.env.TERMINAL_ACCOUNT || "dev1",
|
|
22
|
+
probeBrowser: resolveBrowser
|
|
23
|
+
});
|
|
24
|
+
return report.checks.map((check) => ({
|
|
25
|
+
id: `${policy.adapterId}-${check.id}`,
|
|
26
|
+
status: check.status === "pass" ? "pass" : "fail",
|
|
27
|
+
required: check.required,
|
|
28
|
+
message: check.detail
|
|
29
|
+
}));
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
webDappDoctorChecks
|
|
33
|
+
};
|
|
@@ -2,7 +2,8 @@ import { flag, str } from "@farmslot/recipe-cli/harness";
|
|
|
2
2
|
import { execFileSync } from "node:child_process";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { runnerDir } from "../../paths.js";
|
|
5
|
-
|
|
5
|
+
import { webDappAdapterId } from "../../../adapters/web-dapp/lib/policy.mjs";
|
|
6
|
+
const WEB_DAPP_ONLY_LAUNCH_OPTIONS = Object.freeze({
|
|
6
7
|
signer: "--signer",
|
|
7
8
|
account: "--account",
|
|
8
9
|
freshProfile: "--fresh-profile",
|
|
@@ -12,12 +13,12 @@ const TERMINAL_ONLY_LAUNCH_OPTIONS = Object.freeze({
|
|
|
12
13
|
network: "--network",
|
|
13
14
|
mainnetConfirmation: "--mainnet-confirmation"
|
|
14
15
|
});
|
|
15
|
-
function
|
|
16
|
-
return Object.entries(
|
|
16
|
+
function webDappOnlyLaunchFlags(options) {
|
|
17
|
+
return Object.entries(WEB_DAPP_ONLY_LAUNCH_OPTIONS).filter(([key]) => options[key] !== void 0 && options[key] !== false).map(([, name]) => name);
|
|
17
18
|
}
|
|
18
|
-
function
|
|
19
|
+
function parseWebDappLauncherOutput(output) {
|
|
19
20
|
const text = output.trim();
|
|
20
|
-
if (!text) return { status: "fail", error:
|
|
21
|
+
if (!text) return { status: "fail", error: `the ${webDappAdapterId()} launcher printed no summary` };
|
|
21
22
|
try {
|
|
22
23
|
return JSON.parse(text);
|
|
23
24
|
} catch {
|
|
@@ -25,21 +26,22 @@ function parseTerminalLauncherOutput(output) {
|
|
|
25
26
|
try {
|
|
26
27
|
return JSON.parse(last);
|
|
27
28
|
} catch {
|
|
28
|
-
return { status: "fail", error: `the
|
|
29
|
+
return { status: "fail", error: `the ${webDappAdapterId()} launcher printed no JSON summary: ${last.slice(0, 200)}` };
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
}
|
|
32
|
-
async function
|
|
33
|
+
async function launchWebDapp({ options, target, jsonOutput, stream, usage }) {
|
|
34
|
+
const id = webDappAdapterId();
|
|
33
35
|
const cdpPort = str(options, "cdpPort") ?? process.env.RECIPE_CDP_PORT ?? process.env.CDP_PORT;
|
|
34
36
|
const appPort = str(options, "watcherPort") ?? process.env.TERMINAL_APP_PORT ?? process.env.RECIPE_WATCHER_PORT ?? process.env.WATCHER_PORT;
|
|
35
37
|
if (!cdpPort || !appPort) {
|
|
36
38
|
return usage(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
+
`${id} launch needs the slot browser CDP port and the slot dev-server port.`,
|
|
40
|
+
`mm-harness launch --adapter ${id} --cdp-port <port> --watcher-port <app port>`
|
|
39
41
|
);
|
|
40
42
|
}
|
|
41
43
|
const args = [
|
|
42
|
-
path.join(runnerDir, "adapters", "
|
|
44
|
+
path.join(runnerDir, "adapters", "web-dapp", "launch.mjs"),
|
|
43
45
|
"--target",
|
|
44
46
|
target,
|
|
45
47
|
"--cdp-port",
|
|
@@ -61,7 +63,7 @@ async function launchTerminal({ options, target, jsonOutput, stream, usage }) {
|
|
|
61
63
|
if (network) args.push("--network", network);
|
|
62
64
|
const mainnetConfirmation = str(options, "mainnetConfirmation");
|
|
63
65
|
if (mainnetConfirmation) args.push("--mainnet-confirmation", mainnetConfirmation);
|
|
64
|
-
stream.phase("launch", { adapter:
|
|
66
|
+
stream.phase("launch", { adapter: id, cdpPort, appPort });
|
|
65
67
|
let output = "";
|
|
66
68
|
let status = 0;
|
|
67
69
|
try {
|
|
@@ -70,16 +72,16 @@ async function launchTerminal({ options, target, jsonOutput, stream, usage }) {
|
|
|
70
72
|
status = 1;
|
|
71
73
|
output = String(error.stdout ?? "");
|
|
72
74
|
}
|
|
73
|
-
const result =
|
|
74
|
-
if (jsonOutput) console.log(JSON.stringify({ schemaVersion: 1, command: "launch", adapter:
|
|
75
|
-
else if (result.status === "pass") console.log(`\u2713
|
|
76
|
-
else console.error(`\u2717
|
|
75
|
+
const result = parseWebDappLauncherOutput(output);
|
|
76
|
+
if (jsonOutput) console.log(JSON.stringify({ schemaVersion: 1, command: "launch", adapter: id, target, ...result }, null, 2));
|
|
77
|
+
else if (result.status === "pass") console.log(`\u2713 ${id} browser ${result.reused ? "reused" : "started"} on CDP ${result.cdpPort} (${result.signer}, ${result.account?.name})`);
|
|
78
|
+
else console.error(`\u2717 ${id} launch failed: ${result.error ?? "unknown error"}`);
|
|
77
79
|
stream.complete(result.status === "pass" ? "pass" : "fail", status);
|
|
78
80
|
return result.status === "pass" ? 0 : 1;
|
|
79
81
|
}
|
|
80
82
|
export {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
WEB_DAPP_ONLY_LAUNCH_OPTIONS,
|
|
84
|
+
launchWebDapp,
|
|
85
|
+
parseWebDappLauncherOutput,
|
|
86
|
+
webDappOnlyLaunchFlags
|
|
85
87
|
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { runnerDir } from "../../paths.js";
|
|
3
|
+
const webDappHarness = {
|
|
4
|
+
install: { entry: "adapters/web-dapp/inject.mjs", fallback: "adapters/web-dapp/inject.mjs", node: true },
|
|
5
|
+
cleanup: { entry: "adapters/web-dapp/cleanup.mjs", fallback: "adapters/web-dapp/cleanup.mjs", node: true },
|
|
6
|
+
verify: () => ({
|
|
7
|
+
command: process.execPath,
|
|
8
|
+
prefixArgs: [path.join(runnerDir, "adapters", "web-dapp", "verify.mjs")]
|
|
9
|
+
}),
|
|
10
|
+
restart: "mm-harness launch"
|
|
11
|
+
};
|
|
12
|
+
const webDappRuntimeContext = {
|
|
13
|
+
forbiddenFields: ["metroPort", "simulator", "simulatorUdid", "adbSerial", "extensionId"],
|
|
14
|
+
resources: ({ existing, defaults, envPort }) => ({
|
|
15
|
+
cdpPort: envPort("RECIPE_CDP_PORT", "CDP_PORT") ?? existing.cdpPort ?? defaults.cdpPort
|
|
16
|
+
})
|
|
17
|
+
};
|
|
18
|
+
export {
|
|
19
|
+
webDappHarness,
|
|
20
|
+
webDappRuntimeContext
|
|
21
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { POLICY_ENV, webDappPolicy } from "../../../adapters/web-dapp/lib/policy.mjs";
|
|
2
|
+
const webDappReadiness = {
|
|
3
|
+
compatibilityMode: (target) => process.env[POLICY_ENV] && webDappPolicy().checkout.matches(target) ? "slot browser over CDP (website; no product bridge)" : "unsupported/no bridge",
|
|
4
|
+
liveChecks: async (target) => (await import("./doctor-checks.js")).webDappDoctorChecks(target),
|
|
5
|
+
// Screenshots come from the slot browser over CDP.
|
|
6
|
+
captureProviders: ["cdp"]
|
|
7
|
+
};
|
|
8
|
+
export {
|
|
9
|
+
webDappReadiness
|
|
10
|
+
};
|
|
@@ -2,21 +2,21 @@ import { isRecord } from "@farmslot/recipe-cli/harness";
|
|
|
2
2
|
import { verifyOwnedCollectorControl } from "../console-capture.js";
|
|
3
3
|
import { devicePlatformFromEnv } from "../performance/device-platform.js";
|
|
4
4
|
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
ensureWebDappConsoleCapture,
|
|
6
|
+
webDappCaptureFiles,
|
|
7
|
+
webDappCdpPort,
|
|
8
|
+
webDappRuntimeFile,
|
|
9
|
+
verifyWebDappConsoleControl
|
|
10
10
|
} from "./console-capture.js";
|
|
11
|
-
const
|
|
11
|
+
const webDappDiagnostics = {
|
|
12
12
|
console: {
|
|
13
|
-
start: (projectRoot) =>
|
|
14
|
-
files: (projectRoot) =>
|
|
15
|
-
cdpPort: () =>
|
|
16
|
-
verifyControl: (capture) => verifyOwnedCollectorControl(capture, () =>
|
|
13
|
+
start: (projectRoot) => ensureWebDappConsoleCapture(projectRoot),
|
|
14
|
+
files: (projectRoot) => webDappCaptureFiles(projectRoot),
|
|
15
|
+
cdpPort: () => webDappCdpPort(),
|
|
16
|
+
verifyControl: (capture) => verifyOwnedCollectorControl(capture, () => verifyWebDappConsoleControl(capture))
|
|
17
17
|
},
|
|
18
18
|
requestLog: {
|
|
19
|
-
path: (projectRoot) =>
|
|
19
|
+
path: (projectRoot) => webDappRuntimeFile(projectRoot, "wallet-requests.jsonl"),
|
|
20
20
|
findings: (lines) => walletRequestFindings(lines)
|
|
21
21
|
}
|
|
22
22
|
};
|
|
@@ -38,16 +38,21 @@ function walletRequestFindings(lines) {
|
|
|
38
38
|
}
|
|
39
39
|
return findings;
|
|
40
40
|
}
|
|
41
|
-
const
|
|
41
|
+
const webDappObservation = {
|
|
42
42
|
network: {
|
|
43
|
-
backend: async (_target, env, artifactsDir) => (await import("
|
|
43
|
+
backend: async (_target, env, artifactsDir) => (await import("../extension/network-observer.js")).createExtensionNetworkBackend(env, artifactsDir)
|
|
44
44
|
},
|
|
45
45
|
performance: {
|
|
46
|
-
|
|
47
|
-
|
|
46
|
+
start: async (context) => {
|
|
47
|
+
const observation = await import("../../performance-observation.js");
|
|
48
|
+
return observation.startRunPerformanceObservation({
|
|
49
|
+
backend: (target, env) => observation.createExtensionPerformanceObserver(target, env),
|
|
50
|
+
platform: (env) => devicePlatformFromEnv(env)
|
|
51
|
+
}, context);
|
|
52
|
+
}
|
|
48
53
|
}
|
|
49
54
|
};
|
|
50
55
|
export {
|
|
51
|
-
|
|
52
|
-
|
|
56
|
+
webDappDiagnostics,
|
|
57
|
+
webDappObservation
|
|
53
58
|
};
|