@deeeed/metamask-harness 0.39.0 → 0.40.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 +46 -0
- package/adapters/extension/lib/playwright-cdp.cjs +17 -1
- package/adapters/extension/sidepanel-toggle.sh +135 -13
- package/dist/adapters/mobile/release-artifact.js +65 -0
- package/dist/adapters.js +8 -7
- package/dist/command-contract.js +10 -2
- package/dist/commands/call.js +7 -4
- package/dist/commands/launch/index.js +55 -3
- package/dist/commands/parse-args.js +6 -0
- package/dist/commands/run-engine.js +75 -39
- package/dist/funding-execution-context.js +1386 -0
- package/dist/live-adapter-contract.js +3 -1
- package/dist/metamask-action-validation.js +170 -3
- package/dist/mm-harness-cli.js +8 -2
- package/dist/recipe-security.js +5 -12
- package/dist/runner.js +102 -6
- package/docs/RECIPES.md +74 -1
- package/docs/RELEASE-QA-CAPABILITY-MAP.md +6 -5
- package/library/actions/extension/networks/add_chainlist.mjs +4 -0
- package/library/actions/extension/networks/add_custom.mjs +4 -0
- package/library/actions/extension/networks/custom_network.mjs +48 -1
- package/library/actions/extension/perps/assert_visible_consistency.mjs +366 -0
- package/library/actions/extension/perps/close_visible_position.mjs +299 -37
- package/library/actions/extension/perps/compare_provider_market.mjs +77 -0
- package/library/actions/extension/perps/edit_margin.mjs +285 -44
- package/library/actions/extension/perps/mutation-receipt.mjs +781 -0
- package/library/actions/extension/perps/perps.mjs +1029 -363
- package/library/actions/extension/perps/read_funds_confirmation.mjs +126 -13
- package/library/actions/extension/perps/read_snapshot.mjs +124 -3
- package/library/actions/extension/perps/read_visible_state.mjs +63 -5
- package/library/actions/extension/perps/select_activity_filter.mjs +81 -18
- package/library/actions/extension/perps/select_market_filter.mjs +48 -4
- package/library/actions/extension/perps/update_position_tpsl.mjs +251 -69
- package/library/actions/extension/perps/visible-mutation-identity.mjs +164 -0
- package/library/actions/extension/platform/cdp.mjs +88 -9
- package/library/actions/extension/settings/set_basic_functionality.mjs +35 -7
- package/library/actions/extension/ui/navigate.mjs +45 -5
- package/library/actions/extension/wallet/import.mjs +70 -29
- package/library/actions/extension/wallet/lock.mjs +62 -5
- package/library/actions/extension/wallet/visible-session.mjs +218 -0
- package/library/actions/mobile/ui/native-navigation.mjs +45 -0
- package/library/actions/mobile/wallet/import.mjs +17 -10
- package/library/actions/mobile/wallet/native-ui.mjs +21 -1
- package/library/actions/shared/perps/visible-state.mjs +25 -5
- package/library/manifests/extension.action-manifest.json +1696 -1271
- package/library/recipes/extension/assets/release-custom-network-token-persistence.recipe.json +1 -0
- package/library/recipes/{perps → extension/perps}/release-activity-filters.recipe.json +5 -5
- package/library/recipes/{perps → extension/perps}/release-funds-flows.recipe.json +3 -3
- package/library/recipes/extension/perps/release-live-limit-order.recipe.json +24 -19
- package/library/recipes/extension/perps/release-market-filters.recipe.json +97 -0
- package/library/recipes/extension/perps/release-order-entry.recipe.json +4 -1
- package/library/recipes/extension/perps/release-order-validation.recipe.json +53 -14
- package/library/recipes/extension/perps/release-visible-consistency.recipe.json +47 -0
- package/library/recipes/extension/perps/source-dev-snapshot-consistency.recipe.json +76 -0
- package/library/recipes/perps/release-live-market-order.recipe.json +30 -13
- package/library/recipes/perps/release-market-details.recipe.json +1 -1
- package/library/recipes/swap-bridge/release-quote.recipe.json +1 -0
- package/library/recipes/wallet/import.recipe.json +4 -14
- package/package.json +1 -1
- package/library/actions/extension/assets/finish_send.mjs +0 -128
- package/library/actions/extension/swap_bridge/submit_transaction.mjs +0 -363
- package/library/actions/extension/ui/activate_and_observe.mjs +0 -96
- package/library/recipes/assets/release-custom-gas-send.recipe.json +0 -88
- package/library/recipes/assets/release-native-max-cancel.recipe.json +0 -70
- package/library/recipes/assets/release-native-max-send.recipe.json +0 -78
- package/library/recipes/assets/release-own-account-send.recipe.json +0 -80
- package/library/recipes/extension/runner/action-validation.recipe.json +0 -343
|
@@ -196,6 +196,7 @@ function parsePort(value, errorMessage) {
|
|
|
196
196
|
}
|
|
197
197
|
function runtimeOptionsFromCli(options) {
|
|
198
198
|
const recordVideo = options.recordVideo;
|
|
199
|
+
const hud = optionString(options, "hud");
|
|
199
200
|
const trust = explicitRecipeTrustOptions({
|
|
200
201
|
sourceTrust: optionString(options, "sourceTrust"),
|
|
201
202
|
sourceKind: optionString(options, "sourceKind"),
|
|
@@ -210,6 +211,11 @@ function runtimeOptionsFromCli(options) {
|
|
|
210
211
|
slot: optionString(options, "slot"),
|
|
211
212
|
validationRuntimeDir: optionString(options, "validationRuntimeDir"),
|
|
212
213
|
recordVideo: recordVideo === "full-run" ? "full-run" : false,
|
|
214
|
+
autoHud: hud === "show" ? true : hud === "hide" ? false : void 0,
|
|
215
|
+
fundingRequest: optionString(options, "fundingRequest"),
|
|
216
|
+
fundingPreflight: optionString(options, "fundingPreflight"),
|
|
217
|
+
fundingReleaseProof: optionString(options, "fundingReleaseProof"),
|
|
218
|
+
fundingBalanceProof: optionString(options, "fundingBalanceProof"),
|
|
213
219
|
...trust
|
|
214
220
|
};
|
|
215
221
|
}
|
|
@@ -23,8 +23,15 @@ import {
|
|
|
23
23
|
} from "../paths.js";
|
|
24
24
|
import { captureHelperSupportsRecordSessionSnapshots } from "../recording-target.js";
|
|
25
25
|
import { startRecipeRecording, stopRecipeRecording } from "../run-recording.js";
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
resolveMetaMaskParameterValue,
|
|
28
|
+
validateMetaMaskActionInputs
|
|
29
|
+
} from "../metamask-action-validation.js";
|
|
27
30
|
import { gitLibraryProvenance } from "../library-provenance.js";
|
|
31
|
+
import {
|
|
32
|
+
authorizeTrustedMutationPlan,
|
|
33
|
+
loadTrustedMutationBase
|
|
34
|
+
} from "../funding-execution-context.js";
|
|
28
35
|
import {
|
|
29
36
|
beginRunDiagnostics,
|
|
30
37
|
finishRunDiagnostics
|
|
@@ -139,9 +146,18 @@ async function resolveRecipeExecution(adapter, recipe, artifactsDir, projectRoot
|
|
|
139
146
|
);
|
|
140
147
|
const { createMetaMaskRunner } = await import("../runner.js");
|
|
141
148
|
const recipeDocument = runtimeRecipeDocument(recipe, absoluteRecipePath);
|
|
149
|
+
const trustedMutation = await loadTrustedMutationBase({
|
|
150
|
+
fundingRequest: runtimeOptions.fundingRequest,
|
|
151
|
+
fundingPreflight: runtimeOptions.fundingPreflight,
|
|
152
|
+
fundingReleaseProof: runtimeOptions.fundingReleaseProof,
|
|
153
|
+
fundingBalanceProof: runtimeOptions.fundingBalanceProof,
|
|
154
|
+
artifactsDir: absoluteArtifactsDir,
|
|
155
|
+
projectRoot,
|
|
156
|
+
rootRecipe: recipeDocument
|
|
157
|
+
});
|
|
142
158
|
const suppressHudForLifecycleTiming = adapter === "mobile" && mobileRecipeRequiresUninterruptedLifecycleTiming(recipeDocument);
|
|
143
159
|
const suppressHudForOpaqueMobile = adapter === "mobile" && process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME === "1";
|
|
144
|
-
const
|
|
160
|
+
const runnerOptions = {
|
|
145
161
|
quietStdout: runtimeOptions.stdoutIsMachineContract === true,
|
|
146
162
|
suppressLibraryResolutionLogs: runtimeOptions.suppressLibraryResolutionLogs,
|
|
147
163
|
autoHud: suppressHudForLifecycleTiming || suppressHudForOpaqueMobile ? false : trust.source && trust.source.trust !== "trusted" ? false : runtimeOptions.autoHud,
|
|
@@ -151,28 +167,41 @@ async function resolveRecipeExecution(adapter, recipe, artifactsDir, projectRoot
|
|
|
151
167
|
// task-local adapter. Inherited gateway provenance remains authoritative,
|
|
152
168
|
// so an untrusted worker cannot promote itself with CLI flags.
|
|
153
169
|
trustTaskActions: (trust.source?.trust ?? "trusted") === "trusted"
|
|
170
|
+
};
|
|
171
|
+
let runner = await createMetaMaskRunner(adapter, manifest, {
|
|
172
|
+
...runnerOptions,
|
|
173
|
+
trustedMutation
|
|
154
174
|
});
|
|
155
175
|
const useFramedExtensionRecording = adapter === "extension" && recordVideo === "full-run" && trust.source?.trust !== "untrusted" && trust.source?.trust !== "unknown" && captureHelperSupportsRecordSessionSnapshots(projectRoot);
|
|
176
|
+
const runRequest = {
|
|
177
|
+
...absoluteRecipePath ? { recipePath: absoluteRecipePath } : { recipeDocument: recipe },
|
|
178
|
+
artifactsDir: absoluteArtifactsDir,
|
|
179
|
+
projectRoot,
|
|
180
|
+
inheritProcessEnv: false,
|
|
181
|
+
env: {
|
|
182
|
+
...recipeProcessEnvironment(),
|
|
183
|
+
...recipeRunEnv(adapter, runtimeOptions),
|
|
184
|
+
METAMASK_RECIPE_ACTION_SOURCE_MAP: actionSourceMap(actionSources)
|
|
185
|
+
},
|
|
186
|
+
recordVideo: useFramedExtensionRecording ? false : recordVideo,
|
|
187
|
+
...trust,
|
|
188
|
+
...librarySources ? { librarySources } : {},
|
|
189
|
+
adapter,
|
|
190
|
+
...runtimeOptions.params ? { params: runtimeOptions.params } : {}
|
|
191
|
+
};
|
|
192
|
+
if (trustedMutation) {
|
|
193
|
+
const executionPlan = await runner.preflight(runRequest);
|
|
194
|
+
const authorizedMutation = await authorizeTrustedMutationPlan(trustedMutation, executionPlan);
|
|
195
|
+
runner = await createMetaMaskRunner(adapter, manifest, {
|
|
196
|
+
...runnerOptions,
|
|
197
|
+
trustedMutation: authorizedMutation
|
|
198
|
+
});
|
|
199
|
+
}
|
|
156
200
|
return {
|
|
157
201
|
runner,
|
|
158
202
|
absoluteArtifactsDir,
|
|
159
203
|
useFramedExtensionRecording,
|
|
160
|
-
runRequest
|
|
161
|
-
...absoluteRecipePath ? { recipePath: absoluteRecipePath } : { recipeDocument: recipe },
|
|
162
|
-
artifactsDir: absoluteArtifactsDir,
|
|
163
|
-
projectRoot,
|
|
164
|
-
inheritProcessEnv: false,
|
|
165
|
-
env: {
|
|
166
|
-
...recipeProcessEnvironment(),
|
|
167
|
-
...recipeRunEnv(adapter, runtimeOptions),
|
|
168
|
-
METAMASK_RECIPE_ACTION_SOURCE_MAP: actionSourceMap(actionSources)
|
|
169
|
-
},
|
|
170
|
-
recordVideo: useFramedExtensionRecording ? false : recordVideo,
|
|
171
|
-
...trust,
|
|
172
|
-
...librarySources ? { librarySources } : {},
|
|
173
|
-
adapter,
|
|
174
|
-
...runtimeOptions.params ? { params: runtimeOptions.params } : {}
|
|
175
|
-
}
|
|
204
|
+
runRequest
|
|
176
205
|
};
|
|
177
206
|
}
|
|
178
207
|
function runtimeRecipeDocument(recipe, absoluteRecipePath) {
|
|
@@ -201,8 +230,7 @@ function recipeProcessEnvironment() {
|
|
|
201
230
|
}
|
|
202
231
|
function executionLibrarySources(sources, invocationTrust) {
|
|
203
232
|
return sources?.map((source) => {
|
|
204
|
-
if (source.name === "metamask"
|
|
205
|
-
return source;
|
|
233
|
+
if (source.name === "metamask") return source;
|
|
206
234
|
return {
|
|
207
235
|
...source,
|
|
208
236
|
provenance: {
|
|
@@ -347,7 +375,7 @@ async function validateRecipeAdapterAware(adapter, recipe, manifest, librarySour
|
|
|
347
375
|
);
|
|
348
376
|
const findings = [
|
|
349
377
|
...withManifest.findings,
|
|
350
|
-
...validateMetaMaskActionInputs(recipe, adapter)
|
|
378
|
+
...validateMetaMaskActionInputs(recipe, adapter, params)
|
|
351
379
|
];
|
|
352
380
|
if (withManifest.status === "valid" && isRecord(recipe) && libraryResolution) {
|
|
353
381
|
try {
|
|
@@ -376,10 +404,26 @@ async function validateRecipeAdapterAware(adapter, recipe, manifest, librarySour
|
|
|
376
404
|
dependency.document,
|
|
377
405
|
manifest,
|
|
378
406
|
validationOptions
|
|
379
|
-
).findings
|
|
380
|
-
...validateMetaMaskActionInputs(dependency.document, adapter)
|
|
407
|
+
).findings
|
|
381
408
|
);
|
|
382
409
|
}
|
|
410
|
+
const validateDependencyInstances = (document, parentParams) => {
|
|
411
|
+
if (!isRecord(document) || !isRecord(document.workflow) || !isRecord(document.workflow.nodes)) return;
|
|
412
|
+
for (const rawNode of Object.values(document.workflow.nodes)) {
|
|
413
|
+
const node = resolveMetaMaskParameterValue(rawNode, parentParams);
|
|
414
|
+
if (!isRecord(node) || node.action !== "call" || typeof node.ref !== "string") continue;
|
|
415
|
+
const dependency = dependencies.recipes.get(protocol.normalizeRecipeRef(node.ref));
|
|
416
|
+
if (!dependency) continue;
|
|
417
|
+
const supplied = isRecord(node.params) ? node.params : {};
|
|
418
|
+
const childParams = protocol.applyRecipeParamDefaults(
|
|
419
|
+
supplied,
|
|
420
|
+
isRecord(dependency.document) ? dependency.document.paramsSchema : void 0
|
|
421
|
+
);
|
|
422
|
+
findings.push(...validateMetaMaskActionInputs(dependency.document, adapter, childParams));
|
|
423
|
+
validateDependencyInstances(dependency.document, childParams);
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
validateDependencyInstances(recipe, params ?? {});
|
|
383
427
|
} catch (error) {
|
|
384
428
|
const resolutionError = error instanceof harness.RecipeResolutionError ? error : void 0;
|
|
385
429
|
if (!resolutionError) throw error;
|
|
@@ -429,13 +473,19 @@ function effectiveLibrarySources(librarySources) {
|
|
|
429
473
|
}
|
|
430
474
|
async function listRunnableRecipes(adapter, librarySources) {
|
|
431
475
|
const harness = await importRecipeHarness();
|
|
476
|
+
const sources = effectiveLibrarySources(librarySources);
|
|
432
477
|
const resolution = await harness.loadRecipeLibraries(
|
|
433
|
-
|
|
478
|
+
sources,
|
|
434
479
|
{
|
|
435
480
|
adapter
|
|
436
481
|
}
|
|
437
482
|
);
|
|
438
|
-
|
|
483
|
+
const protocol = await importRecipeProtocol();
|
|
484
|
+
const { manifest } = await resolveActionManifest(adapter, void 0, sources);
|
|
485
|
+
const externalRecipeIds = new Set(resolution.recipes.keys());
|
|
486
|
+
return [...resolution.recipes.values()].filter((recipe) => protocol.validateRecipeWithManifest(recipe.document, manifest, {
|
|
487
|
+
externalRecipeIds
|
|
488
|
+
}).status === "valid").map(
|
|
439
489
|
(recipe) => recipeSummary(recipe.ref, adapter, recipe.document, recipe)
|
|
440
490
|
).sort((left, right) => left.name.localeCompare(right.name));
|
|
441
491
|
}
|
|
@@ -630,20 +680,6 @@ async function validateRunRecipeStatic(recipeArg, adapter, options, params = {})
|
|
|
630
680
|
findings.push(
|
|
631
681
|
...protocol.validateRecipeParams(effectiveParams, recipe.paramsSchema).findings
|
|
632
682
|
);
|
|
633
|
-
const workflow = isRecord(recipe.workflow) ? recipe.workflow : void 0;
|
|
634
|
-
const nodes = workflow && isRecord(workflow.nodes) ? workflow.nodes : void 0;
|
|
635
|
-
if (adapter === "extension" && nodes) {
|
|
636
|
-
for (const [nodeId, node] of Object.entries(nodes)) {
|
|
637
|
-
if (isRecord(node) && node.action === "metamask.perps.start_state" && node.page === false) {
|
|
638
|
-
findings.push({
|
|
639
|
-
severity: "error",
|
|
640
|
-
code: "recipe.extension-perps-start-page",
|
|
641
|
-
path: `workflow.nodes.${nodeId}.page`,
|
|
642
|
-
message: "Extension metamask.perps.start_state must enter Perps; omit page or provide a string target instead of false"
|
|
643
|
-
});
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
683
|
}
|
|
648
684
|
const validation = manifestOk ? await validateRecipeAdapterAware(
|
|
649
685
|
adapter,
|