@deeeed/metamask-harness 0.17.5 → 0.19.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 +1059 -0
- package/README.md +91 -140
- package/adapters/core/inject.sh +1 -6
- package/adapters/extension/console-tail.mjs +55 -24
- package/adapters/extension/ensure-browser.sh +6 -1
- package/adapters/extension/inject.mjs +7 -13
- package/adapters/extension/launch-browser.cjs +7 -1
- package/adapters/extension/launch-webpack.cjs +83 -0
- package/adapters/extension/launch.sh +8 -11
- package/adapters/extension/lib/chrome-args.cjs +8 -1
- package/adapters/extension/lib/macos-focus.cjs +32 -0
- package/adapters/extension/live.sh +12 -21
- package/adapters/extension/reattach.sh +1 -0
- package/adapters/extension/seed-fixture.sh +4 -12
- package/adapters/extension/sidepanel-toggle.sh +4 -1
- package/adapters/extension/stamp-runtime-title.cjs +58 -0
- package/adapters/extension/start-watch.sh +17 -4
- package/adapters/extension/stop-viewers.sh +1 -1
- package/adapters/extension/sync-webpack-dist.cjs +107 -0
- package/adapters/extension/verify.sh +2 -2
- package/adapters/manifest.json +53 -37
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +16 -0
- package/adapters/mobile/bridge-runtime/console-forwarder.cjs +1 -1
- package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +3 -1
- package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +2 -3
- package/adapters/mobile/cleanup.sh +16 -176
- package/adapters/mobile/inject.sh +91 -671
- package/adapters/mobile/launch-console-forwarder.cjs +39 -0
- package/adapters/mobile/open-device.sh +45 -7
- package/adapters/mobile/start-console-forwarder.sh +70 -0
- package/adapters/mobile/start-metro.sh +0 -41
- package/adapters/mobile/verify.sh +34 -83
- package/adapters/mobile/wait-for-bridge.sh +22 -8
- package/adapters/shared/harness-source-fingerprint.mjs +14 -12
- package/adapters/shared/resolve-slot-ports-core.mjs +17 -6
- package/adapters/shared/resolve-slot-ports.sh +17 -5
- package/adapters/shared/update-check-worker.mjs +43 -0
- package/bin/mm-harness +21 -2
- package/dist/adapters/core/surface.js +4 -1
- package/dist/adapters/extension/console-capture.js +95 -0
- package/dist/adapters/extension/product-config.js +110 -0
- package/dist/adapters/extension/runtime-decision.js +20 -2
- package/dist/adapters/extension/surface.js +22 -3
- package/dist/adapters/harness-freshness.js +49 -0
- package/dist/adapters/mobile/perps-env.js +43 -3
- package/dist/adapters/mobile/prepare.js +39 -7
- package/dist/adapters/mobile/surface.js +5 -2
- package/dist/adapters/resolve-slot-ports.js +2 -2
- package/dist/adapters/slot-ports.js +13 -16
- package/dist/adapters.js +56 -16
- package/dist/checkout-lock.js +27 -2
- package/dist/cli-color.js +19 -0
- package/dist/cli-commands.js +1 -1
- package/dist/cli.js +7 -18
- package/dist/command-contract.js +462 -0
- package/dist/command-journal.js +263 -0
- package/dist/commands/call.js +252 -57
- package/dist/commands/check.js +10 -4
- package/dist/commands/completion-candidates.js +17 -22
- package/dist/commands/device-target.js +27 -12
- package/dist/commands/doctor.js +106 -25
- package/dist/commands/fixtures.js +142 -35
- package/dist/commands/last.js +60 -0
- package/dist/commands/launch/extension.js +40 -17
- package/dist/commands/launch/index.js +207 -63
- package/dist/commands/launch/mobile.js +2 -0
- package/dist/commands/list-executables.js +187 -29
- package/dist/commands/logs.js +8 -6
- package/dist/commands/manifest.js +286 -35
- package/dist/commands/parse-args.js +68 -4
- package/dist/commands/provision.js +10 -4
- package/dist/commands/run-engine.js +772 -264
- package/dist/commands/run-report.js +12 -3
- package/dist/commands/run.js +378 -85
- package/dist/commands/shared.js +76 -4
- package/dist/commands/status-probe.js +4 -1
- package/dist/commands/status.js +2 -1
- package/dist/commands/stop.js +7 -2
- package/dist/commands/update.js +16 -28
- package/dist/completions-cache.js +1 -1
- package/dist/doctor.js +57 -10
- package/dist/harness.js +47 -137
- package/dist/heal-bounds.js +7 -2
- package/dist/json-stream.js +57 -0
- package/dist/live-adapter-contract.js +138 -32
- package/dist/manifest.js +161 -1
- package/dist/mm-harness-cli.js +146 -73
- package/dist/paths.js +2 -5
- package/dist/recipe-security.js +178 -0
- package/dist/run-diagnostics.js +261 -0
- package/dist/run-recording.js +1 -1
- package/dist/runner.js +140 -9
- package/docs/CONTRIBUTING.md +136 -0
- package/docs/QA.md +184 -0
- package/docs/RECIPES.md +122 -0
- package/docs/SECURITY.md +88 -0
- package/library/README.md +7 -3
- package/library/actions/core/perps/_controller.mjs +43 -56
- package/library/actions/core/perps/assert_orders.mjs +6 -7
- package/library/actions/core/perps/assert_positions.mjs +6 -7
- package/library/actions/core/perps/close_orders.mjs +2 -0
- package/library/actions/core/perps/close_positions.mjs +2 -0
- package/library/actions/core/perps/ensure_orders.mjs +4 -2
- package/library/actions/core/perps/ensure_positions.mjs +4 -2
- package/library/actions/core/perps/place_order.mjs +7 -3
- package/library/actions/core/perps/read_account.mjs +2 -2
- package/library/actions/core/perps/read_orders.mjs +2 -1
- package/library/actions/core/perps/read_positions.mjs +2 -1
- package/library/actions/core/wallet/list_accounts.mjs +95 -0
- package/library/actions/extension/perps/assert_orders.mjs +2 -1
- package/library/actions/extension/perps/assert_positions.mjs +2 -1
- package/library/actions/extension/perps/perps.mjs +43 -14
- package/library/actions/extension/platform/cdp.mjs +1 -0
- package/library/actions/extension/wallet/list_accounts.mjs +41 -0
- package/library/actions/mobile/perps/assert_orders.mjs +2 -1
- package/library/actions/mobile/perps/assert_positions.mjs +2 -1
- package/library/actions/mobile/perps/perps.mjs +40 -12
- package/library/actions/mobile/wallet/list_accounts.mjs +37 -0
- package/library/library.json +1 -1
- package/library/manifests/core.action-manifest.json +1214 -396
- package/library/manifests/extension.action-manifest.json +1635 -728
- package/library/manifests/mobile.action-manifest.json +1740 -727
- package/library/recipes/app/lifecycle.android-smoke.mobile.recipe.json +63 -81
- package/library/recipes/perps/clean-market-testnet.core.recipe.json +44 -0
- package/library/recipes/perps/clean-market-testnet.recipe.json +49 -0
- package/library/recipes/perps/lifecycle.recipe.json +136 -180
- package/library/recipes/perps/order-lifecycle.core.recipe.json +71 -67
- package/library/recipes/perps/performance.background-resume.mobile.recipe.json +51 -67
- package/library/recipes/perps/performance.cold-start.mobile.recipe.json +51 -67
- package/library/recipes/perps/performance.mobile.recipe.json +37 -51
- package/library/recipes/perps/performance.warm-start.mobile.recipe.json +44 -59
- package/library/recipes/perps/read-markets.core.recipe.json +29 -31
- package/library/recipes/perps/smoke.core.recipe.json +29 -32
- package/library/recipes/perps/smoke.extension.recipe.json +41 -44
- package/library/recipes/perps/smoke.mobile.recipe.json +42 -44
- package/library/recipes/perps/trading-lifecycle.core.recipe.json +69 -65
- package/library/recipes/runner/action-validation.extension.recipe.json +312 -398
- package/library/recipes/runner/action-validation.mobile.recipe.json +316 -402
- package/library/recipes/runner/smoke.core.recipe.json +25 -0
- package/library/recipes/runner/smoke.extension.recipe.json +23 -24
- package/library/recipes/runner/smoke.mobile.recipe.json +23 -24
- package/library/recipes/wallet/smoke.extension.recipe.json +40 -0
- package/library/recipes/wallet/smoke.mobile.recipe.json +40 -0
- package/package.json +12 -5
- package/scripts/completions.sh +8 -11
- package/scripts/validate-human-outcomes.mjs +169 -0
- package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +0 -185
- package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +0 -1662
- package/adapters/mobile/overlay/compat/README.md +0 -28
- package/adapters/mobile/overlay/compat/rn81-message-event-source.patch +0 -42
- package/adapters/shared/resolve-farmslot-ports-core.mjs +0 -3
- package/adapters/shared/resolve-farmslot-ports.mjs +0 -5
- package/adapters/shared/resolve-farmslot-ports.sh +0 -7
- package/dist/adapters/extension/harness-freshness.js +0 -39
- package/dist/commands/flows.js +0 -62
- package/docs/ADAPTER-SURFACE.md +0 -119
- package/docs/CHEATSHEET.md +0 -61
- package/docs/CLI-SPEC.md +0 -1098
- package/docs/CODE-MAP.md +0 -62
- package/docs/DEBUG-HANDOVER.md +0 -36
- package/docs/MENTAL-MODEL.md +0 -295
- package/docs/UX-PRINCIPLES.md +0 -64
- package/docs/architecture.md +0 -398
- package/docs/live-adapter-contract.md +0 -188
- package/docs/package-boundaries.md +0 -47
- package/docs/perps-flow-catalog.md +0 -235
- package/docs/recipe-libraries.md +0 -301
- package/docs/runtime-file-conventions.md +0 -36
- package/library/flows/perps.flows.json +0 -64
|
@@ -90,12 +90,7 @@ function buildInfrastructure(stubbed) {
|
|
|
90
90
|
// The fixture has the standard { accounts: [{ type, value, name }] } shape from
|
|
91
91
|
// wallet-fixture.json.sample. The recipe node selects an account by name via
|
|
92
92
|
// `account_name` (default "dev1"). The viem account derived from the fixture
|
|
93
|
-
// entry is the authoritative source for both the address
|
|
94
|
-
// no separate MM_TEST_ACCOUNT_ADDRESS env var needed.
|
|
95
|
-
//
|
|
96
|
-
// Env-var fallback: if no fixture is present (e.g. direct CLI invocation),
|
|
97
|
-
// MM_TEST_ACCOUNT_SRP / MM_TEST_ACCOUNT_PRIVATE_KEY + MM_TEST_ACCOUNT_ADDRESS
|
|
98
|
-
// are still accepted for backward compatibility.
|
|
93
|
+
// entry is the authoritative source for both the address and signing key.
|
|
99
94
|
|
|
100
95
|
/**
|
|
101
96
|
* Load wallet-fixture.json and return the named account entry.
|
|
@@ -155,26 +150,6 @@ function viemAccountFromFixtureEntry(entry) {
|
|
|
155
150
|
return privateKeyToAccount(normalized);
|
|
156
151
|
}
|
|
157
152
|
|
|
158
|
-
// Env-var fallback constants (used only when wallet-fixture.json is absent).
|
|
159
|
-
const SIGNER_PRIVATE_KEY_ENV = 'MM_TEST_ACCOUNT_PRIVATE_KEY';
|
|
160
|
-
const SIGNER_MNEMONIC_ENV = 'MM_TEST_ACCOUNT_SRP';
|
|
161
|
-
|
|
162
|
-
function signerFromEnv() {
|
|
163
|
-
const pk = process.env[SIGNER_PRIVATE_KEY_ENV]?.trim();
|
|
164
|
-
if (pk && pk.length > 0) {
|
|
165
|
-
const normalized = pk.startsWith('0x') ? pk : `0x${pk}`;
|
|
166
|
-
if (!/^0x[0-9a-fA-F]{64}$/u.test(normalized)) {
|
|
167
|
-
throw new Error(`${SIGNER_PRIVATE_KEY_ENV} is not a 32-byte hex private key.`);
|
|
168
|
-
}
|
|
169
|
-
return privateKeyToAccount(normalized);
|
|
170
|
-
}
|
|
171
|
-
const mnemonic = process.env[SIGNER_MNEMONIC_ENV]?.trim();
|
|
172
|
-
if (mnemonic && mnemonic.split(/\s+/u).length >= 12) {
|
|
173
|
-
return mnemonicToAccount(mnemonic, { addressIndex: 0 });
|
|
174
|
-
}
|
|
175
|
-
return null;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
153
|
/**
|
|
179
154
|
* Resolve the account name to use for signing.
|
|
180
155
|
* Precedence: node.account_name → node.account (if not an address) → "dev1".
|
|
@@ -198,9 +173,7 @@ function resolveAccountName(input) {
|
|
|
198
173
|
|
|
199
174
|
/**
|
|
200
175
|
* Resolve the viem signer and EVM address for writes.
|
|
201
|
-
*
|
|
202
|
-
* Fallback: MM_TEST_ACCOUNT_PRIVATE_KEY / MM_TEST_ACCOUNT_SRP env vars
|
|
203
|
-
* (requires MM_TEST_ACCOUNT_ADDRESS for address verification).
|
|
176
|
+
* The wallet fixture account selected by name is the only signing source.
|
|
204
177
|
*
|
|
205
178
|
* @param input - Adapter input (context.projectRoot, node.account_name).
|
|
206
179
|
* @returns { account: ViemAccount, address: string }
|
|
@@ -209,7 +182,6 @@ async function resolveSignerFromFixture(input) {
|
|
|
209
182
|
const projectRoot = input.context?.projectRoot;
|
|
210
183
|
const accountName = resolveAccountName(input);
|
|
211
184
|
|
|
212
|
-
// Primary: fixture
|
|
213
185
|
if (projectRoot) {
|
|
214
186
|
const entry = await loadFixtureAccount(projectRoot, accountName);
|
|
215
187
|
if (entry) {
|
|
@@ -218,32 +190,15 @@ async function resolveSignerFromFixture(input) {
|
|
|
218
190
|
}
|
|
219
191
|
}
|
|
220
192
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
`core perps writes require a wallet-fixture.json with account "${accountName}", ` +
|
|
226
|
-
`or env vars ${SIGNER_PRIVATE_KEY_ENV} / ${SIGNER_MNEMONIC_ENV} + MM_TEST_ACCOUNT_ADDRESS.`,
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
const envAddress = String(process.env.MM_TEST_ACCOUNT_ADDRESS ?? '').trim();
|
|
230
|
-
if (!/^0x[0-9a-fA-F]{40}$/u.test(envAddress)) {
|
|
231
|
-
throw new Error(
|
|
232
|
-
`Env-var fallback requires MM_TEST_ACCOUNT_ADDRESS (a 0x EVM address) to verify the signer.`,
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
|
-
if (account.address.toLowerCase() !== envAddress.toLowerCase()) {
|
|
236
|
-
throw new Error(
|
|
237
|
-
`Env-var signer derives ${account.address} but MM_TEST_ACCOUNT_ADDRESS is ${envAddress}; signatures would be invalid.`,
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
return { account, address: envAddress };
|
|
193
|
+
throw new Error(
|
|
194
|
+
`core perps writes require wallet-fixture.json account "${accountName}". ` +
|
|
195
|
+
`Next: run mm-harness fixtures set`,
|
|
196
|
+
);
|
|
241
197
|
}
|
|
242
198
|
|
|
243
199
|
/**
|
|
244
200
|
* Resolve the account address for reads (no signing required).
|
|
245
|
-
*
|
|
246
|
-
* Fallback: node.account / node.address / MM_TEST_ACCOUNT_ADDRESS env var.
|
|
201
|
+
* Prefer the wallet fixture account; explicit node addresses support read-only calls.
|
|
247
202
|
*/
|
|
248
203
|
async function requireAccountAddress(input) {
|
|
249
204
|
const projectRoot = input.context?.projectRoot;
|
|
@@ -261,13 +216,13 @@ async function requireAccountAddress(input) {
|
|
|
261
216
|
}
|
|
262
217
|
}
|
|
263
218
|
|
|
264
|
-
//
|
|
219
|
+
// Read-only calls may name an address directly.
|
|
265
220
|
const fromNode = input.node?.account ?? input.node?.address ?? input.node?.userAddress;
|
|
266
|
-
const address = String(fromNode ??
|
|
221
|
+
const address = String(fromNode ?? '').trim();
|
|
267
222
|
if (!/^0x[0-9a-fA-F]{40}$/u.test(address)) {
|
|
268
223
|
throw new Error(
|
|
269
224
|
`core perps reads require a wallet-fixture.json with account "${accountName}", ` +
|
|
270
|
-
`or a 0x EVM address via node.account
|
|
225
|
+
`or a 0x EVM address via node.account.`,
|
|
271
226
|
);
|
|
272
227
|
}
|
|
273
228
|
return address;
|
|
@@ -291,6 +246,14 @@ export function resolveNetwork(input) {
|
|
|
291
246
|
return raw;
|
|
292
247
|
}
|
|
293
248
|
|
|
249
|
+
export function requireRuntimeExport(moduleNamespace, exportName, sourceLabel) {
|
|
250
|
+
const value = moduleNamespace?.[exportName] ?? moduleNamespace?.default?.[exportName];
|
|
251
|
+
if (value === undefined) {
|
|
252
|
+
throw new Error(`${sourceLabel} does not export ${exportName}.`);
|
|
253
|
+
}
|
|
254
|
+
return value;
|
|
255
|
+
}
|
|
256
|
+
|
|
294
257
|
/**
|
|
295
258
|
* Instantiate the PerpsController headlessly against the resolved core checkout.
|
|
296
259
|
* Returns the controller plus the resolved read account address. Cached per
|
|
@@ -306,10 +269,25 @@ export async function getCoreController(input) {
|
|
|
306
269
|
return { ...cached, accountAddress };
|
|
307
270
|
}
|
|
308
271
|
|
|
309
|
-
const [
|
|
272
|
+
const [controllerModule, messengerModule] = await Promise.all([
|
|
310
273
|
import(controllerEntry(projectRoot)),
|
|
311
274
|
import(messengerEntry(projectRoot)),
|
|
312
275
|
]);
|
|
276
|
+
const PerpsController = requireRuntimeExport(
|
|
277
|
+
controllerModule,
|
|
278
|
+
'PerpsController',
|
|
279
|
+
'Core perps-controller entrypoint',
|
|
280
|
+
);
|
|
281
|
+
const Messenger = requireRuntimeExport(
|
|
282
|
+
messengerModule,
|
|
283
|
+
'Messenger',
|
|
284
|
+
'Core messenger entrypoint',
|
|
285
|
+
);
|
|
286
|
+
const MOCK_ANY_NAMESPACE = requireRuntimeExport(
|
|
287
|
+
messengerModule,
|
|
288
|
+
'MOCK_ANY_NAMESPACE',
|
|
289
|
+
'Core messenger entrypoint',
|
|
290
|
+
);
|
|
313
291
|
|
|
314
292
|
const stubbed = new Set();
|
|
315
293
|
const infrastructure = buildInfrastructure(stubbed);
|
|
@@ -603,6 +581,15 @@ export function configuredSymbols(input, items) {
|
|
|
603
581
|
return single ? [normalizeMarketSymbol(single)] : [];
|
|
604
582
|
}
|
|
605
583
|
|
|
584
|
+
export function requireExplicitSelection(input) {
|
|
585
|
+
const selector =
|
|
586
|
+
input.node?.selector && typeof input.node.selector === 'object' ? input.node.selector : {};
|
|
587
|
+
const mode = String(input.node?.mode ?? selector.mode ?? '').toLowerCase();
|
|
588
|
+
if (configuredSymbols(input, []).length === 0 && mode !== 'all') {
|
|
589
|
+
throw new Error(`${input.action} requires market=<symbol> or mode=all.`);
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
606
593
|
/**
|
|
607
594
|
* Filter live items to the recipe-selected subset using the same market/side
|
|
608
595
|
* vocabulary as the extension perps adapter. With no selector and no
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
getCoreController,
|
|
3
3
|
isDirectRun,
|
|
4
4
|
redactOrder,
|
|
5
|
+
requireExplicitSelection,
|
|
5
6
|
runAdapter,
|
|
6
7
|
selectedItems,
|
|
7
8
|
} from './_controller.mjs';
|
|
@@ -12,13 +13,15 @@ import {
|
|
|
12
13
|
// assert_positions.mjs.
|
|
13
14
|
|
|
14
15
|
export function expectedOpen(input) {
|
|
15
|
-
|
|
16
|
+
if (input.node?.state == null) throw new Error('metamask.perps.assert_orders requires state=open or state=none.');
|
|
17
|
+
const state = String(input.node.state).toLowerCase();
|
|
16
18
|
if (state === 'open' || state === 'present') return true;
|
|
17
19
|
if (state === 'none' || state === 'closed' || state === 'absent') return false;
|
|
18
20
|
throw new Error(`metamask.perps.assert_orders received unsupported state: ${state}`);
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
export async function assertOrders(input, expectOpen = expectedOpen(input)) {
|
|
24
|
+
requireExplicitSelection(input);
|
|
22
25
|
const { controller, accountAddress, network } = await getCoreController(input);
|
|
23
26
|
const orders = await controller.getOpenOrders({
|
|
24
27
|
standalone: true,
|
|
@@ -28,14 +31,10 @@ export async function assertOrders(input, expectOpen = expectedOpen(input)) {
|
|
|
28
31
|
const hasOrder = matching.length > 0;
|
|
29
32
|
|
|
30
33
|
if (expectOpen && !hasOrder) {
|
|
31
|
-
throw new Error(
|
|
32
|
-
`Expected selected Perps open order(s), but none matched ${JSON.stringify(input.node)}.`,
|
|
33
|
-
);
|
|
34
|
+
throw new Error('Expected at least one matching open Perps order, but found none.');
|
|
34
35
|
}
|
|
35
36
|
if (!expectOpen && hasOrder) {
|
|
36
|
-
throw new Error(
|
|
37
|
-
`Expected no selected Perps open orders, but ${matching.length} matched ${JSON.stringify(input.node)}.`,
|
|
38
|
-
);
|
|
37
|
+
throw new Error(`Expected no matching open Perps orders, but found ${matching.length}.`);
|
|
39
38
|
}
|
|
40
39
|
|
|
41
40
|
return {
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
getCoreController,
|
|
3
3
|
isDirectRun,
|
|
4
4
|
redactPosition,
|
|
5
|
+
requireExplicitSelection,
|
|
5
6
|
runAdapter,
|
|
6
7
|
selectedItems,
|
|
7
8
|
} from './_controller.mjs';
|
|
@@ -11,13 +12,15 @@ import {
|
|
|
11
12
|
// throws on mismatch so the recipe fails loudly.
|
|
12
13
|
|
|
13
14
|
export function expectedOpen(input) {
|
|
14
|
-
|
|
15
|
+
if (input.node?.state == null) throw new Error('metamask.perps.assert_positions requires state=open or state=none.');
|
|
16
|
+
const state = String(input.node.state).toLowerCase();
|
|
15
17
|
if (state === 'open' || state === 'present') return true;
|
|
16
18
|
if (state === 'none' || state === 'closed' || state === 'absent') return false;
|
|
17
19
|
throw new Error(`metamask.perps.assert_positions received unsupported state: ${state}`);
|
|
18
20
|
}
|
|
19
21
|
|
|
20
22
|
export async function assertPositions(input, expectOpen = expectedOpen(input)) {
|
|
23
|
+
requireExplicitSelection(input);
|
|
21
24
|
const { controller, accountAddress, network } = await getCoreController(input);
|
|
22
25
|
const positions = await controller.getPositions({
|
|
23
26
|
standalone: true,
|
|
@@ -27,14 +30,10 @@ export async function assertPositions(input, expectOpen = expectedOpen(input)) {
|
|
|
27
30
|
const hasPosition = matching.length > 0;
|
|
28
31
|
|
|
29
32
|
if (expectOpen && !hasPosition) {
|
|
30
|
-
throw new Error(
|
|
31
|
-
`Expected selected Perps position(s), but none matched ${JSON.stringify(input.node)}.`,
|
|
32
|
-
);
|
|
33
|
+
throw new Error('Expected at least one matching open Perps position, but found none.');
|
|
33
34
|
}
|
|
34
35
|
if (!expectOpen && hasPosition) {
|
|
35
|
-
throw new Error(
|
|
36
|
-
`Expected no selected Perps positions, but ${matching.length} matched ${JSON.stringify(input.node)}.`,
|
|
37
|
-
);
|
|
36
|
+
throw new Error(`Expected no matching open Perps positions, but found ${matching.length}.`);
|
|
38
37
|
}
|
|
39
38
|
|
|
40
39
|
return {
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
getCoreControllerWithSigner,
|
|
3
3
|
isDirectRun,
|
|
4
4
|
redactOrder,
|
|
5
|
+
requireExplicitSelection,
|
|
5
6
|
runAdapter,
|
|
6
7
|
selectedItems,
|
|
7
8
|
symbolForItem,
|
|
@@ -37,6 +38,7 @@ async function waitForOrdersAbsent(controller, accountAddress, symbols, timeoutM
|
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
export async function closeOrders(input) {
|
|
41
|
+
requireExplicitSelection(input);
|
|
40
42
|
const { controller, accountAddress, network } = await getCoreControllerWithSigner(input);
|
|
41
43
|
const timeoutMs = Number(input.node?.timeout_ms ?? 30000);
|
|
42
44
|
|
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
getCoreControllerWithSigner,
|
|
4
4
|
isDirectRun,
|
|
5
5
|
redactPosition,
|
|
6
|
+
requireExplicitSelection,
|
|
6
7
|
runAdapter,
|
|
7
8
|
selectedItems,
|
|
8
9
|
symbolForItem,
|
|
@@ -36,6 +37,7 @@ async function waitForPositionsAbsent(controller, accountAddress, symbols, timeo
|
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
export async function closePositions(input) {
|
|
40
|
+
requireExplicitSelection(input);
|
|
39
41
|
const { controller, accountAddress, network } = await getCoreControllerWithSigner(input);
|
|
40
42
|
const timeoutMs = Number(input.node?.timeout_ms ?? 30000);
|
|
41
43
|
const maxSlippageBps = Number(
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getCoreController, isDirectRun, runAdapter, selectedItems } from './_controller.mjs';
|
|
1
|
+
import { getCoreController, isDirectRun, requireExplicitSelection, runAdapter, selectedItems } from './_controller.mjs';
|
|
2
2
|
import { assertOrders } from './assert_orders.mjs';
|
|
3
3
|
import { closeOrders } from './close_orders.mjs';
|
|
4
4
|
import { placeOrder } from './place_order.mjs';
|
|
@@ -8,7 +8,9 @@ import { placeOrder } from './place_order.mjs';
|
|
|
8
8
|
// order state. Mirrors ensure_positions.mjs.
|
|
9
9
|
|
|
10
10
|
export async function ensureOrders(input) {
|
|
11
|
-
|
|
11
|
+
if (input.node?.state == null) throw new Error('metamask.perps.ensure_orders requires state=open or state=none.');
|
|
12
|
+
requireExplicitSelection(input);
|
|
13
|
+
const state = String(input.node.state).toLowerCase();
|
|
12
14
|
|
|
13
15
|
if (state === 'none' || state === 'closed' || state === 'absent') {
|
|
14
16
|
const close = await closeOrders(input);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getCoreController, isDirectRun, runAdapter, selectedItems } from './_controller.mjs';
|
|
1
|
+
import { getCoreController, isDirectRun, requireExplicitSelection, runAdapter, selectedItems } from './_controller.mjs';
|
|
2
2
|
import { assertPositions } from './assert_positions.mjs';
|
|
3
3
|
import { closePositions } from './close_positions.mjs';
|
|
4
4
|
import { placeOrder } from './place_order.mjs';
|
|
@@ -8,7 +8,9 @@ import { placeOrder } from './place_order.mjs';
|
|
|
8
8
|
// state. Mirrors the extension ensure_positions semantics.
|
|
9
9
|
|
|
10
10
|
export async function ensurePositions(input) {
|
|
11
|
-
|
|
11
|
+
if (input.node?.state == null) throw new Error('metamask.perps.ensure_positions requires state=open or state=none.');
|
|
12
|
+
requireExplicitSelection(input);
|
|
13
|
+
const state = String(input.node.state).toLowerCase();
|
|
12
14
|
|
|
13
15
|
if (state === 'none' || state === 'closed' || state === 'absent') {
|
|
14
16
|
const close = await closePositions(input);
|
|
@@ -81,12 +81,16 @@ function resolveLimitPrice(input, isBuy, mid) {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
export async function placeOrder(input) {
|
|
84
|
-
const { controller, accountAddress, network } = await getCoreControllerWithSigner(input);
|
|
85
84
|
const symbol = resolveSymbol(input);
|
|
86
|
-
|
|
85
|
+
if (input.node?.side == null) throw new Error('metamask.perps.place_order requires side=long or side=short.');
|
|
86
|
+
if (input.node?.amount == null && input.node?.notional == null) {
|
|
87
|
+
throw new Error('metamask.perps.place_order requires amount=<usd> or notional=<usd>.');
|
|
88
|
+
}
|
|
89
|
+
const { controller, accountAddress, network } = await getCoreControllerWithSigner(input);
|
|
90
|
+
const side = String(input.node.side).toLowerCase();
|
|
87
91
|
const isBuy = side !== 'short';
|
|
88
92
|
const orderType = resolveOrderType(input);
|
|
89
|
-
const usdAmount = String(input.node
|
|
93
|
+
const usdAmount = String(input.node.amount ?? input.node.notional);
|
|
90
94
|
const leverage = Number(input.node?.leverage ?? 3);
|
|
91
95
|
const maxSlippageBps = Number(
|
|
92
96
|
input.node?.max_slippage_bps ?? input.node?.maxSlippageBps ?? 300,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getCoreController, runAdapter } from './_controller.mjs';
|
|
1
|
+
import { getCoreController, isDirectRun, runAdapter } from './_controller.mjs';
|
|
2
2
|
|
|
3
3
|
function redactAccount(account) {
|
|
4
4
|
return {
|
|
@@ -27,4 +27,4 @@ export async function readAccount(input) {
|
|
|
27
27
|
};
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
runAdapter(readAccount);
|
|
30
|
+
if (isDirectRun(import.meta.url)) runAdapter(readAccount);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
getCoreController,
|
|
3
|
+
isDirectRun,
|
|
3
4
|
redactPosition,
|
|
4
5
|
runAdapter,
|
|
5
6
|
selectedItems,
|
|
@@ -24,4 +25,4 @@ export async function readPositions(input) {
|
|
|
24
25
|
};
|
|
25
26
|
}
|
|
26
27
|
|
|
27
|
-
runAdapter(readPositions);
|
|
28
|
+
if (isDirectRun(import.meta.url)) runAdapter(readPositions);
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
3
|
+
import { mnemonicToAccount, privateKeyToAccount } from 'viem/accounts';
|
|
4
|
+
|
|
5
|
+
import { walletFixturePath } from '../../harness-exports.mjs';
|
|
6
|
+
|
|
7
|
+
function fixtureAccountName(base, index, count) {
|
|
8
|
+
if (count === 1) return base || 'account1';
|
|
9
|
+
const match = /^(.*?)(\d+)$/u.exec(base || 'account1');
|
|
10
|
+
return match
|
|
11
|
+
? `${match[1]}${Number(match[2]) + index}`
|
|
12
|
+
: `${base || 'account'}${index + 1}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function mnemonicCount(entry, fixturePath, index) {
|
|
16
|
+
const count = Number(entry.count ?? entry.numberOfAccounts ?? 1);
|
|
17
|
+
if (!Number.isInteger(count) || count < 1 || count > 100) {
|
|
18
|
+
throw new Error(`${fixturePath} accounts[${index}] mnemonic count must be an integer from 1 through 100.`);
|
|
19
|
+
}
|
|
20
|
+
return count;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function deriveFixtureAccounts(fixture, fixturePath) {
|
|
24
|
+
if (!Array.isArray(fixture.accounts) || fixture.accounts.length === 0) {
|
|
25
|
+
throw new Error(`wallet fixture at ${fixturePath} has no accounts array.`);
|
|
26
|
+
}
|
|
27
|
+
return fixture.accounts.flatMap((entry, fixtureIndex) => {
|
|
28
|
+
if (!entry || typeof entry !== 'object' || typeof entry.value !== 'string' || !entry.value.trim()) {
|
|
29
|
+
throw new Error(`${fixturePath} accounts[${fixtureIndex}] must contain a non-empty value.`);
|
|
30
|
+
}
|
|
31
|
+
if (entry.type === 'mnemonic') {
|
|
32
|
+
const count = mnemonicCount(entry, fixturePath, fixtureIndex);
|
|
33
|
+
return Array.from({ length: count }, (_, addressIndex) => {
|
|
34
|
+
const account = mnemonicToAccount(entry.value.trim(), { addressIndex });
|
|
35
|
+
return {
|
|
36
|
+
id: `fixture:${fixtureAccountName(entry.name, addressIndex, count)}`,
|
|
37
|
+
address: account.address,
|
|
38
|
+
name: fixtureAccountName(entry.name, addressIndex, count),
|
|
39
|
+
type: 'eip155:eoa',
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
if (entry.type === 'privateKey') {
|
|
44
|
+
const raw = entry.value.trim();
|
|
45
|
+
const normalized = raw.startsWith('0x') ? raw : `0x${raw}`;
|
|
46
|
+
if (!/^0x[0-9a-fA-F]{64}$/u.test(normalized)) {
|
|
47
|
+
throw new Error(`${fixturePath} accounts[${fixtureIndex}] privateKey is not a 32-byte hex key.`);
|
|
48
|
+
}
|
|
49
|
+
const account = privateKeyToAccount(normalized);
|
|
50
|
+
const name = entry.name || `account${fixtureIndex + 1}`;
|
|
51
|
+
return [{ id: `fixture:${name}`, address: account.address, name, type: 'eip155:eoa' }];
|
|
52
|
+
}
|
|
53
|
+
throw new Error(`${fixturePath} accounts[${fixtureIndex}].type must be mnemonic or privateKey.`);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function listAccounts(input) {
|
|
58
|
+
const scope = input.node?.scope == null ? 'named' : String(input.node.scope);
|
|
59
|
+
if (scope !== 'selected' && scope !== 'named' && scope !== 'evm' && scope !== 'all') {
|
|
60
|
+
throw new Error('metamask.wallet.list_accounts scope must be selected, named, evm, or all.');
|
|
61
|
+
}
|
|
62
|
+
const fixturePath = walletFixturePath(input.context.projectRoot);
|
|
63
|
+
const fixture = JSON.parse(await readFile(fixturePath, 'utf8'));
|
|
64
|
+
const selectedName = String(input.node?.account_name ?? input.node?.account ?? 'dev1');
|
|
65
|
+
const accounts = deriveFixtureAccounts(fixture, fixturePath)
|
|
66
|
+
.map((account) => ({
|
|
67
|
+
...account,
|
|
68
|
+
selected: account.name === selectedName,
|
|
69
|
+
}))
|
|
70
|
+
.filter((account) => {
|
|
71
|
+
if (scope === 'selected') return account.selected;
|
|
72
|
+
if (scope === 'all') return true;
|
|
73
|
+
if (scope === 'evm') return /^0x[0-9a-f]{40}$/iu.test(account.address);
|
|
74
|
+
return account.selected || Boolean(account.name.trim());
|
|
75
|
+
});
|
|
76
|
+
return {
|
|
77
|
+
action: input.action,
|
|
78
|
+
source: 'core-headless-wallet-fixture',
|
|
79
|
+
scope,
|
|
80
|
+
accounts,
|
|
81
|
+
count: accounts.length,
|
|
82
|
+
redacted: true,
|
|
83
|
+
selectionSource: 'explicit-controller-account-input',
|
|
84
|
+
proofPath: 'core-wallet-fixture-accounts',
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
89
|
+
const inputPath = process.argv[2] || process.env.METAMASK_RECIPE_ADAPTER_INPUT;
|
|
90
|
+
if (!inputPath) throw new Error('Adapter input path is required.');
|
|
91
|
+
const input = JSON.parse(await readFile(inputPath, 'utf8'));
|
|
92
|
+
const result = await listAccounts(input);
|
|
93
|
+
if (input.outputPath) await writeFile(input.outputPath, `${JSON.stringify(result, null, 2)}\n`);
|
|
94
|
+
else process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
95
|
+
}
|
|
@@ -2,7 +2,8 @@ import { runAdapter } from '../platform/cdp.mjs';
|
|
|
2
2
|
import { assertOrders } from './perps.mjs';
|
|
3
3
|
|
|
4
4
|
function expectedOpen(input) {
|
|
5
|
-
|
|
5
|
+
if (input.node?.state == null) throw new Error('metamask.perps.assert_orders requires state=open or state=none.');
|
|
6
|
+
const state = String(input.node.state).toLowerCase();
|
|
6
7
|
if (state === 'open' || state === 'present') return true;
|
|
7
8
|
if (state === 'none' || state === 'closed' || state === 'absent') return false;
|
|
8
9
|
throw new Error(`metamask.perps.assert_orders received unsupported state: ${state}`);
|
|
@@ -2,7 +2,8 @@ import { runAdapter } from '../platform/cdp.mjs';
|
|
|
2
2
|
import { assertPositions } from './perps.mjs';
|
|
3
3
|
|
|
4
4
|
function expectedOpen(input) {
|
|
5
|
-
|
|
5
|
+
if (input.node?.state == null) throw new Error('metamask.perps.assert_positions requires state=open or state=none.');
|
|
6
|
+
const state = String(input.node.state).toLowerCase();
|
|
6
7
|
if (state === 'open' || state === 'present') return true;
|
|
7
8
|
if (state === 'none' || state === 'closed' || state === 'absent') return false;
|
|
8
9
|
throw new Error(`metamask.perps.assert_positions received unsupported state: ${state}`);
|
|
@@ -24,20 +24,39 @@ function configuredSymbols(input, items) {
|
|
|
24
24
|
const explicit = input.node?.markets ?? input.node?.symbols ?? selector.markets ?? selector.symbols;
|
|
25
25
|
if (Array.isArray(explicit) && explicit.length > 0) return uniqueSymbols(explicit.map(normalizeMarketSymbol));
|
|
26
26
|
if (typeof explicit === 'string' && explicit.length > 0) return uniqueSymbols(explicit.split(',').map((part) => normalizeMarketSymbol(part.trim())).filter(Boolean));
|
|
27
|
-
|
|
27
|
+
const single = input.node?.market ?? input.node?.symbol ?? selector.market ?? selector.symbol;
|
|
28
|
+
return single ? [normalizeMarketSymbol(single)] : [];
|
|
28
29
|
}
|
|
29
30
|
|
|
30
|
-
function selectedItems(input, items) {
|
|
31
|
-
const
|
|
31
|
+
export function selectedItems(input, items) {
|
|
32
|
+
const requested = configuredSymbols(input, items);
|
|
33
|
+
const symbols = requested.length > 0 ? new Set(requested) : null;
|
|
32
34
|
const selector = input.node?.selector && typeof input.node.selector === 'object' ? input.node.selector : {};
|
|
33
35
|
const side = input.node?.side ?? selector.side;
|
|
34
36
|
return items.filter((item) => {
|
|
35
|
-
if (!symbols.has(symbolForItem(item))) return false;
|
|
37
|
+
if (symbols && !symbols.has(symbolForItem(item))) return false;
|
|
36
38
|
if (side && sideForItem(item) && sideForItem(item) !== String(side).toLowerCase()) return false;
|
|
37
39
|
return true;
|
|
38
40
|
});
|
|
39
41
|
}
|
|
40
42
|
|
|
43
|
+
function requireExplicitSelection(input) {
|
|
44
|
+
const node = input.node ?? {};
|
|
45
|
+
const selector = node.selector && typeof node.selector === 'object' ? node.selector : {};
|
|
46
|
+
const values = [node.market, node.symbol, node.markets, node.symbols, selector.market, selector.symbol, selector.markets, selector.symbols];
|
|
47
|
+
const selected = values.some((value) => Array.isArray(value) ? value.length > 0 : value != null && String(value).trim() !== '');
|
|
48
|
+
if (!selected && node.mode !== 'all' && selector.mode !== 'all') {
|
|
49
|
+
throw new Error(`${input.action} requires market=<symbol> or mode=all.`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function requirePlaceOrderInputs(input) {
|
|
54
|
+
const node = input.node ?? {};
|
|
55
|
+
if (node.market == null && node.symbol == null) throw new Error(`${input.action} requires market=<symbol>.`);
|
|
56
|
+
if (node.side == null) throw new Error(`${input.action} requires side=long or side=short.`);
|
|
57
|
+
if (node.amount == null && node.notional == null) throw new Error(`${input.action} requires amount=<usd> or notional=<usd>.`);
|
|
58
|
+
}
|
|
59
|
+
|
|
41
60
|
export function positionSelectionState(input, positions) {
|
|
42
61
|
return { hasMatching: selectedItems(input, positions).length > 0 };
|
|
43
62
|
}
|
|
@@ -85,33 +104,37 @@ export async function readOrders(input) {
|
|
|
85
104
|
}
|
|
86
105
|
|
|
87
106
|
export async function assertPositions(input, expectedOpen) {
|
|
107
|
+
requireExplicitSelection(input);
|
|
88
108
|
return withExtensionPage(input, async (page) => {
|
|
89
109
|
const timeoutMs = Number(input.node?.timeout_ms ?? 0);
|
|
90
|
-
|
|
91
|
-
|
|
110
|
+
const requested = configuredSymbols(input, []);
|
|
111
|
+
if (expectedOpen && timeoutMs > 0 && requested.length === 1) {
|
|
112
|
+
await waitForPositionPresent(page, requested[0], timeoutMs);
|
|
92
113
|
}
|
|
93
114
|
const state = await page.readPositions();
|
|
94
115
|
if (!state.available) throw new Error('stateHooks.submitRequestToBackground is unavailable; cannot assert live Perps positions.');
|
|
95
116
|
const matching = selectedItems(input, state.positions);
|
|
96
117
|
const hasPosition = matching.length > 0;
|
|
97
|
-
if (expectedOpen && !hasPosition) throw new Error(
|
|
98
|
-
if (!expectedOpen && hasPosition) throw new Error(`Expected no
|
|
118
|
+
if (expectedOpen && !hasPosition) throw new Error('Expected at least one matching open Perps position, but found none.');
|
|
119
|
+
if (!expectedOpen && hasPosition) throw new Error(`Expected no matching open Perps positions, but found ${matching.length}.`);
|
|
99
120
|
return { action: input.action, expectedOpen, matchingCount: matching.length, positions: matching.map(redactPosition), source: state.source ?? 'perps-stream-manager-cache' };
|
|
100
121
|
});
|
|
101
122
|
}
|
|
102
123
|
|
|
103
124
|
export async function assertOrders(input, expectedOpen) {
|
|
125
|
+
requireExplicitSelection(input);
|
|
104
126
|
return withExtensionPage(input, async (page) => {
|
|
105
127
|
const orders = await readOpenOrders(page);
|
|
106
128
|
const matching = selectedItems(input, orders);
|
|
107
129
|
const hasOrders = matching.length > 0;
|
|
108
|
-
if (expectedOpen && !hasOrders) throw new Error(
|
|
109
|
-
if (!expectedOpen && hasOrders) throw new Error(`Expected no
|
|
130
|
+
if (expectedOpen && !hasOrders) throw new Error('Expected at least one matching open Perps order, but found none.');
|
|
131
|
+
if (!expectedOpen && hasOrders) throw new Error(`Expected no matching open Perps orders, but found ${matching.length}.`);
|
|
110
132
|
return { action: input.action, expectedOpen, matchingCount: matching.length, orders: matching.map(redactOrder), source: 'perps-stream-manager-cache' };
|
|
111
133
|
});
|
|
112
134
|
}
|
|
113
135
|
|
|
114
136
|
export async function closePositions(input) {
|
|
137
|
+
requireExplicitSelection(input);
|
|
115
138
|
return withExtensionPage(input, async (page) => {
|
|
116
139
|
const timeoutMs = Number(input.node?.timeout_ms ?? 30000);
|
|
117
140
|
const state = await page.readPositions();
|
|
@@ -147,6 +170,7 @@ export async function closePositions(input) {
|
|
|
147
170
|
}
|
|
148
171
|
|
|
149
172
|
export async function closeOrders(input) {
|
|
173
|
+
requireExplicitSelection(input);
|
|
150
174
|
return withExtensionPage(input, async (page) => {
|
|
151
175
|
const timeoutMs = Number(input.node?.timeout_ms ?? 30000);
|
|
152
176
|
const before = await readOpenOrders(page);
|
|
@@ -370,9 +394,10 @@ async function waitForPositionPresent(page, symbol, timeoutMs) {
|
|
|
370
394
|
}
|
|
371
395
|
|
|
372
396
|
export async function placeOrder(input) {
|
|
397
|
+
requirePlaceOrderInputs(input);
|
|
373
398
|
const symbol = marketSymbol(input);
|
|
374
|
-
const side = String(input.node
|
|
375
|
-
const amount = String(input.node
|
|
399
|
+
const side = String(input.node.side).toLowerCase();
|
|
400
|
+
const amount = String(input.node.amount ?? input.node.notional);
|
|
376
401
|
const leverage = Number(input.node?.leverage ?? 3);
|
|
377
402
|
return withExtensionPage(input, async (page) => {
|
|
378
403
|
await page.navigateHash(`#/perps/market/${encodeURIComponent(symbol)}`);
|
|
@@ -427,7 +452,9 @@ export async function placeOrder(input) {
|
|
|
427
452
|
}
|
|
428
453
|
|
|
429
454
|
export async function ensurePositions(input) {
|
|
430
|
-
|
|
455
|
+
if (input.node?.state == null) throw new Error('metamask.perps.ensure_positions requires state=open or state=none.');
|
|
456
|
+
requireExplicitSelection(input);
|
|
457
|
+
const state = String(input.node.state).toLowerCase();
|
|
431
458
|
if (state === 'none' || state === 'closed' || state === 'absent') {
|
|
432
459
|
const close = await closePositions(input);
|
|
433
460
|
return { ...(await assertPositions(input, false)), close };
|
|
@@ -445,7 +472,9 @@ export async function ensurePositions(input) {
|
|
|
445
472
|
}
|
|
446
473
|
|
|
447
474
|
export async function ensureOrders(input) {
|
|
448
|
-
|
|
475
|
+
if (input.node?.state == null) throw new Error('metamask.perps.ensure_orders requires state=open or state=none.');
|
|
476
|
+
requireExplicitSelection(input);
|
|
477
|
+
const state = String(input.node.state).toLowerCase();
|
|
449
478
|
if (state === 'none' || state === 'closed' || state === 'absent') {
|
|
450
479
|
const close = await closeOrders(input);
|
|
451
480
|
return { ...(await assertOrders(input, false)), close };
|
|
@@ -397,6 +397,7 @@ async function launchExistingDistRuntime(input, port) {
|
|
|
397
397
|
try {
|
|
398
398
|
child = spawn(chrome, [
|
|
399
399
|
`--user-data-dir=${profileDir}`,
|
|
400
|
+
...(process.platform === 'darwin' ? ['--use-mock-keychain'] : []),
|
|
400
401
|
'--remote-debugging-address=127.0.0.1',
|
|
401
402
|
`--remote-debugging-port=${port}`,
|
|
402
403
|
'--no-first-run',
|