@deeeed/metamask-harness 0.2.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 +161 -0
- package/README.md +140 -0
- package/bin/mm-harness +99 -0
- package/docs/CHEATSHEET.md +61 -0
- package/docs/CLI-SPEC.md +915 -0
- package/docs/MENTAL-MODEL.md +295 -0
- package/docs/architecture.md +367 -0
- package/docs/extension-runtime-commands.md +60 -0
- package/docs/harness-cli.md +43 -0
- package/docs/live-adapter-contract.md +188 -0
- package/docs/package-boundaries.md +47 -0
- package/docs/perps-flow-catalog.md +235 -0
- package/docs/recipe-libraries.md +95 -0
- package/docs/runtime-file-conventions.md +36 -0
- package/library/actions/core/perps/_controller.mjs +727 -0
- package/library/actions/core/perps/assert_orders.mjs +53 -0
- package/library/actions/core/perps/assert_positions.mjs +52 -0
- package/library/actions/core/perps/close_orders.mjs +97 -0
- package/library/actions/core/perps/close_positions.mjs +118 -0
- package/library/actions/core/perps/ensure_orders.mjs +40 -0
- package/library/actions/core/perps/ensure_positions.mjs +37 -0
- package/library/actions/core/perps/place_order.mjs +201 -0
- package/library/actions/core/perps/read_account.mjs +30 -0
- package/library/actions/core/perps/read_orders.mjs +27 -0
- package/library/actions/core/perps/read_positions.mjs +27 -0
- package/library/actions/core/perps/start_state.mjs +92 -0
- package/library/actions/core/perps/teardown_state.mjs +86 -0
- package/library/actions/extension/perps/assert_orders.mjs +11 -0
- package/library/actions/extension/perps/assert_positions.mjs +11 -0
- package/library/actions/extension/perps/close_orders.mjs +8 -0
- package/library/actions/extension/perps/close_positions.mjs +8 -0
- package/library/actions/extension/perps/ensure_orders.mjs +4 -0
- package/library/actions/extension/perps/ensure_positions.mjs +4 -0
- package/library/actions/extension/perps/perps.mjs +730 -0
- package/library/actions/extension/perps/place_order.mjs +7 -0
- package/library/actions/extension/perps/read_orders.mjs +4 -0
- package/library/actions/extension/perps/read_positions.mjs +3 -0
- package/library/actions/extension/platform/cdp.mjs +541 -0
- package/library/actions/extension/ui/navigate.mjs +44 -0
- package/library/actions/extension/wallet/ensure_unlocked.mjs +36 -0
- package/library/actions/extension/wallet/read_state.mjs +27 -0
- package/library/actions/extension/wallet/select_account.mjs +48 -0
- package/library/actions/extension/wallet/setup.mjs +35 -0
- package/library/actions/mobile/app-overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +185 -0
- package/library/actions/mobile/app-overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +1662 -0
- package/library/actions/mobile/bridge-runtime/cdp-bridge.cjs +686 -0
- package/library/actions/mobile/bridge-runtime/lib/cdp-eval.cjs +110 -0
- package/library/actions/mobile/bridge-runtime/lib/config.cjs +39 -0
- package/library/actions/mobile/bridge-runtime/lib/issue-capture.cjs +446 -0
- package/library/actions/mobile/bridge-runtime/lib/target-discovery.cjs +204 -0
- package/library/actions/mobile/bridge-runtime/lib/ws-client.cjs +108 -0
- package/library/actions/mobile/bridge-runtime/setup-wallet.sh +442 -0
- package/library/actions/mobile/perps/assert_orders.mjs +11 -0
- package/library/actions/mobile/perps/assert_positions.mjs +11 -0
- package/library/actions/mobile/perps/close_orders.mjs +8 -0
- package/library/actions/mobile/perps/close_positions.mjs +8 -0
- package/library/actions/mobile/perps/ensure_orders.mjs +4 -0
- package/library/actions/mobile/perps/ensure_positions.mjs +4 -0
- package/library/actions/mobile/perps/perps.mjs +709 -0
- package/library/actions/mobile/perps/place_order.mjs +7 -0
- package/library/actions/mobile/perps/read_orders.mjs +4 -0
- package/library/actions/mobile/perps/read_positions.mjs +3 -0
- package/library/actions/mobile/platform/bridge.mjs +283 -0
- package/library/actions/mobile/ui/navigate.mjs +38 -0
- package/library/actions/mobile/wallet/ensure_unlocked.mjs +107 -0
- package/library/actions/mobile/wallet/home.mjs +35 -0
- package/library/actions/mobile/wallet/read_state.mjs +40 -0
- package/library/actions/mobile/wallet/select_account.mjs +48 -0
- package/library/actions/mobile/wallet/setup.mjs +220 -0
- package/library/flows/perps.flows.json +64 -0
- package/library/library.json +7 -0
- package/library/manifests/core.action-manifest.json +1282 -0
- package/library/manifests/extension.action-manifest.json +1749 -0
- package/library/manifests/mobile.action-manifest.json +1753 -0
- package/library/recipes/action-validation.extension.recipe.json +417 -0
- package/library/recipes/action-validation.mobile.recipe.json +422 -0
- package/library/recipes/order-lifecycle.core.recipe.json +78 -0
- package/library/recipes/perps-lifecycle.recipe.json +194 -0
- package/library/recipes/read-markets.core.recipe.json +38 -0
- package/library/recipes/smoke.extension.recipe.json +31 -0
- package/library/recipes/smoke.mobile.recipe.json +31 -0
- package/library/recipes/trading-lifecycle.core.recipe.json +76 -0
- package/orchestration/compat-overlays/README.md +19 -0
- package/orchestration/compat-overlays/mobile/README.md +13 -0
- package/orchestration/compat-overlays/mobile/rn81-message-event-source.patch +42 -0
- package/orchestration/core/cleanup.sh +37 -0
- package/orchestration/core/inject.sh +154 -0
- package/orchestration/doctor.mjs +72 -0
- package/orchestration/extension/cleanup.mjs +60 -0
- package/orchestration/extension/console-tail.mjs +228 -0
- package/orchestration/extension/ensure-browser.sh +416 -0
- package/orchestration/extension/ensure-ready.ts +185 -0
- package/orchestration/extension/extension-id.ts +107 -0
- package/orchestration/extension/inject.mjs +266 -0
- package/orchestration/extension/launch-browser.cjs +216 -0
- package/orchestration/extension/launch.sh +175 -0
- package/orchestration/extension/live.sh +320 -0
- package/orchestration/extension/pin-remote-flags.cjs +45 -0
- package/orchestration/extension/readiness.mjs +414 -0
- package/orchestration/extension/refresh-build.sh +190 -0
- package/orchestration/extension/runtime-decision.ts +445 -0
- package/orchestration/extension/runtime.ts +407 -0
- package/orchestration/extension/seed-fixture.sh +177 -0
- package/orchestration/extension/sidepanel-toggle.sh +291 -0
- package/orchestration/extension/snapshot-dist.sh +84 -0
- package/orchestration/extension/start-watch.sh +339 -0
- package/orchestration/extension/wallet-fixture-state.cjs +1086 -0
- package/orchestration/lib/activate-repo-node.sh +144 -0
- package/orchestration/lib/cli-color.mjs +84 -0
- package/orchestration/lib/cli-commands.mjs +243 -0
- package/orchestration/lib/cli-home.mjs +354 -0
- package/orchestration/lib/cli-ux.sh +252 -0
- package/orchestration/lib/cli-version.mjs +123 -0
- package/orchestration/lib/ensure-runner-deps.sh +56 -0
- package/orchestration/lib/harness-path.sh +55 -0
- package/orchestration/lib/hash-helpers.sh +44 -0
- package/orchestration/lib/json-field.sh +23 -0
- package/orchestration/lib/log-tui.mjs +304 -0
- package/orchestration/lib/open-debug.mjs +317 -0
- package/orchestration/lib/path-defaults.json +4 -0
- package/orchestration/lib/progress.mjs +107 -0
- package/orchestration/lib/recipe-paths.mjs +26 -0
- package/orchestration/lib/resolve-farmslot-ports.sh +144 -0
- package/orchestration/manifest.json +358 -0
- package/orchestration/mobile/cleanup.sh +192 -0
- package/orchestration/mobile/deps-markers.ts +21 -0
- package/orchestration/mobile/inject.sh +681 -0
- package/orchestration/mobile/launch.sh +137 -0
- package/orchestration/mobile/live.sh +125 -0
- package/orchestration/mobile/runtime-decision.ts +292 -0
- package/orchestration/porcelain/metamask-recipe +99 -0
- package/orchestration/porcelain/mm-recipe +1591 -0
- package/orchestration/porcelain/mme-recipe +1181 -0
- package/package.json +59 -0
- package/runner/extension/verify.sh +511 -0
- package/runner/mobile/verify.sh +501 -0
- package/runner/src/adapters.ts +601 -0
- package/runner/src/cli.ts +1820 -0
- package/runner/src/commands/debug.ts +44 -0
- package/runner/src/commands/fixtures.ts +99 -0
- package/runner/src/commands/launch.ts +397 -0
- package/runner/src/commands/logs.ts +60 -0
- package/runner/src/commands/shared.ts +138 -0
- package/runner/src/completions-cache.ts +86 -0
- package/runner/src/doctor.ts +203 -0
- package/runner/src/harness.ts +516 -0
- package/runner/src/heal-bounds.ts +179 -0
- package/runner/src/index.ts +6 -0
- package/runner/src/live-adapter-contract.ts +274 -0
- package/runner/src/manifest.ts +47 -0
- package/runner/src/mm-harness-cli.ts +488 -0
- package/runner/src/paths.ts +198 -0
- package/runner/src/recording-target.ts +147 -0
- package/runner/src/run-recording.ts +329 -0
- package/runner/src/runner.ts +108 -0
- package/runner/src/types.ts +57 -0
- package/scripts/completions.sh +125 -0
- package/scripts/install-completions.sh +62 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { runAdapter } from '../platform/cdp.mjs';
|
|
2
|
+
import { assertPositions, placeOrder } from './perps.mjs';
|
|
3
|
+
runAdapter(async (input) => {
|
|
4
|
+
const order = await placeOrder(input);
|
|
5
|
+
const assertion = await assertPositions(input, true);
|
|
6
|
+
return { ...assertion, submitted: order.submitted === true, order };
|
|
7
|
+
});
|
|
@@ -0,0 +1,541 @@
|
|
|
1
|
+
import { constants, access, cp, mkdir, readFile, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { closeSync, openSync } from 'node:fs';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
importRecipeHarnessRuntimeBrowserExtension,
|
|
9
|
+
importRecipeHarnessRuntimeCdp,
|
|
10
|
+
extensionIdPath,
|
|
11
|
+
recipeHarnessPath,
|
|
12
|
+
walletFixturePath,
|
|
13
|
+
} from '../../../../runner/src/paths.ts';
|
|
14
|
+
import { captureActiveRecipeRecordingSnapshot } from '../../../../runner/src/run-recording.ts';
|
|
15
|
+
|
|
16
|
+
// Resolve the Farmslot harness through normal package dependencies by default.
|
|
17
|
+
// Local Farmslot source is only a dev override handled by src/paths.ts.
|
|
18
|
+
const {
|
|
19
|
+
CdpSession,
|
|
20
|
+
CdpWebPage,
|
|
21
|
+
dataTestId,
|
|
22
|
+
jsonGet,
|
|
23
|
+
retryJsonGet,
|
|
24
|
+
sleep,
|
|
25
|
+
} = await importRecipeHarnessRuntimeCdp();
|
|
26
|
+
const { extensionIdFromTarget } = await importRecipeHarnessRuntimeBrowserExtension();
|
|
27
|
+
|
|
28
|
+
export { dataTestId };
|
|
29
|
+
|
|
30
|
+
async function canAccess(file) {
|
|
31
|
+
try {
|
|
32
|
+
await access(file, constants.F_OK);
|
|
33
|
+
return true;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
if (error?.code === 'ENOENT') return false;
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function cdpPort(input) {
|
|
41
|
+
const raw = input.node?.cdp_port ?? process.env.CDP_PORT ?? process.env.RECIPE_CDP_PORT;
|
|
42
|
+
const port = Number(raw);
|
|
43
|
+
if (!Number.isInteger(port) || port <= 0) {
|
|
44
|
+
throw new Error('Extension live adapter requires node.cdp_port, CDP_PORT, or RECIPE_CDP_PORT.');
|
|
45
|
+
}
|
|
46
|
+
return port;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function extensionTarget(targets, extensionId = null) {
|
|
50
|
+
return (Array.isArray(targets) ? targets : []).find((target) => {
|
|
51
|
+
if (target?.type !== 'page') return false;
|
|
52
|
+
if (!String(target?.url ?? '').startsWith('chrome-extension://')) return false;
|
|
53
|
+
if (!String(target.url).includes('/home.html')) return false;
|
|
54
|
+
if (extensionId && extensionIdFromTarget(target) !== extensionId) return false;
|
|
55
|
+
return Boolean(target.webSocketDebuggerUrl);
|
|
56
|
+
}) ?? null;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
function resolveRelativeArtifactPath(artifactsDir, relPath) {
|
|
61
|
+
const relative = relPath || 'screenshots/extension-page.png';
|
|
62
|
+
if (path.isAbsolute(relative) || relative.split(/[\/]+/).includes('..')) {
|
|
63
|
+
throw new Error(`Refusing Extension screenshot artifact path outside artifacts dir: ${relative}`);
|
|
64
|
+
}
|
|
65
|
+
const normalized = path.normalize(relative);
|
|
66
|
+
const absolute = path.resolve(artifactsDir, normalized);
|
|
67
|
+
const artifactsRoot = path.resolve(artifactsDir);
|
|
68
|
+
if (absolute !== artifactsRoot && !absolute.startsWith(`${artifactsRoot}${path.sep}`)) {
|
|
69
|
+
throw new Error(`Refusing Extension screenshot artifact path outside artifacts dir: ${relative}`);
|
|
70
|
+
}
|
|
71
|
+
return { relative: normalized, absolute };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function captureHelperPath() {
|
|
75
|
+
return process.env.CAPTURE_HELPER_PATH || 'capture-helper';
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function parsePositivePid(value) {
|
|
79
|
+
const pid = Number(String(value ?? '').trim());
|
|
80
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function readPidFile(file) {
|
|
84
|
+
if (!(await canAccess(file))) return null;
|
|
85
|
+
return parsePositivePid(await readFile(file, 'utf8'));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function captureHelperBrowserPid(context, port) {
|
|
89
|
+
const explicit = parsePositivePid(process.env.METAMASK_RECIPE_EXTENSION_BROWSER_PID);
|
|
90
|
+
if (explicit) return explicit;
|
|
91
|
+
|
|
92
|
+
const lsof = await runProcess('lsof', ['-nP', `-iTCP:${port}`, '-sTCP:LISTEN', '-t'], {
|
|
93
|
+
cwd: context.projectRoot,
|
|
94
|
+
env: process.env,
|
|
95
|
+
timeoutMs: 5000,
|
|
96
|
+
});
|
|
97
|
+
if (lsof.exitCode === 0) {
|
|
98
|
+
const pid = parsePositivePid(lsof.stdout.split(/\s+/u).find(Boolean));
|
|
99
|
+
if (pid) return pid;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const runtimeJson = path.join(context.artifactsDir, 'extension-runtime/runtime.json');
|
|
103
|
+
if (await canAccess(runtimeJson)) {
|
|
104
|
+
const runtime = JSON.parse(await readFile(runtimeJson, 'utf8'));
|
|
105
|
+
const pid = parsePositivePid(runtime?.pid);
|
|
106
|
+
if (pid) return pid;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const recipeRuntime = path.join(context.projectRoot, 'temp/recipe/runtime');
|
|
110
|
+
for (const name of ['chromium.pid', 'browser.pid']) {
|
|
111
|
+
const pid = await readPidFile(path.join(recipeRuntime, name));
|
|
112
|
+
if (pid) return pid;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
throw new Error(
|
|
116
|
+
`Extension ui.screenshot requires a live browser PID for capture-helper snapshot, but none could be resolved from CDP port ${port}, extension-runtime/runtime.json, or temp/recipe/runtime/*.pid.`,
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function captureHelperSnapshot(page, context, relPath, metadata) {
|
|
121
|
+
if (process.platform !== 'darwin') {
|
|
122
|
+
throw new Error('Extension ui.screenshot uses capture-helper snapshot and is currently supported only on macOS.');
|
|
123
|
+
}
|
|
124
|
+
const { relative, absolute } = resolveRelativeArtifactPath(context.artifactsDir, relPath);
|
|
125
|
+
await mkdir(path.dirname(absolute), { recursive: true });
|
|
126
|
+
|
|
127
|
+
await page.session.call('Page.bringToFront');
|
|
128
|
+
const pid = await captureHelperBrowserPid(context, page.port);
|
|
129
|
+
const timeoutMs = Number(metadata?.timeoutMs ?? 30000);
|
|
130
|
+
const sessionSnapshot = await captureActiveRecipeRecordingSnapshot(pid, absolute, timeoutMs);
|
|
131
|
+
if (sessionSnapshot) {
|
|
132
|
+
return {
|
|
133
|
+
path: relative,
|
|
134
|
+
type: 'screenshot',
|
|
135
|
+
nodeId: context.nodeId,
|
|
136
|
+
label: metadata?.label ?? `${context.nodeId} screenshot`,
|
|
137
|
+
category: metadata?.category ?? 'evidence',
|
|
138
|
+
mimeType: 'image/png',
|
|
139
|
+
metadata: {
|
|
140
|
+
provider: 'capture-helper',
|
|
141
|
+
mode: 'record_session_snapshot',
|
|
142
|
+
pid,
|
|
143
|
+
captureHelper: sessionSnapshot,
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const result = await runProcess(captureHelperPath(), ['snapshot', '--pid', String(pid), '--output', absolute], {
|
|
149
|
+
cwd: context.projectRoot,
|
|
150
|
+
env: process.env,
|
|
151
|
+
timeoutMs,
|
|
152
|
+
});
|
|
153
|
+
if (result.exitCode !== 0) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
`capture-helper snapshot failed for Extension ui.screenshot (pid ${pid}): ${result.stderr || result.stdout}`,
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
const details = parseJsonObject(result.stdout);
|
|
159
|
+
return {
|
|
160
|
+
path: relative,
|
|
161
|
+
type: 'screenshot',
|
|
162
|
+
nodeId: context.nodeId,
|
|
163
|
+
label: metadata?.label ?? `${context.nodeId} screenshot`,
|
|
164
|
+
category: metadata?.category ?? 'evidence',
|
|
165
|
+
mimeType: 'image/png',
|
|
166
|
+
metadata: {
|
|
167
|
+
provider: 'capture-helper',
|
|
168
|
+
mode: 'snapshot',
|
|
169
|
+
pid,
|
|
170
|
+
...(details ? { captureHelper: details } : {}),
|
|
171
|
+
},
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function autolaunchEnabled(input) {
|
|
176
|
+
return input.node?.launch_existing_dist === true ||
|
|
177
|
+
input.node?.autolaunch === true ||
|
|
178
|
+
process.env.METAMASK_RECIPE_EXTENSION_AUTOLAUNCH === '1' ||
|
|
179
|
+
process.env.METAMASK_RECIPE_EXTENSION_LAUNCH_EXISTING_DIST === '1';
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function projectRequire(projectRoot) {
|
|
183
|
+
return createRequire(path.join(projectRoot, 'package.json'));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function resolveChromeBinary(projectRoot) {
|
|
187
|
+
if (process.env.RECIPE_HARNESS_CHROME_BIN) return process.env.RECIPE_HARNESS_CHROME_BIN;
|
|
188
|
+
const requireFromProject = projectRequire(projectRoot);
|
|
189
|
+
for (const packageName of ['@playwright/test', 'playwright']) {
|
|
190
|
+
try {
|
|
191
|
+
const { chromium } = requireFromProject(packageName);
|
|
192
|
+
const executable = chromium.executablePath();
|
|
193
|
+
if (executable) return executable;
|
|
194
|
+
} catch (error) {
|
|
195
|
+
if (error?.code === 'MODULE_NOT_FOUND') continue;
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
throw new Error('Extension autolaunch requires RECIPE_HARNESS_CHROME_BIN or Playwright installed in the target checkout.');
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function execNodeScript(script, args, options) {
|
|
203
|
+
const result = await runProcess(process.execPath, [script, ...args], options);
|
|
204
|
+
if (result.exitCode !== 0) {
|
|
205
|
+
throw new Error(`Command failed: node ${script} ${args.join(' ')}\n${result.stderr || result.stdout}`);
|
|
206
|
+
}
|
|
207
|
+
return result;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function runProcess(command, args, options) {
|
|
211
|
+
return new Promise((resolve, reject) => {
|
|
212
|
+
const child = spawn(command, args, {
|
|
213
|
+
cwd: options.cwd,
|
|
214
|
+
env: options.env || process.env,
|
|
215
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
216
|
+
});
|
|
217
|
+
let timer = null;
|
|
218
|
+
let stdout = '';
|
|
219
|
+
let stderr = '';
|
|
220
|
+
if (options.timeoutMs) {
|
|
221
|
+
timer = setTimeout(() => {
|
|
222
|
+
child.kill('SIGTERM');
|
|
223
|
+
reject(new Error(`Command timed out after ${options.timeoutMs}ms: ${command} ${args.join(' ')}`));
|
|
224
|
+
}, options.timeoutMs);
|
|
225
|
+
}
|
|
226
|
+
child.stdout.on('data', (chunk) => { stdout += chunk; });
|
|
227
|
+
child.stderr.on('data', (chunk) => { stderr += chunk; });
|
|
228
|
+
child.on('error', (error) => {
|
|
229
|
+
if (timer) clearTimeout(timer);
|
|
230
|
+
reject(error);
|
|
231
|
+
});
|
|
232
|
+
child.on('close', (exitCode) => {
|
|
233
|
+
if (timer) clearTimeout(timer);
|
|
234
|
+
resolve({ exitCode, stdout, stderr });
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function parseJsonObject(value) {
|
|
240
|
+
const trimmed = String(value ?? '').trim();
|
|
241
|
+
if (!trimmed) return null;
|
|
242
|
+
try {
|
|
243
|
+
const parsed = JSON.parse(trimmed);
|
|
244
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
|
|
245
|
+
} catch (error) {
|
|
246
|
+
throw new Error(`capture-helper snapshot returned non-JSON output: ${trimmed}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
async function findWalletFixture(projectRoot) {
|
|
251
|
+
const candidates = [
|
|
252
|
+
walletFixturePath(projectRoot),
|
|
253
|
+
];
|
|
254
|
+
for (const candidate of candidates) {
|
|
255
|
+
if (await canAccess(candidate)) return candidate;
|
|
256
|
+
}
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async function launchExistingDistRuntime(input, port) {
|
|
261
|
+
const projectRoot = input.context.projectRoot;
|
|
262
|
+
const artifactsDir = input.context.artifactsDir;
|
|
263
|
+
const distDir = path.resolve(projectRoot, input.node?.dist_dir || process.env.METAMASK_RECIPE_EXTENSION_DIST_DIR || 'dist/chrome');
|
|
264
|
+
const distManifest = path.join(distDir, 'manifest.json');
|
|
265
|
+
if (!(await canAccess(distManifest))) {
|
|
266
|
+
throw new Error(`Extension autolaunch requires an existing built dist with manifest.json: ${distManifest}`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const runtimeRoot = path.join(artifactsDir, 'extension-runtime');
|
|
270
|
+
const runtimeDist = path.join(runtimeRoot, 'runtime-dist');
|
|
271
|
+
const profileDir = path.resolve(process.env.METAMASK_RECIPE_EXTENSION_PROFILE_DIR || path.join(runtimeRoot, 'chrome-profile'));
|
|
272
|
+
const logsDir = path.join(runtimeRoot, 'logs');
|
|
273
|
+
await mkdir(logsDir, { recursive: true });
|
|
274
|
+
await rm(runtimeDist, { recursive: true, force: true });
|
|
275
|
+
await mkdir(runtimeDist, { recursive: true });
|
|
276
|
+
await cp(distDir, runtimeDist, { recursive: true, force: true, filter: (source) => !source.endsWith(`${path.sep}_metadata`) });
|
|
277
|
+
await mkdir(profileDir, { recursive: true });
|
|
278
|
+
|
|
279
|
+
const fixtureScript = recipeHarnessPath(projectRoot, 'extension/scripts/wallet-fixture-state.cjs');
|
|
280
|
+
const fixture = await findWalletFixture(projectRoot);
|
|
281
|
+
const extensionIdFile = extensionIdPath(projectRoot);
|
|
282
|
+
const fixtureState = path.join(runtimeRoot, 'fixture-state.json');
|
|
283
|
+
let fixtureSeeded = false;
|
|
284
|
+
if (fixture && await canAccess(fixtureScript)) {
|
|
285
|
+
await execNodeScript(fixtureScript, ['generate', '--target', projectRoot, '--fixture', fixture, '--out', fixtureState], { cwd: projectRoot });
|
|
286
|
+
await execNodeScript(fixtureScript, ['prefill-profile', '--target', projectRoot, '--state', fixtureState, '--profile', profileDir, '--extension-dir', runtimeDist, '--extension-id-file', extensionIdFile], { cwd: projectRoot });
|
|
287
|
+
fixtureSeeded = true;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const chrome = resolveChromeBinary(projectRoot);
|
|
291
|
+
const stdoutFd = openSync(path.join(logsDir, 'chrome.log'), 'a');
|
|
292
|
+
const stderrFd = openSync(path.join(logsDir, 'chrome.log'), 'a');
|
|
293
|
+
let child;
|
|
294
|
+
try {
|
|
295
|
+
child = spawn(chrome, [
|
|
296
|
+
`--user-data-dir=${profileDir}`,
|
|
297
|
+
'--remote-debugging-address=127.0.0.1',
|
|
298
|
+
`--remote-debugging-port=${port}`,
|
|
299
|
+
'--no-first-run',
|
|
300
|
+
'--disable-first-run-ui',
|
|
301
|
+
'--disable-default-apps',
|
|
302
|
+
'--disable-popup-blocking',
|
|
303
|
+
'--disable-extensions-file-access-check',
|
|
304
|
+
'--disable-extensions-content-verification',
|
|
305
|
+
'--disable-features=ExtensionContentVerification,DisableLoadExtensionCommandLineSwitch',
|
|
306
|
+
`--disable-extensions-except=${runtimeDist}`,
|
|
307
|
+
`--load-extension=${runtimeDist}`,
|
|
308
|
+
'chrome://extensions/',
|
|
309
|
+
], {
|
|
310
|
+
cwd: projectRoot,
|
|
311
|
+
detached: true,
|
|
312
|
+
env: {
|
|
313
|
+
...process.env,
|
|
314
|
+
BUNDLED_DEBUGPY_PATH: undefined,
|
|
315
|
+
PYTHONHOME: undefined,
|
|
316
|
+
PYTHONPATH: undefined,
|
|
317
|
+
DYLD_LIBRARY_PATH: undefined,
|
|
318
|
+
DYLD_FALLBACK_LIBRARY_PATH: undefined,
|
|
319
|
+
DYLD_INSERT_LIBRARIES: undefined,
|
|
320
|
+
},
|
|
321
|
+
stdio: ['ignore', stdoutFd, stderrFd],
|
|
322
|
+
});
|
|
323
|
+
} finally {
|
|
324
|
+
closeSync(stdoutFd);
|
|
325
|
+
closeSync(stderrFd);
|
|
326
|
+
}
|
|
327
|
+
child.unref();
|
|
328
|
+
await writeFile(path.join(logsDir, 'chrome.pid'), `${child.pid}\n`);
|
|
329
|
+
await retryJsonGet(`http://127.0.0.1:${port}/json/version`, 45000);
|
|
330
|
+
|
|
331
|
+
let fixtureValidation = null;
|
|
332
|
+
if (fixtureSeeded) {
|
|
333
|
+
fixtureValidation = path.join(logsDir, 'fixture-account-parity.json');
|
|
334
|
+
await execNodeScript(
|
|
335
|
+
fixtureScript,
|
|
336
|
+
[
|
|
337
|
+
'seed-cdp',
|
|
338
|
+
'--target',
|
|
339
|
+
projectRoot,
|
|
340
|
+
'--fixture',
|
|
341
|
+
fixture,
|
|
342
|
+
'--state',
|
|
343
|
+
fixtureState,
|
|
344
|
+
'--cdp-port',
|
|
345
|
+
String(port),
|
|
346
|
+
'--extension-dir',
|
|
347
|
+
runtimeDist,
|
|
348
|
+
'--extension-id-file',
|
|
349
|
+
extensionIdFile,
|
|
350
|
+
'--out',
|
|
351
|
+
fixtureValidation,
|
|
352
|
+
],
|
|
353
|
+
{ cwd: projectRoot },
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
await writeFile(path.join(runtimeRoot, 'runtime.json'), `${JSON.stringify({
|
|
358
|
+
port,
|
|
359
|
+
pid: child.pid,
|
|
360
|
+
projectRoot,
|
|
361
|
+
distDir,
|
|
362
|
+
runtimeDist,
|
|
363
|
+
profileDir,
|
|
364
|
+
fixtureSeeded,
|
|
365
|
+
fixtureValidation,
|
|
366
|
+
launchedAt: new Date().toISOString(),
|
|
367
|
+
}, null, 2)}\n`);
|
|
368
|
+
return { launched: true, pid: child.pid, runtimeRoot, fixtureSeeded };
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
async function extensionIdFromFile(projectRoot) {
|
|
372
|
+
const extensionIdFile = extensionIdPath(projectRoot);
|
|
373
|
+
if (!(await canAccess(extensionIdFile))) return null;
|
|
374
|
+
const id = (await readFile(extensionIdFile, 'utf8')).trim();
|
|
375
|
+
return /^[a-z]{32}$/u.test(id) ? id : null;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
function extensionIdFromAnyTarget(targets) {
|
|
379
|
+
for (const target of Array.isArray(targets) ? targets : []) {
|
|
380
|
+
try {
|
|
381
|
+
return extensionIdFromTarget(target);
|
|
382
|
+
} catch (error) {
|
|
383
|
+
if (String(error?.message ?? '').startsWith('Could not derive extension ID')) continue;
|
|
384
|
+
throw error;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
async function openExtensionHomePage(port, extensionId) {
|
|
391
|
+
const url = `chrome-extension://${extensionId}/home.html`;
|
|
392
|
+
const endpoint = `http://127.0.0.1:${port}/json/new?${encodeURIComponent(url)}`;
|
|
393
|
+
let response = await fetch(endpoint, { method: 'PUT' });
|
|
394
|
+
if (response.status === 405 || response.status === 404) {
|
|
395
|
+
response = await fetch(endpoint);
|
|
396
|
+
}
|
|
397
|
+
if (!response.ok) {
|
|
398
|
+
throw new Error(`Failed to open extension home page ${url}: HTTP ${response.status}`);
|
|
399
|
+
}
|
|
400
|
+
return response.json();
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async function ensureExtensionTarget(input, port) {
|
|
404
|
+
let launch = null;
|
|
405
|
+
try {
|
|
406
|
+
const targets = await jsonGet(`http://127.0.0.1:${port}/json/list`);
|
|
407
|
+
const expectedExtensionId = await extensionIdFromFile(input.context.projectRoot);
|
|
408
|
+
const target =
|
|
409
|
+
(expectedExtensionId ? extensionTarget(Array.isArray(targets) ? targets : [], expectedExtensionId) : null) ??
|
|
410
|
+
extensionTarget(Array.isArray(targets) ? targets : []);
|
|
411
|
+
if (target?.webSocketDebuggerUrl) return { target, launch };
|
|
412
|
+
} catch (error) {
|
|
413
|
+
if (!autolaunchEnabled(input)) throw error;
|
|
414
|
+
}
|
|
415
|
+
if (!autolaunchEnabled(input)) {
|
|
416
|
+
throw new Error(`No extension page target found on CDP port ${port}. Launch the extension runtime first, or set METAMASK_RECIPE_EXTENSION_AUTOLAUNCH=1 to launch an existing dist/chrome.`);
|
|
417
|
+
}
|
|
418
|
+
launch = await launchExistingDistRuntime(input, port);
|
|
419
|
+
let targets = await retryJsonGet(`http://127.0.0.1:${port}/json/list`, 45000);
|
|
420
|
+
const expectedExtensionId = await extensionIdFromFile(input.context.projectRoot);
|
|
421
|
+
let target = expectedExtensionId ? extensionTarget(Array.isArray(targets) ? targets : [], expectedExtensionId) : null;
|
|
422
|
+
if (!target?.webSocketDebuggerUrl) {
|
|
423
|
+
const extensionId =
|
|
424
|
+
expectedExtensionId ??
|
|
425
|
+
extensionIdFromAnyTarget(Array.isArray(targets) ? targets : []);
|
|
426
|
+
if (!extensionId) {
|
|
427
|
+
throw new Error(`Autolaunch started Chrome on CDP port ${port}, but no extension ID could be derived from CDP targets or the recipe runtime extension.id.`);
|
|
428
|
+
}
|
|
429
|
+
await openExtensionHomePage(port, extensionId);
|
|
430
|
+
targets = await retryJsonGet(`http://127.0.0.1:${port}/json/list`, 15000);
|
|
431
|
+
target = extensionTarget(Array.isArray(targets) ? targets : [], extensionId);
|
|
432
|
+
}
|
|
433
|
+
if (!target?.webSocketDebuggerUrl) {
|
|
434
|
+
throw new Error(`Autolaunch started Chrome on CDP port ${port}, but no extension page target was found.`);
|
|
435
|
+
}
|
|
436
|
+
return { target, launch };
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
export async function loadInput() {
|
|
440
|
+
const inputPath = process.argv[2] || process.env.METAMASK_RECIPE_ADAPTER_INPUT;
|
|
441
|
+
if (!inputPath) throw new Error('Missing live adapter input path.');
|
|
442
|
+
return JSON.parse(await readFile(inputPath, 'utf8'));
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
export async function writeOutput(input, output) {
|
|
446
|
+
await writeFile(input.outputPath, `${JSON.stringify(output, null, 2)}\n`);
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export async function withExtensionPage(input, callback) {
|
|
450
|
+
const port = cdpPort(input);
|
|
451
|
+
const { target, launch } = await ensureExtensionTarget(input, port);
|
|
452
|
+
const session = await CdpSession.connect(target.webSocketDebuggerUrl);
|
|
453
|
+
try {
|
|
454
|
+
const extensionId = extensionIdFromTarget(target);
|
|
455
|
+
const origin = `chrome-extension://${extensionId}`;
|
|
456
|
+
const page = new ExtensionPage(session, origin, target, port);
|
|
457
|
+
await session.call('Runtime.enable');
|
|
458
|
+
await session.call('Page.enable');
|
|
459
|
+
const result = await callback(page);
|
|
460
|
+
return { ...result, cdpPort: port, targetUrl: target.url, runtimeLaunch: launch };
|
|
461
|
+
} finally {
|
|
462
|
+
session.close();
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
export class ExtensionPage extends CdpWebPage {
|
|
467
|
+
constructor(session, origin, target, port) {
|
|
468
|
+
super(session);
|
|
469
|
+
this.origin = origin;
|
|
470
|
+
this.target = target;
|
|
471
|
+
this.port = port;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
async navigateHash(hash) {
|
|
475
|
+
const normalizedHash = String(hash || '').startsWith('#') ? hash : `#${hash || '/'}`;
|
|
476
|
+
const href = `${this.origin}/home.html${normalizedHash}`;
|
|
477
|
+
return this.navigate(href);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
async readPositions() {
|
|
481
|
+
return this.evaluate(`(async () => {
|
|
482
|
+
const request = globalThis.stateHooks?.submitRequestToBackground;
|
|
483
|
+
const manager = globalThis.stateHooks?.getPerpsStreamManager?.();
|
|
484
|
+
const cached = manager?.positions?.cache;
|
|
485
|
+
if (typeof request === 'function') {
|
|
486
|
+
const result = await Promise.race([
|
|
487
|
+
request('perpsGetPositions', []).then((positions) => ({ ok: true, positions })),
|
|
488
|
+
new Promise((resolve) => setTimeout(() => resolve({ ok: false, timeout: true }), 5000)),
|
|
489
|
+
]);
|
|
490
|
+
if (result.ok && Array.isArray(result.positions)) {
|
|
491
|
+
return { available: true, source: 'background-perpsGetPositions', positions: result.positions };
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
return {
|
|
495
|
+
available: Array.isArray(cached),
|
|
496
|
+
source: 'perps-stream-manager-cache',
|
|
497
|
+
initialized: Boolean(manager?.isInitialized?.()),
|
|
498
|
+
connected: Boolean(manager?.positions?.isConnected),
|
|
499
|
+
positions: Array.isArray(cached) ? cached : [],
|
|
500
|
+
};
|
|
501
|
+
})()`);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
async screenshot(contextOrInput, relPath, metadata = {}) {
|
|
505
|
+
if (contextOrInput?.context) {
|
|
506
|
+
const options = {
|
|
507
|
+
label: contextOrInput.node?.description || `${contextOrInput.action} screenshot`,
|
|
508
|
+
category: 'evidence',
|
|
509
|
+
timeoutMs: contextOrInput.node?.timeout_ms,
|
|
510
|
+
...metadata,
|
|
511
|
+
};
|
|
512
|
+
if (contextOrInput.node?.screenshot_mode === 'dom_raster' || process.env.METAMASK_RECIPE_EXTENSION_SCREENSHOT_MODE === 'dom-raster') {
|
|
513
|
+
throw new Error('Extension ui.screenshot no longer supports DOM raster screenshots. Use capture-helper snapshot evidence.');
|
|
514
|
+
}
|
|
515
|
+
return captureHelperSnapshot(this, contextOrInput.context, relPath, options);
|
|
516
|
+
}
|
|
517
|
+
if (process.env.METAMASK_RECIPE_EXTENSION_SCREENSHOT_MODE === 'dom-raster') {
|
|
518
|
+
throw new Error('Extension screenshots no longer support DOM raster mode. Use capture-helper snapshot evidence.');
|
|
519
|
+
}
|
|
520
|
+
return captureHelperSnapshot(this, contextOrInput, relPath, metadata);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
export function marketSymbol(input) {
|
|
525
|
+
return normalizeMarketSymbol(input.node?.market ?? input.node?.symbol ?? 'BTC');
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
export function normalizeMarketSymbol(rawSymbol) {
|
|
529
|
+
const raw = String(rawSymbol);
|
|
530
|
+
if (raw.includes(':')) {
|
|
531
|
+
const [source, ...symbolParts] = raw.split(':');
|
|
532
|
+
return `${source.toLowerCase()}:${symbolParts.join(':').toUpperCase()}`;
|
|
533
|
+
}
|
|
534
|
+
return raw.toUpperCase();
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
export async function runAdapter(callback) {
|
|
538
|
+
const input = await loadInput();
|
|
539
|
+
const output = await callback(input);
|
|
540
|
+
await writeOutput(input, output);
|
|
541
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
|
+
|
|
3
|
+
const PAGE_HASHES = {
|
|
4
|
+
home: '#/',
|
|
5
|
+
perps: '#/?tab=perps',
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
function text(value) {
|
|
9
|
+
return typeof value === 'string' && value.length > 0 ? value : undefined;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function pageHash(node) {
|
|
13
|
+
const page = text(node?.page);
|
|
14
|
+
if (!page) return undefined;
|
|
15
|
+
if (PAGE_HASHES[page]) return { page, hash: PAGE_HASHES[page] };
|
|
16
|
+
if (page === 'perps-market') {
|
|
17
|
+
const market = text(node.market) ?? text(node.symbol);
|
|
18
|
+
if (!market) throw new Error('extension ui.navigate page=perps-market requires market or symbol.');
|
|
19
|
+
return { page, hash: `#/perps/market/${encodeURIComponent(market)}` };
|
|
20
|
+
}
|
|
21
|
+
throw new Error('extension ui.navigate supported page aliases: home, perps, perps-market.');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
runAdapter((input) => withExtensionPage(input, async (page) => {
|
|
25
|
+
const alias = pageHash(input.node);
|
|
26
|
+
if (alias) {
|
|
27
|
+
const navigation = await page.navigateHash(alias.hash);
|
|
28
|
+
return { action: input.action, ...alias, navigation, proofPath: 'ui-navigation' };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const url = input.node?.url;
|
|
32
|
+
if (typeof url === 'string' && url.length > 0) {
|
|
33
|
+
const navigation = await page.navigate(url);
|
|
34
|
+
return { action: input.action, url, navigation, proofPath: 'ui-navigation' };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const hash = input.node?.hash ?? input.node?.path;
|
|
38
|
+
if (typeof hash === 'string' && hash.length > 0) {
|
|
39
|
+
const navigation = await page.navigateHash(hash);
|
|
40
|
+
return { action: input.action, hash, navigation, proofPath: 'ui-navigation' };
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
throw new Error('extension ui.navigate requires page, raw extension url, hash, or path.');
|
|
44
|
+
}));
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
3
|
+
import { walletFixturePath } from '../../../../runner/src/paths.ts';
|
|
4
|
+
|
|
5
|
+
async function fixturePassword(projectRoot) {
|
|
6
|
+
const candidates = [
|
|
7
|
+
walletFixturePath(projectRoot),
|
|
8
|
+
];
|
|
9
|
+
for (const candidate of candidates) {
|
|
10
|
+
try {
|
|
11
|
+
const fixture = JSON.parse(await readFile(candidate, 'utf8'));
|
|
12
|
+
if (fixture.password) return fixture.password;
|
|
13
|
+
} catch (error) {
|
|
14
|
+
if (error?.code === 'ENOENT') continue;
|
|
15
|
+
throw error;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
throw new Error('No wallet fixture password found for Extension unlock.');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
runAdapter((input) => withExtensionPage(input, async (page) => {
|
|
22
|
+
const locked = await page.evaluate(`(() => Boolean(document.querySelector('input[type="password"]')))()`);
|
|
23
|
+
if (!locked) {
|
|
24
|
+
return { action: input.action, unlocked: true, alreadyUnlocked: true, proofPath: 'extension-unlocked-state' };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const password = input.node?.password ?? await fixturePassword(input.context.projectRoot);
|
|
28
|
+
await page.setInput('input[type="password"]', String(password));
|
|
29
|
+
await page.evaluate(`(() => {
|
|
30
|
+
const button = document.querySelector('button[type="submit"]') || Array.from(document.querySelectorAll('button')).find((candidate) => /unlock/i.test(candidate.innerText || candidate.textContent || ''));
|
|
31
|
+
if (!button) throw new Error('Unlock button not found.');
|
|
32
|
+
button.click();
|
|
33
|
+
})()`);
|
|
34
|
+
await page.waitForExpression(`!document.querySelector('input[type="password"]')`, { timeoutMs: Number(input.node?.timeout_ms ?? 15000) });
|
|
35
|
+
return { action: input.action, unlocked: true, redacted: true, proofPath: 'extension-password-unlock' };
|
|
36
|
+
}));
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
|
+
|
|
3
|
+
runAdapter((input) => withExtensionPage(input, async (page) => {
|
|
4
|
+
const state = await page.evaluate(`(() => {
|
|
5
|
+
const hooks = globalThis.stateHooks;
|
|
6
|
+
const store = hooks && hooks.store;
|
|
7
|
+
const root = store && typeof store.getState === 'function' ? store.getState() : {};
|
|
8
|
+
const metamask = root.metamask || {};
|
|
9
|
+
const internal = metamask.internalAccounts || {};
|
|
10
|
+
const accounts = internal.accounts || {};
|
|
11
|
+
const selectedId = internal.selectedAccount || null;
|
|
12
|
+
const selected = selectedId && accounts[selectedId] ? accounts[selectedId] : null;
|
|
13
|
+
const metadata = selected && selected.metadata ? selected.metadata : {};
|
|
14
|
+
return {
|
|
15
|
+
href: String(globalThis.location && globalThis.location.href || ''),
|
|
16
|
+
selectedAccount: selected ? {
|
|
17
|
+
id: selectedId,
|
|
18
|
+
address: selected.address || null,
|
|
19
|
+
name: metadata.name || null,
|
|
20
|
+
type: selected.type || null
|
|
21
|
+
} : null,
|
|
22
|
+
completedOnboarding: Boolean(metamask.completedOnboarding),
|
|
23
|
+
selectedNetworkClientId: metamask.selectedNetworkClientId || null
|
|
24
|
+
};
|
|
25
|
+
})()`);
|
|
26
|
+
return { action: input.action, source: 'extension-stateHooks-store', state, redacted: true, proofPath: 'extension-wallet-state' };
|
|
27
|
+
}));
|