@deeeed/metamask-harness 0.18.0 → 0.19.1

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.
Files changed (82) hide show
  1. package/CHANGELOG.md +200 -0
  2. package/README.md +4 -7
  3. package/adapters/core/inject.sh +1 -6
  4. package/adapters/extension/inject.mjs +1 -4
  5. package/adapters/extension/verify.sh +1 -1
  6. package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +33 -0
  7. package/adapters/mobile/inject.sh +5 -4
  8. package/adapters/mobile/verify.sh +15 -19
  9. package/adapters/shared/harness-source-fingerprint.mjs +14 -12
  10. package/dist/adapters/harness-freshness.js +49 -0
  11. package/dist/adapters.js +6 -1
  12. package/dist/cli.js +0 -4
  13. package/dist/command-contract.js +51 -40
  14. package/dist/command-journal.js +50 -12
  15. package/dist/commands/call.js +95 -20
  16. package/dist/commands/check.js +1 -1
  17. package/dist/commands/completion-candidates.js +7 -19
  18. package/dist/commands/fixtures.js +50 -1
  19. package/dist/commands/last.js +9 -1
  20. package/dist/commands/launch/extension.js +2 -2
  21. package/dist/commands/launch/mobile.js +2 -0
  22. package/dist/commands/list-executables.js +56 -20
  23. package/dist/commands/manifest.js +28 -12
  24. package/dist/commands/parse-args.js +55 -3
  25. package/dist/commands/provision.js +0 -1
  26. package/dist/commands/run-engine.js +514 -336
  27. package/dist/commands/run.js +25 -37
  28. package/dist/commands/shared.js +1 -1
  29. package/dist/heal-bounds.js +5 -0
  30. package/dist/live-adapter-contract.js +15 -2
  31. package/dist/mm-harness-cli.js +22 -30
  32. package/dist/run-diagnostics.js +1 -1
  33. package/dist/run-recording.js +1 -1
  34. package/dist/runner.js +24 -2
  35. package/docs/CONTRIBUTING.md +2 -3
  36. package/docs/QA.md +0 -1
  37. package/docs/RECIPES.md +67 -91
  38. package/library/README.md +5 -5
  39. package/library/actions/core/perps/_controller.mjs +33 -1
  40. package/library/actions/core/perps/assert_orders.mjs +22 -12
  41. package/library/actions/core/perps/assert_positions.mjs +22 -12
  42. package/library/actions/core/perps/close_orders.mjs +2 -0
  43. package/library/actions/core/perps/close_positions.mjs +2 -0
  44. package/library/actions/core/perps/ensure_orders.mjs +4 -2
  45. package/library/actions/core/perps/ensure_positions.mjs +4 -2
  46. package/library/actions/core/perps/place_order.mjs +7 -3
  47. package/library/actions/extension/perps/assert_orders.mjs +2 -1
  48. package/library/actions/extension/perps/assert_positions.mjs +2 -1
  49. package/library/actions/extension/perps/perps.mjs +151 -30
  50. package/library/actions/mobile/perps/assert_orders.mjs +2 -1
  51. package/library/actions/mobile/perps/assert_positions.mjs +2 -1
  52. package/library/actions/mobile/perps/perps.mjs +148 -22
  53. package/library/library.json +1 -1
  54. package/library/manifests/core.action-manifest.json +1204 -411
  55. package/library/manifests/extension.action-manifest.json +1536 -656
  56. package/library/manifests/mobile.action-manifest.json +1728 -758
  57. package/library/recipes/app/lifecycle.android-smoke.mobile.recipe.json +63 -81
  58. package/library/recipes/perps/clean-market-testnet.core.recipe.json +44 -0
  59. package/library/recipes/perps/clean-market-testnet.recipe.json +49 -0
  60. package/library/recipes/perps/lifecycle.recipe.json +136 -180
  61. package/library/recipes/perps/order-lifecycle.core.recipe.json +71 -67
  62. package/library/recipes/perps/performance.background-resume.mobile.recipe.json +51 -67
  63. package/library/recipes/perps/performance.cold-start.mobile.recipe.json +51 -67
  64. package/library/recipes/perps/performance.mobile.recipe.json +37 -51
  65. package/library/recipes/perps/performance.warm-start.mobile.recipe.json +44 -59
  66. package/library/recipes/perps/read-markets.core.recipe.json +29 -31
  67. package/library/recipes/perps/smoke.core.recipe.json +29 -32
  68. package/library/recipes/perps/smoke.extension.recipe.json +41 -44
  69. package/library/recipes/perps/smoke.mobile.recipe.json +42 -44
  70. package/library/recipes/perps/trading-lifecycle.core.recipe.json +69 -65
  71. package/library/recipes/runner/action-validation.extension.recipe.json +312 -405
  72. package/library/recipes/runner/action-validation.mobile.recipe.json +316 -409
  73. package/library/recipes/runner/smoke.core.recipe.json +18 -20
  74. package/library/recipes/runner/smoke.extension.recipe.json +23 -24
  75. package/library/recipes/runner/smoke.mobile.recipe.json +23 -24
  76. package/library/recipes/wallet/smoke.extension.recipe.json +33 -35
  77. package/library/recipes/wallet/smoke.mobile.recipe.json +33 -35
  78. package/package.json +3 -3
  79. package/scripts/completions.sh +1 -4
  80. package/dist/adapters/extension/harness-freshness.js +0 -39
  81. package/dist/commands/flows.js +0 -91
  82. package/library/flows/perps.flows.json +0 -64
@@ -12,6 +12,7 @@
12
12
  "index_artifacts",
13
13
  "end",
14
14
  "call",
15
+ "switch",
15
16
  "ui.navigate",
16
17
  "ui.press",
17
18
  "ui.key_press",
@@ -35,7 +36,28 @@
35
36
  "intent": "Run a shell command from the project root"
36
37
  }
37
38
  }
38
- ]
39
+ ],
40
+ "schema": {
41
+ "type": "object",
42
+ "properties": {
43
+ "cmd": {
44
+ "type": "string"
45
+ },
46
+ "command": {
47
+ "type": "string"
48
+ },
49
+ "cwd": {
50
+ "type": "string"
51
+ },
52
+ "timeout_ms": {
53
+ "type": "number"
54
+ },
55
+ "allow_failure": {
56
+ "type": "boolean"
57
+ }
58
+ },
59
+ "additionalProperties": false
60
+ }
39
61
  },
40
62
  "wait": {
41
63
  "description": "Base wait action.",
@@ -47,7 +69,44 @@
47
69
  "intent": "Base wait action"
48
70
  }
49
71
  }
50
- ]
72
+ ],
73
+ "schema": {
74
+ "type": "object",
75
+ "properties": {
76
+ "duration_ms": {
77
+ "type": "integer"
78
+ },
79
+ "ms": {
80
+ "type": "integer"
81
+ }
82
+ },
83
+ "additionalProperties": false
84
+ }
85
+ },
86
+ "switch": {
87
+ "description": "Choose a graph branch when one resolved string equals another.",
88
+ "examples": [
89
+ {
90
+ "node": {
91
+ "action": "switch",
92
+ "value": "{{params.mode}}",
93
+ "equals": "background_resume",
94
+ "cases": { "match": "background" },
95
+ "default": "warm",
96
+ "intent": "Choose the requested lifecycle preparation path"
97
+ }
98
+ }
99
+ ],
100
+ "schema": {
101
+ "type": "object",
102
+ "properties": {
103
+ "value": { "type": "string" },
104
+ "equals": { "type": "string" }
105
+ },
106
+ "required": ["value", "equals"],
107
+ "additionalProperties": false
108
+ },
109
+ "result_cases": ["match"]
51
110
  },
52
111
  "watch_logs": {
53
112
  "description": "Base log assertion/capture action.",
@@ -59,7 +118,29 @@
59
118
  "intent": "Base log assertion/capture action"
60
119
  }
61
120
  }
62
- ]
121
+ ],
122
+ "schema": {
123
+ "type": "object",
124
+ "properties": {
125
+ "path": {
126
+ "type": "string"
127
+ },
128
+ "contains": {
129
+ "type": "string"
130
+ },
131
+ "scope": {
132
+ "type": "string",
133
+ "enum": [
134
+ "run",
135
+ "file"
136
+ ]
137
+ }
138
+ },
139
+ "required": [
140
+ "path"
141
+ ],
142
+ "additionalProperties": false
143
+ }
63
144
  },
64
145
  "index_artifacts": {
65
146
  "description": "Index extra recipe-authored artifacts that already exist on disk. Do not use after ui.screenshot; screenshots are registered by the runner.",
@@ -78,7 +159,55 @@
78
159
  "intent": "Register a pre-existing report artifact"
79
160
  }
80
161
  }
81
- ]
162
+ ],
163
+ "schema": {
164
+ "type": "object",
165
+ "properties": {
166
+ "artifacts": {
167
+ "type": "array",
168
+ "items": {
169
+ "type": [
170
+ "string",
171
+ "object"
172
+ ],
173
+ "properties": {
174
+ "path": {
175
+ "type": "string"
176
+ },
177
+ "type": {
178
+ "type": "string"
179
+ },
180
+ "label": {
181
+ "type": "string"
182
+ },
183
+ "nodeId": {
184
+ "type": "string"
185
+ },
186
+ "mimeType": {
187
+ "type": "string"
188
+ },
189
+ "category": {
190
+ "type": "string"
191
+ },
192
+ "proofTarget": {
193
+ "type": "string"
194
+ },
195
+ "covers": {
196
+ "type": "array",
197
+ "items": {
198
+ "type": "string"
199
+ }
200
+ }
201
+ },
202
+ "additionalProperties": false
203
+ }
204
+ }
205
+ },
206
+ "required": [
207
+ "artifacts"
208
+ ],
209
+ "additionalProperties": false
210
+ }
82
211
  },
83
212
  "end": {
84
213
  "description": "Base terminal action.",
@@ -102,30 +231,37 @@
102
231
  "home",
103
232
  "perps",
104
233
  "perps-market"
105
- ],
106
- "description": "Small stable page aliases; use raw route/hash when you need a route outside this list."
234
+ ]
107
235
  },
108
236
  "market": {
109
- "type": "string",
110
- "description": "Market symbol for page=perps-market, e.g. ETH."
237
+ "type": "string"
111
238
  },
112
239
  "symbol": {
113
- "type": "string",
114
- "description": "Alias for market."
240
+ "type": "string"
241
+ },
242
+ "url": {
243
+ "type": "string"
244
+ },
245
+ "hash": {
246
+ "type": "string"
247
+ },
248
+ "path": {
249
+ "type": "string"
115
250
  },
116
251
  "route": {
117
- "type": "string",
118
- "description": "Raw React Navigation route."
252
+ "type": "string"
119
253
  },
120
254
  "screen": {
121
- "type": "string",
122
- "description": "Alias for route."
255
+ "type": "string"
123
256
  },
124
257
  "params": {
125
- "type": "object",
126
- "description": "Raw React Navigation params."
258
+ "type": "object"
259
+ },
260
+ "timeout_ms": {
261
+ "type": "number"
127
262
  }
128
- }
263
+ },
264
+ "additionalProperties": false
129
265
  },
130
266
  "examples": [
131
267
  {
@@ -160,18 +296,33 @@
160
296
  "schema": {
161
297
  "type": "object",
162
298
  "properties": {
163
- "action": { "const": "ui.press" },
164
- "test_id": { "type": "string", "minLength": 1 },
165
- "testID": { "type": "string", "minLength": 1 },
166
- "selector": { "type": "string", "minLength": 1 },
167
- "text": { "type": "string", "minLength": 1 }
299
+ "selector": {
300
+ "type": "string"
301
+ },
302
+ "test_id": {
303
+ "type": "string"
304
+ },
305
+ "testID": {
306
+ "type": "string"
307
+ },
308
+ "text": {
309
+ "type": "string"
310
+ },
311
+ "label": {
312
+ "type": "string"
313
+ },
314
+ "timeout_ms": {
315
+ "type": "number"
316
+ },
317
+ "settle": {
318
+ "type": "boolean"
319
+ },
320
+ "long_press": {
321
+ "type": "boolean",
322
+ "description": "Invoke the target component's long-press gesture. Requires identifier targeting."
323
+ }
168
324
  },
169
- "oneOf": [
170
- { "required": ["test_id"] },
171
- { "required": ["testID"] },
172
- { "required": ["selector"] },
173
- { "required": ["text"] }
174
- ]
325
+ "additionalProperties": false
175
326
  },
176
327
  "examples": [
177
328
  {
@@ -189,6 +340,15 @@
189
340
  "text": "Ethereum",
190
341
  "intent": "Press the component containing the visible Ethereum label"
191
342
  }
343
+ },
344
+ {
345
+ "description": "Long-press a component by stable React Native testID",
346
+ "node": {
347
+ "action": "ui.press",
348
+ "test_id": "keypad-delete-button",
349
+ "long_press": true,
350
+ "intent": "Clear the current keypad value through its human reset gesture"
351
+ }
192
352
  }
193
353
  ]
194
354
  },
@@ -212,23 +372,94 @@
212
372
  }
213
373
  }
214
374
  ],
215
- "proof_effect": "E2E validation must include a passing ui-scroll-into-view trace event with output.intoView=true."
375
+ "proof_effect": "E2E validation must include a passing ui-scroll-into-view trace event with output.intoView=true.",
376
+ "schema": {
377
+ "type": "object",
378
+ "properties": {
379
+ "selector": {
380
+ "type": "string"
381
+ },
382
+ "test_id": {
383
+ "type": "string"
384
+ },
385
+ "testID": {
386
+ "type": "string"
387
+ },
388
+ "delta_x": {
389
+ "type": "number"
390
+ },
391
+ "delta_y": {
392
+ "type": "number"
393
+ },
394
+ "deltaX": {
395
+ "type": "number"
396
+ },
397
+ "deltaY": {
398
+ "type": "number"
399
+ },
400
+ "offset": {
401
+ "type": "number"
402
+ },
403
+ "scroll_into_view": {
404
+ "type": "boolean"
405
+ },
406
+ "into_view": {
407
+ "type": "boolean"
408
+ },
409
+ "animated": {
410
+ "type": "boolean"
411
+ },
412
+ "timeout_ms": {
413
+ "type": "number"
414
+ },
415
+ "settle": {
416
+ "type": "boolean"
417
+ }
418
+ },
419
+ "additionalProperties": false
420
+ }
216
421
  },
217
422
  "ui.wait_for": {
218
423
  "description": "Wait for a mounted React Native testID/test_id. Optional text validates text returned by that testID; text alone is not supported on Mobile.",
219
424
  "schema": {
220
425
  "type": "object",
221
426
  "properties": {
222
- "action": { "const": "ui.wait_for" },
223
- "test_id": { "type": "string" },
224
- "text": { "type": "string" },
427
+ "selector": {
428
+ "type": "string"
429
+ },
430
+ "test_id": {
431
+ "type": "string"
432
+ },
433
+ "testID": {
434
+ "type": "string"
435
+ },
436
+ "text": {
437
+ "type": "string"
438
+ },
439
+ "text_match": {
440
+ "type": "string"
441
+ },
442
+ "textMatch": {
443
+ "type": "string"
444
+ },
225
445
  "expected": {
226
446
  "type": "string",
227
- "enum": ["present", "absent", "hidden", "not_present"]
447
+ "enum": [
448
+ "present",
449
+ "visible",
450
+ "absent",
451
+ "hidden",
452
+ "not_present"
453
+ ]
454
+ },
455
+ "visible": {
456
+ "type": "boolean"
228
457
  },
229
- "timeout_ms": { "type": "number" }
458
+ "timeout_ms": {
459
+ "type": "number"
460
+ }
230
461
  },
231
- "required": ["action", "test_id"]
462
+ "additionalProperties": false
232
463
  },
233
464
  "examples": [
234
465
  {
@@ -236,7 +467,7 @@
236
467
  "action": "ui.wait_for",
237
468
  "test_id": "perps-home-heading",
238
469
  "expected": "present",
239
- "intent": "Wait for a React Native testID to be mounted"
470
+ "intent": "Confirm the requested mobile content is visible"
240
471
  }
241
472
  },
242
473
  {
@@ -244,7 +475,7 @@
244
475
  "action": "ui.wait_for",
245
476
  "test_id": "conditional-banner",
246
477
  "expected": "absent",
247
- "intent": "Prove a React Native testID is not mounted"
478
+ "intent": "Confirm the conditional mobile content is absent"
248
479
  }
249
480
  }
250
481
  ]
@@ -259,7 +490,25 @@
259
490
  "intent": "Capture visual evidence after a wait/assert step"
260
491
  }
261
492
  }
262
- ]
493
+ ],
494
+ "schema": {
495
+ "type": "object",
496
+ "properties": {
497
+ "path": {
498
+ "type": "string"
499
+ },
500
+ "label": {
501
+ "type": "string"
502
+ },
503
+ "category": {
504
+ "type": "string"
505
+ },
506
+ "timeout_ms": {
507
+ "type": "number"
508
+ }
509
+ },
510
+ "additionalProperties": false
511
+ }
263
512
  },
264
513
  "app.lifecycle": {
265
514
  "description": "Drive outer mobile app lifecycle for deterministic performance start states without rebuilding: foreground/launch, background, terminate, or restart the installed app.",
@@ -272,7 +521,101 @@
272
521
  "intent": "Send the selected Android device to the launcher before a warm-resume measurement"
273
522
  }
274
523
  }
275
- ]
524
+ ],
525
+ "schema": {
526
+ "type": "object",
527
+ "properties": {
528
+ "command": {
529
+ "type": "string",
530
+ "enum": [
531
+ "launch",
532
+ "foreground",
533
+ "background",
534
+ "terminate",
535
+ "restart"
536
+ ]
537
+ },
538
+ "event": {
539
+ "type": "string",
540
+ "enum": [
541
+ "launch",
542
+ "foreground",
543
+ "background",
544
+ "terminate",
545
+ "restart"
546
+ ]
547
+ },
548
+ "state": {
549
+ "type": "string",
550
+ "enum": [
551
+ "launch",
552
+ "foreground",
553
+ "background",
554
+ "terminate",
555
+ "restart"
556
+ ]
557
+ },
558
+ "settle_ms": {
559
+ "type": "integer"
560
+ },
561
+ "timeout_ms": {
562
+ "type": "integer"
563
+ },
564
+ "platform": {
565
+ "type": "string",
566
+ "enum": [
567
+ "ios",
568
+ "android"
569
+ ]
570
+ },
571
+ "watcher_port": {
572
+ "type": "number"
573
+ },
574
+ "metro_port": {
575
+ "type": "number"
576
+ },
577
+ "launch_url": {
578
+ "type": "string"
579
+ },
580
+ "launchUrl": {
581
+ "type": "string"
582
+ },
583
+ "url": {
584
+ "type": "string"
585
+ },
586
+ "adb_serial": {
587
+ "type": "string"
588
+ },
589
+ "android_device": {
590
+ "type": "string"
591
+ },
592
+ "device": {
593
+ "type": "string"
594
+ },
595
+ "package_id": {
596
+ "type": "string"
597
+ },
598
+ "packageName": {
599
+ "type": "string"
600
+ },
601
+ "app_id": {
602
+ "type": "string"
603
+ },
604
+ "simulator": {
605
+ "type": "string"
606
+ },
607
+ "ios_simulator": {
608
+ "type": "string"
609
+ },
610
+ "bundle_id": {
611
+ "type": "string"
612
+ },
613
+ "bundleId": {
614
+ "type": "string"
615
+ }
616
+ },
617
+ "additionalProperties": false
618
+ }
276
619
  },
277
620
  "assert_file": {
278
621
  "description": "Assert that a project file exists and optionally contains text.",
@@ -285,10 +628,25 @@
285
628
  "intent": "Assert that a project file exists and optionally contains text"
286
629
  }
287
630
  }
288
- ]
289
- },
290
- "assert_json": {
291
- "description": "Assert a JSON file value with a JSONPath-style selector.",
631
+ ],
632
+ "schema": {
633
+ "type": "object",
634
+ "properties": {
635
+ "path": {
636
+ "type": "string"
637
+ },
638
+ "contains": {
639
+ "type": "string"
640
+ }
641
+ },
642
+ "required": [
643
+ "path"
644
+ ],
645
+ "additionalProperties": false
646
+ }
647
+ },
648
+ "assert_json": {
649
+ "description": "Assert a JSON file value with a JSONPath-style selector.",
292
650
  "examples": [
293
651
  {
294
652
  "node": {
@@ -302,7 +660,69 @@
302
660
  "intent": "Assert a JSON file value with a JSONPath-style selector"
303
661
  }
304
662
  }
305
- ]
663
+ ],
664
+ "schema": {
665
+ "type": "object",
666
+ "properties": {
667
+ "path": {
668
+ "type": "string"
669
+ },
670
+ "assert": {
671
+ "type": "object",
672
+ "properties": {
673
+ "path": {
674
+ "type": "string"
675
+ },
676
+ "operator": {
677
+ "type": "string"
678
+ },
679
+ "value": {
680
+ "type": [
681
+ "string",
682
+ "number",
683
+ "integer",
684
+ "boolean",
685
+ "object",
686
+ "array"
687
+ ],
688
+ "items": {
689
+ "type": [
690
+ "string",
691
+ "number",
692
+ "integer",
693
+ "boolean",
694
+ "object"
695
+ ]
696
+ }
697
+ },
698
+ "all": {
699
+ "type": "array",
700
+ "items": {
701
+ "type": "object"
702
+ }
703
+ },
704
+ "any": {
705
+ "type": "array",
706
+ "items": {
707
+ "type": "object"
708
+ }
709
+ },
710
+ "none": {
711
+ "type": "array",
712
+ "items": {
713
+ "type": "object"
714
+ }
715
+ }
716
+ },
717
+ "additionalProperties": false
718
+ }
719
+ },
720
+ "required": [
721
+ "path",
722
+ "assert"
723
+ ],
724
+ "additionalProperties": false
725
+ }
306
726
  },
307
727
  "assert_exit_code": {
308
728
  "description": "Assert the exit code captured from a command node output.",
@@ -315,7 +735,22 @@
315
735
  "intent": "Assert the exit code captured from a command node output"
316
736
  }
317
737
  }
318
- ]
738
+ ],
739
+ "schema": {
740
+ "type": "object",
741
+ "properties": {
742
+ "source": {
743
+ "type": "string"
744
+ },
745
+ "node": {
746
+ "type": "string"
747
+ },
748
+ "expected": {
749
+ "type": "number"
750
+ }
751
+ },
752
+ "additionalProperties": false
753
+ }
319
754
  },
320
755
  "assert_output": {
321
756
  "description": "Assert stdout or stderr captured from a command node output.",
@@ -329,10 +764,80 @@
329
764
  "intent": "Assert stdout or stderr captured from a command node output"
330
765
  }
331
766
  }
332
- ]
767
+ ],
768
+ "schema": {
769
+ "type": "object",
770
+ "properties": {
771
+ "source": {
772
+ "type": "string"
773
+ },
774
+ "node": {
775
+ "type": "string"
776
+ },
777
+ "stream": {
778
+ "type": "string"
779
+ },
780
+ "contains": {
781
+ "type": "string"
782
+ },
783
+ "match": {
784
+ "type": "string"
785
+ },
786
+ "assert": {
787
+ "type": "object",
788
+ "properties": {
789
+ "path": {
790
+ "type": "string"
791
+ },
792
+ "operator": {
793
+ "type": "string"
794
+ },
795
+ "value": {
796
+ "type": [
797
+ "string",
798
+ "number",
799
+ "integer",
800
+ "boolean",
801
+ "object",
802
+ "array"
803
+ ],
804
+ "items": {
805
+ "type": [
806
+ "string",
807
+ "number",
808
+ "integer",
809
+ "boolean",
810
+ "object"
811
+ ]
812
+ }
813
+ },
814
+ "all": {
815
+ "type": "array",
816
+ "items": {
817
+ "type": "object"
818
+ }
819
+ },
820
+ "any": {
821
+ "type": "array",
822
+ "items": {
823
+ "type": "object"
824
+ }
825
+ },
826
+ "none": {
827
+ "type": "array",
828
+ "items": {
829
+ "type": "object"
830
+ }
831
+ }
832
+ },
833
+ "additionalProperties": false
834
+ }
835
+ },
836
+ "additionalProperties": false
837
+ }
333
838
  },
334
839
  "app.hud": {
335
- "description": "Display minimal human-readable recipe progress. The intent must explain the current agent goal; debug metadata and flow/domain labels are hidden unless explicitly opted in.",
840
+ "description": "Display minimal human-readable recipe progress. The intent must explain the current agent goal; debug metadata and grouping labels are hidden unless explicitly opted in.",
336
841
  "examples": [
337
842
  {
338
843
  "description": "Default one-intent progress HUD",
@@ -351,6 +856,12 @@
351
856
  "schema": {
352
857
  "type": "object",
353
858
  "properties": {
859
+ "clear": {
860
+ "type": "boolean"
861
+ },
862
+ "title": {
863
+ "type": "string"
864
+ },
354
865
  "status": {
355
866
  "type": "string",
356
867
  "enum": [
@@ -359,454 +870,176 @@
359
870
  "fail"
360
871
  ]
361
872
  },
362
- "intent": {
873
+ "node_id": {
363
874
  "type": "string"
364
875
  },
365
- "progress": {
366
- "type": "object",
367
- "properties": {
368
- "current": {
369
- "type": "number"
370
- },
371
- "total": {
372
- "type": "number"
373
- }
374
- },
375
- "required": [
376
- "current",
377
- "total"
378
- ]
379
- },
380
- "detail": {
876
+ "nodeId": {
381
877
  "type": "string"
382
878
  },
383
- "error": {
879
+ "phase": {
384
880
  "type": "string"
385
881
  },
386
- "display": {
387
- "type": "object",
388
- "properties": {
389
- "showDetail": {
390
- "type": "boolean"
391
- },
392
- "showDebug": {
393
- "type": "boolean"
394
- },
395
- "showTitle": {
396
- "type": "boolean"
397
- },
398
- "layout": {
399
- "type": "string"
400
- },
401
- "position": {
402
- "type": "string"
403
- }
404
- }
405
- },
406
- "clear": {
407
- "type": "boolean"
408
- }
409
- }
410
- }
411
- },
412
- "ui.set_input": {
413
- "description": "Set text on a React Native TextInput by testID through the app control bridge.",
414
- "schema": {
415
- "type": "object",
416
- "properties": {
417
- "test_id": {
882
+ "action_name": {
418
883
  "type": "string"
419
884
  },
420
- "testID": {
421
- "type": "string"
422
- },
423
- "value": {
885
+ "recipe_action": {
424
886
  "type": "string"
425
887
  },
426
888
  "text": {
427
889
  "type": "string"
428
890
  },
429
- "next": {
430
- "type": "string"
431
- }
432
- },
433
- "additionalProperties": true
434
- },
435
- "examples": [
436
- {
437
- "node": {
438
- "action": "ui.set_input",
439
- "test_id": "network-selector-network-search-input",
440
- "value": "Sepolia",
441
- "intent": "Set text on a React Native TextInput by testID through the app control bridge"
442
- }
443
- }
444
- ]
445
- },
446
- "ui.key_press": {
447
- "description": "Send a trusted keyboard press to the currently focused UI element through the UI transport.",
448
- "schema": {
449
- "type": "object",
450
- "properties": {
451
- "key": {
891
+ "detail": {
452
892
  "type": "string"
453
893
  },
454
- "next": {
894
+ "error": {
455
895
  "type": "string"
896
+ },
897
+ "display": {
898
+ "type": "object"
899
+ },
900
+ "progress": {
901
+ "type": "object"
456
902
  }
457
- },
458
- "required": [
459
- "key"
460
- ],
461
- "additionalProperties": true
462
- },
463
- "examples": [
464
- {
465
- "node": {
466
- "action": "ui.key_press",
467
- "key": "Enter",
468
- "intent": "Send a trusted Enter key press to the focused UI element"
469
- }
470
- }
471
- ]
472
- },
473
- "app.status": {
474
- "description": "Report the adapter's static status — platform, project root, resolved checkout shape, and headless compatibility mode (no live route or account).",
475
- "examples": [
476
- {
477
- "node": {
478
- "action": "app.status",
479
- "intent": "Report the resolved checkout status"
480
- }
481
- }
482
- ]
483
- },
484
- "cdp.target": {
485
- "description": "Probe the platform debug transport — Chrome CDP for extension, the React Native debug bridge for mobile — and report whether it is reachable.",
486
- "examples": [
487
- {
488
- "node": {
489
- "action": "cdp.target",
490
- "require_reachable": true,
491
- "intent": "Confirm the mobile React Native debug bridge is reachable"
492
- }
493
- }
494
- ]
495
- },
496
- "metamask.wallet.fixture_status": {
497
- "description": "Report the on-disk wallet fixture status (accounts and password presence) without launching or mutating the app.",
498
- "examples": [
499
- {
500
- "description": "Minimal node",
501
- "node": {
502
- "action": "metamask.wallet.fixture_status",
503
- "intent": "Check wallet fixture status"
504
- }
505
- }
506
- ]
507
- },
508
- "metamask.wallet.setup": {
509
- "description": "Import the wallet fixture accounts and password so the app starts from a known signed-in state.",
510
- "examples": [
511
- {
512
- "description": "Minimal node",
513
- "node": {
514
- "action": "metamask.wallet.setup",
515
- "intent": "Prepare the wallet fixture for recipe execution"
516
- }
517
- }
518
- ]
519
- },
520
- "metamask.wallet.ensure_unlocked": {
521
- "description": "Unlock the wallet with the fixture password when it is currently locked.",
522
- "examples": [
523
- {
524
- "description": "Minimal node",
525
- "node": {
526
- "action": "metamask.wallet.ensure_unlocked",
527
- "intent": "Ensure the wallet is unlocked before proof steps"
528
- }
529
- }
530
- ]
531
- },
532
- "metamask.wallet.select_account": {
533
- "description": "Select a wallet account by address, id, or display name.",
534
- "examples": [
535
- {
536
- "description": "Select by deterministic fixture address",
537
- "node": {
538
- "action": "metamask.wallet.select_account",
539
- "address": "0x8dc623e964475d4d669da601fd15ea9125469003",
540
- "intent": "Select the requested wallet account"
541
- }
542
- },
543
- {
544
- "description": "Select by fixture display name",
545
- "node": {
546
- "action": "metamask.wallet.select_account",
547
- "name": "dev1",
548
- "intent": "Select the requested wallet account"
549
- }
550
- }
551
- ]
552
- },
553
- "metamask.wallet.list_accounts": {
554
- "description": "List redacted wallet accounts and identify the selected account without mutating the app.",
555
- "examples": [
556
- {
557
- "description": "List wallet accounts",
558
- "node": {
559
- "action": "metamask.wallet.list_accounts",
560
- "intent": "Discover the available wallet accounts"
561
- }
562
- }
563
- ]
564
- },
565
- "metamask.wallet.read_state": {
566
- "description": "Read the redacted wallet state (selected account, route, and device) from the running app.",
567
- "examples": [
568
- {
569
- "description": "Minimal node",
570
- "node": {
571
- "action": "metamask.wallet.read_state",
572
- "intent": "Read wallet state needed for the proof"
573
- }
574
- }
575
- ]
576
- },
577
- "metamask.perps.read_positions": {
578
- "description": "Read live Perps positions and return the selected subset.",
579
- "examples": [
580
- {
581
- "description": "Read selected live state",
582
- "node": {
583
- "action": "metamask.perps.read_positions",
584
- "market": "BTC",
585
- "intent": "Read Perps positions needed for the proof"
586
- }
587
- }
588
- ]
589
- },
590
- "metamask.perps.read_orders": {
591
- "description": "Read live Perps open orders and return the selected subset.",
592
- "examples": [
593
- {
594
- "description": "Read selected live state",
595
- "node": {
596
- "action": "metamask.perps.read_orders",
597
- "market": "BTC",
598
- "intent": "Read Perps open orders needed for the proof"
599
- }
600
- }
601
- ]
602
- },
603
- "metamask.perps.close_positions": {
604
- "description": "Default to testnet for Perps mutations; mainnet is read-only unless explicitly requested. Close selected live Perps positions. Use selector/mode params to close matching symbols or all positions. The network field in examples is an authoring convention: only start_state/teardown_state enforce the network (ensureNetwork); the mutation handlers themselves do not read it.",
605
- "examples": [
606
- {
607
- "description": "Close every open position",
608
- "node": {
609
- "action": "metamask.perps.close_positions",
610
- "mode": "all",
611
- "intent": "Close selected Perps positions before continuing",
612
- "network": "testnet"
613
- }
614
- },
615
- {
616
- "description": "Close selected BTC/ETH positions",
617
- "node": {
618
- "action": "metamask.perps.close_positions",
619
- "markets": [
620
- "BTC",
621
- "ETH"
622
- ],
623
- "intent": "Close selected Perps positions before continuing",
624
- "network": "testnet"
625
- }
626
- }
627
- ]
628
- },
629
- "metamask.perps.close_orders": {
630
- "description": "Default to testnet for Perps mutations; mainnet is read-only unless explicitly requested. Cancel selected live Perps open orders. Use selector/mode params to cancel matching symbols or all orders. The network field in examples is an authoring convention: only start_state/teardown_state enforce the network (ensureNetwork); the mutation handlers themselves do not read it.",
631
- "examples": [
632
- {
633
- "description": "Cancel every open order",
634
- "node": {
635
- "action": "metamask.perps.close_orders",
636
- "mode": "all",
637
- "intent": "Cancel selected Perps orders before continuing",
638
- "network": "testnet"
639
- }
640
- },
641
- {
642
- "description": "Cancel BTC orders only",
643
- "node": {
644
- "action": "metamask.perps.close_orders",
645
- "market": "BTC",
646
- "intent": "Cancel selected Perps orders before continuing",
647
- "network": "testnet"
648
- }
649
- }
650
- ]
651
- },
652
- "metamask.perps.place_order": {
653
- "description": "Place a Perps order through a supported app/API path. Default to testnet; mainnet order placement requires an explicit user request. The network field in examples is an authoring convention: only start_state/teardown_state enforce the network (ensureNetwork); the mutation handlers themselves do not read it.",
654
- "examples": [
655
- {
656
- "description": "Place BTC market order",
657
- "node": {
658
- "action": "metamask.perps.place_order",
659
- "market": "BTC",
660
- "side": "long",
661
- "notional": "10",
662
- "leverage": 2,
663
- "intent": "Place the requested Perps order",
664
- "network": "testnet"
665
- }
666
- }
667
- ]
903
+ },
904
+ "additionalProperties": false
905
+ }
668
906
  },
669
- "metamask.perps.assert_positions": {
670
- "description": "Assert selected live Perps positions are present or absent.",
671
- "examples": [
672
- {
673
- "description": "Assert BTC position is open",
674
- "node": {
675
- "action": "metamask.perps.assert_positions",
676
- "state": "open",
677
- "market": "BTC",
678
- "intent": "Assert the expected Perps position state"
907
+ "ui.set_input": {
908
+ "description": "Set text on a React Native TextInput by testID through the app control bridge.",
909
+ "schema": {
910
+ "type": "object",
911
+ "properties": {
912
+ "selector": {
913
+ "type": "string"
914
+ },
915
+ "test_id": {
916
+ "type": "string"
917
+ },
918
+ "testID": {
919
+ "type": "string"
920
+ },
921
+ "value": {
922
+ "type": "string"
923
+ },
924
+ "text": {
925
+ "type": "string"
926
+ },
927
+ "timeout_ms": {
928
+ "type": "number"
929
+ },
930
+ "settle": {
931
+ "type": "boolean"
679
932
  }
680
- }
681
- ]
682
- },
683
- "metamask.perps.assert_orders": {
684
- "description": "Assert selected live Perps open orders are present or absent.",
933
+ },
934
+ "additionalProperties": false
935
+ },
685
936
  "examples": [
686
937
  {
687
- "description": "Assert no BTC open orders",
688
938
  "node": {
689
- "action": "metamask.perps.assert_orders",
690
- "state": "none",
691
- "market": "BTC",
692
- "intent": "Assert the expected Perps open order state"
939
+ "action": "ui.set_input",
940
+ "test_id": "network-selector-network-search-input",
941
+ "value": "Sepolia",
942
+ "intent": "Set text on a React Native TextInput by testID through the app control bridge"
693
943
  }
694
944
  }
695
945
  ]
696
946
  },
697
- "metamask.perps.ensure_positions": {
698
- "description": "Default to testnet for Perps mutations; mainnet is read-only unless explicitly requested. Higher-level wrapper that reads selected positions, closes or places when needed, then asserts final position state. The network field in examples is an authoring convention: only start_state/teardown_state enforce the network (ensureNetwork); the mutation handlers themselves do not read it.",
699
- "examples": [
700
- {
701
- "description": "Ensure no BTC position before proof window",
702
- "node": {
703
- "action": "metamask.perps.ensure_positions",
704
- "state": "none",
705
- "market": "BTC",
706
- "intent": "Converge Perps positions to the requested state",
707
- "network": "testnet"
947
+ "ui.key_press": {
948
+ "description": "Send a trusted keyboard press to the currently focused UI element through the UI transport.",
949
+ "schema": {
950
+ "type": "object",
951
+ "properties": {
952
+ "key": {
953
+ "type": "string"
954
+ },
955
+ "timeout_ms": {
956
+ "type": "number"
957
+ },
958
+ "settle": {
959
+ "type": "boolean"
708
960
  }
709
961
  },
962
+ "required": [
963
+ "key"
964
+ ],
965
+ "additionalProperties": false
966
+ },
967
+ "examples": [
710
968
  {
711
- "description": "Ensure any existing positions are closed",
712
969
  "node": {
713
- "action": "metamask.perps.ensure_positions",
714
- "state": "none",
715
- "mode": "all",
716
- "intent": "Converge Perps positions to the requested state",
717
- "network": "testnet"
970
+ "action": "ui.key_press",
971
+ "key": "Enter",
972
+ "intent": "Send a trusted Enter key press to the focused UI element"
718
973
  }
719
974
  }
720
975
  ]
721
976
  },
722
- "metamask.perps.ensure_orders": {
723
- "description": "Default to testnet for Perps mutations; mainnet is read-only unless explicitly requested. Higher-level wrapper that reads selected orders, cancels when needed, then asserts final order state. The network field in examples is an authoring convention: only start_state/teardown_state enforce the network (ensureNetwork); the mutation handlers themselves do not read it.",
977
+ "app.status": {
978
+ "description": "Report the adapter's static status platform, project root, resolved checkout shape, and headless compatibility mode (no live route or account).",
724
979
  "examples": [
725
980
  {
726
- "description": "Ensure no open orders before proof window",
727
981
  "node": {
728
- "action": "metamask.perps.ensure_orders",
729
- "state": "none",
730
- "mode": "all",
731
- "intent": "Converge Perps orders to the requested state",
732
- "network": "testnet"
982
+ "action": "app.status",
983
+ "intent": "Report the resolved checkout status"
733
984
  }
734
985
  }
735
- ]
986
+ ],
987
+ "schema": {
988
+ "type": "object",
989
+ "properties": {},
990
+ "additionalProperties": false
991
+ }
736
992
  },
737
- "metamask.perps.start_state": {
738
- "description": "Default to testnet for Perps mutations; mainnet is read-only unless explicitly requested. Recommended configurable Perps start state that first restores the fixture-backed unlocked wallet, then composes reusable domain operations before the proof window.",
993
+ "cdp.target": {
994
+ "description": "Probe the platform debug transport Chrome CDP for extension, the React Native debug bridge for mobile and report whether it is reachable.",
739
995
  "examples": [
740
996
  {
741
- "description": "Clean BTC testnet market state",
742
- "node": {
743
- "action": "metamask.perps.start_state",
744
- "profile": "clean_market_testnet",
745
- "market": "BTC",
746
- "positions": {
747
- "state": "none"
748
- },
749
- "orders": {
750
- "state": "none"
751
- },
752
- "intent": "Prepare the Perps start state for the proof",
753
- "network": "testnet"
754
- }
755
- },
756
- {
757
- "description": "Start with an open ETH long position",
758
997
  "node": {
759
- "action": "metamask.perps.start_state",
760
- "profile": "open_position_testnet",
761
- "market": "ETH",
762
- "side": "long",
763
- "positions": {
764
- "state": "open",
765
- "notional": "10",
766
- "leverage": 2
767
- },
768
- "intent": "Prepare the Perps start state for the proof",
769
- "network": "testnet"
998
+ "action": "cdp.target",
999
+ "require_reachable": true,
1000
+ "intent": "Confirm the mobile React Native debug bridge is reachable"
770
1001
  }
771
1002
  }
772
- ]
773
- },
774
- "metamask.perps.teardown_state": {
775
- "description": "Recommended configurable Perps teardown state that restores the selected environment after proof.",
776
- "examples": [
777
- {
778
- "description": "Return BTC test account to clean state",
779
- "node": {
780
- "action": "metamask.perps.teardown_state",
781
- "market": "BTC",
782
- "positions": {
783
- "state": "none"
784
- },
785
- "orders": {
786
- "state": "none"
787
- },
788
- "page": "home",
789
- "intent": "Restore Perps state after the proof"
1003
+ ],
1004
+ "schema": {
1005
+ "type": "object",
1006
+ "properties": {
1007
+ "cdp_port": {
1008
+ "type": "number"
1009
+ },
1010
+ "metro_port": {
1011
+ "type": "number"
1012
+ },
1013
+ "watcher_port": {
1014
+ "type": "number"
1015
+ },
1016
+ "probe_timeout_ms": {
1017
+ "type": "number"
1018
+ },
1019
+ "cdp_timeout_ms": {
1020
+ "type": "number"
1021
+ },
1022
+ "timeout_ms": {
1023
+ "type": "number"
1024
+ },
1025
+ "require_reachable": {
1026
+ "type": "boolean"
1027
+ },
1028
+ "required": {
1029
+ "type": "boolean"
790
1030
  }
791
1031
  },
792
- {
793
- "description": "Teardown only state created by selected market",
794
- "node": {
795
- "action": "metamask.perps.teardown_state",
796
- "market": "BTC",
797
- "intent": "Restore Perps state after the proof"
798
- }
799
- }
800
- ]
1032
+ "additionalProperties": false
1033
+ }
801
1034
  },
802
1035
  "call": {
803
- "description": "Invoke a named sub-recipe flow by ref and run its steps inline.",
1036
+ "description": "Invoke a named recipe by ref and run its steps inline.",
804
1037
  "examples": [
805
1038
  {
806
1039
  "node": {
807
1040
  "action": "call",
808
1041
  "ref": "mydev.open_perps_setup",
809
- "intent": "Run a personal Perps setup flow segment"
1042
+ "intent": "Run a personal Perps setup recipe"
810
1043
  }
811
1044
  }
812
1045
  ]
@@ -819,14 +1052,8 @@
819
1052
  "description": "Report the on-disk wallet fixture status (accounts and password presence) without launching or mutating the app.",
820
1053
  "schema": {
821
1054
  "type": "object",
822
- "properties": {
823
- "action": {
824
- "const": "metamask.wallet.fixture_status"
825
- }
826
- },
827
- "required": [
828
- "action"
829
- ]
1055
+ "properties": {},
1056
+ "additionalProperties": false
830
1057
  },
831
1058
  "examples": [
832
1059
  {
@@ -838,7 +1065,8 @@
838
1065
  }
839
1066
  ],
840
1067
  "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
841
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof."
1068
+ "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof.",
1069
+ "execution_capabilities": []
842
1070
  },
843
1071
  {
844
1072
  "name": "metamask.wallet.setup",
@@ -846,14 +1074,8 @@
846
1074
  "description": "Import the wallet fixture accounts and password so the app starts from a known signed-in state.",
847
1075
  "schema": {
848
1076
  "type": "object",
849
- "properties": {
850
- "action": {
851
- "const": "metamask.wallet.setup"
852
- }
853
- },
854
- "required": [
855
- "action"
856
- ]
1077
+ "properties": {},
1078
+ "additionalProperties": false
857
1079
  },
858
1080
  "examples": [
859
1081
  {
@@ -865,7 +1087,10 @@
865
1087
  }
866
1088
  ],
867
1089
  "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
868
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof."
1090
+ "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof.",
1091
+ "execution_capabilities": [
1092
+ "app-mutation"
1093
+ ]
869
1094
  },
870
1095
  {
871
1096
  "name": "metamask.wallet.ensure_unlocked",
@@ -874,13 +1099,14 @@
874
1099
  "schema": {
875
1100
  "type": "object",
876
1101
  "properties": {
877
- "action": {
878
- "const": "metamask.wallet.ensure_unlocked"
1102
+ "target_timeout_ms": {
1103
+ "type": "number"
1104
+ },
1105
+ "unlock_timeout_ms": {
1106
+ "type": "number"
879
1107
  }
880
1108
  },
881
- "required": [
882
- "action"
883
- ]
1109
+ "additionalProperties": false
884
1110
  },
885
1111
  "examples": [
886
1112
  {
@@ -892,7 +1118,10 @@
892
1118
  }
893
1119
  ],
894
1120
  "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
895
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof."
1121
+ "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof.",
1122
+ "execution_capabilities": [
1123
+ "app-mutation"
1124
+ ]
896
1125
  },
897
1126
  {
898
1127
  "name": "metamask.wallet.select_account",
@@ -901,9 +1130,6 @@
901
1130
  "schema": {
902
1131
  "type": "object",
903
1132
  "properties": {
904
- "action": {
905
- "const": "metamask.wallet.select_account"
906
- },
907
1133
  "address": {
908
1134
  "type": "string",
909
1135
  "description": "EVM address of the account to select."
@@ -917,26 +1143,7 @@
917
1143
  "description": "Display name of the account to select."
918
1144
  }
919
1145
  },
920
- "required": [
921
- "action"
922
- ],
923
- "anyOf": [
924
- {
925
- "required": [
926
- "address"
927
- ]
928
- },
929
- {
930
- "required": [
931
- "id"
932
- ]
933
- },
934
- {
935
- "required": [
936
- "name"
937
- ]
938
- }
939
- ]
1146
+ "additionalProperties": false
940
1147
  },
941
1148
  "examples": [
942
1149
  {
@@ -957,7 +1164,10 @@
957
1164
  }
958
1165
  ],
959
1166
  "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
960
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof."
1167
+ "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof.",
1168
+ "execution_capabilities": [
1169
+ "app-mutation"
1170
+ ]
961
1171
  },
962
1172
  {
963
1173
  "name": "metamask.wallet.list_accounts",
@@ -966,11 +1176,9 @@
966
1176
  "schema": {
967
1177
  "type": "object",
968
1178
  "properties": {
969
- "action": {
970
- "const": "metamask.wallet.list_accounts"
971
- },
972
1179
  "scope": {
973
1180
  "type": "string",
1181
+ "default": "named",
974
1182
  "enum": [
975
1183
  "selected",
976
1184
  "named",
@@ -980,9 +1188,7 @@
980
1188
  "description": "Account scope. Defaults to named human-visible accounts; use selected for the active account, evm for EVM accounts, or all for the full inventory."
981
1189
  }
982
1190
  },
983
- "required": [
984
- "action"
985
- ]
1191
+ "additionalProperties": false
986
1192
  },
987
1193
  "examples": [
988
1194
  {
@@ -994,7 +1200,8 @@
994
1200
  }
995
1201
  ],
996
1202
  "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
997
- "safety_notes": "Read-only account metadata; must never expose keys, mnemonics, vault data, or credentials."
1203
+ "safety_notes": "Read-only account metadata; must never expose keys, mnemonics, vault data, or credentials.",
1204
+ "execution_capabilities": []
998
1205
  },
999
1206
  {
1000
1207
  "name": "metamask.wallet.read_state",
@@ -1002,14 +1209,8 @@
1002
1209
  "description": "Read the redacted wallet state (selected account, route, and device) from the running app.",
1003
1210
  "schema": {
1004
1211
  "type": "object",
1005
- "properties": {
1006
- "action": {
1007
- "const": "metamask.wallet.read_state"
1008
- }
1009
- },
1010
- "required": [
1011
- "action"
1012
- ]
1212
+ "properties": {},
1213
+ "additionalProperties": false
1013
1214
  },
1014
1215
  "examples": [
1015
1216
  {
@@ -1021,18 +1222,16 @@
1021
1222
  }
1022
1223
  ],
1023
1224
  "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
1024
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof."
1225
+ "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof.",
1226
+ "execution_capabilities": []
1025
1227
  },
1026
1228
  {
1027
1229
  "name": "metamask.perps.read_positions",
1028
1230
  "owner": "metamask",
1029
- "description": "mobile Read live Perps positions and return the selected subset.",
1231
+ "description": "mobile Read live Perps positions; without a selector, return all live positions.",
1030
1232
  "schema": {
1031
1233
  "type": "object",
1032
1234
  "properties": {
1033
- "action": {
1034
- "const": "metamask.perps.read_positions"
1035
- },
1036
1235
  "market": {
1037
1236
  "type": "string",
1038
1237
  "description": "Single market symbol, e.g. BTC or ETH. Alias: symbol."
@@ -1065,7 +1264,41 @@
1065
1264
  },
1066
1265
  "selector": {
1067
1266
  "type": "object",
1068
- "description": "Optional structured selector with mode, symbols/markets, and side."
1267
+ "properties": {
1268
+ "mode": {
1269
+ "type": "string",
1270
+ "enum": [
1271
+ "matching",
1272
+ "all"
1273
+ ]
1274
+ },
1275
+ "market": {
1276
+ "type": "string"
1277
+ },
1278
+ "symbol": {
1279
+ "type": "string"
1280
+ },
1281
+ "markets": {
1282
+ "type": "array",
1283
+ "items": {
1284
+ "type": "string"
1285
+ }
1286
+ },
1287
+ "symbols": {
1288
+ "type": "array",
1289
+ "items": {
1290
+ "type": "string"
1291
+ }
1292
+ },
1293
+ "side": {
1294
+ "type": "string",
1295
+ "enum": [
1296
+ "long",
1297
+ "short"
1298
+ ]
1299
+ }
1300
+ },
1301
+ "additionalProperties": false
1069
1302
  },
1070
1303
  "side": {
1071
1304
  "type": "string",
@@ -1079,9 +1312,7 @@
1079
1312
  "type": "number"
1080
1313
  }
1081
1314
  },
1082
- "required": [
1083
- "action"
1084
- ]
1315
+ "additionalProperties": false
1085
1316
  },
1086
1317
  "examples": [
1087
1318
  {
@@ -1094,18 +1325,16 @@
1094
1325
  }
1095
1326
  ],
1096
1327
  "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
1097
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof."
1328
+ "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof.",
1329
+ "execution_capabilities": []
1098
1330
  },
1099
1331
  {
1100
1332
  "name": "metamask.perps.read_orders",
1101
1333
  "owner": "metamask",
1102
- "description": "mobile Read live Perps open orders and return the selected subset.",
1334
+ "description": "mobile Read live Perps open orders; without a selector, return all live orders.",
1103
1335
  "schema": {
1104
1336
  "type": "object",
1105
1337
  "properties": {
1106
- "action": {
1107
- "const": "metamask.perps.read_orders"
1108
- },
1109
1338
  "market": {
1110
1339
  "type": "string",
1111
1340
  "description": "Single market symbol, e.g. BTC or ETH. Alias: symbol."
@@ -1138,7 +1367,41 @@
1138
1367
  },
1139
1368
  "selector": {
1140
1369
  "type": "object",
1141
- "description": "Optional structured selector with mode, symbols/markets, and side."
1370
+ "properties": {
1371
+ "mode": {
1372
+ "type": "string",
1373
+ "enum": [
1374
+ "matching",
1375
+ "all"
1376
+ ]
1377
+ },
1378
+ "market": {
1379
+ "type": "string"
1380
+ },
1381
+ "symbol": {
1382
+ "type": "string"
1383
+ },
1384
+ "markets": {
1385
+ "type": "array",
1386
+ "items": {
1387
+ "type": "string"
1388
+ }
1389
+ },
1390
+ "symbols": {
1391
+ "type": "array",
1392
+ "items": {
1393
+ "type": "string"
1394
+ }
1395
+ },
1396
+ "side": {
1397
+ "type": "string",
1398
+ "enum": [
1399
+ "long",
1400
+ "short"
1401
+ ]
1402
+ }
1403
+ },
1404
+ "additionalProperties": false
1142
1405
  },
1143
1406
  "side": {
1144
1407
  "type": "string",
@@ -1152,9 +1415,7 @@
1152
1415
  "type": "number"
1153
1416
  }
1154
1417
  },
1155
- "required": [
1156
- "action"
1157
- ]
1418
+ "additionalProperties": false
1158
1419
  },
1159
1420
  "examples": [
1160
1421
  {
@@ -1167,7 +1428,8 @@
1167
1428
  }
1168
1429
  ],
1169
1430
  "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
1170
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof."
1431
+ "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof.",
1432
+ "execution_capabilities": []
1171
1433
  },
1172
1434
  {
1173
1435
  "name": "metamask.perps.close_positions",
@@ -1176,9 +1438,6 @@
1176
1438
  "schema": {
1177
1439
  "type": "object",
1178
1440
  "properties": {
1179
- "action": {
1180
- "const": "metamask.perps.close_positions"
1181
- },
1182
1441
  "market": {
1183
1442
  "type": "string",
1184
1443
  "description": "Single market symbol, e.g. BTC or ETH. Alias: symbol."
@@ -1211,7 +1470,41 @@
1211
1470
  },
1212
1471
  "selector": {
1213
1472
  "type": "object",
1214
- "description": "Optional structured selector with mode, symbols/markets, and side."
1473
+ "properties": {
1474
+ "mode": {
1475
+ "type": "string",
1476
+ "enum": [
1477
+ "matching",
1478
+ "all"
1479
+ ]
1480
+ },
1481
+ "market": {
1482
+ "type": "string"
1483
+ },
1484
+ "symbol": {
1485
+ "type": "string"
1486
+ },
1487
+ "markets": {
1488
+ "type": "array",
1489
+ "items": {
1490
+ "type": "string"
1491
+ }
1492
+ },
1493
+ "symbols": {
1494
+ "type": "array",
1495
+ "items": {
1496
+ "type": "string"
1497
+ }
1498
+ },
1499
+ "side": {
1500
+ "type": "string",
1501
+ "enum": [
1502
+ "long",
1503
+ "short"
1504
+ ]
1505
+ }
1506
+ },
1507
+ "additionalProperties": false
1215
1508
  },
1216
1509
  "side": {
1217
1510
  "type": "string",
@@ -1225,9 +1518,7 @@
1225
1518
  "type": "number"
1226
1519
  }
1227
1520
  },
1228
- "required": [
1229
- "action"
1230
- ]
1521
+ "additionalProperties": false
1231
1522
  },
1232
1523
  "examples": [
1233
1524
  {
@@ -1235,8 +1526,7 @@
1235
1526
  "node": {
1236
1527
  "action": "metamask.perps.close_positions",
1237
1528
  "mode": "all",
1238
- "intent": "Close selected Perps positions before continuing",
1239
- "network": "testnet"
1529
+ "intent": "Close selected Perps positions before continuing"
1240
1530
  }
1241
1531
  },
1242
1532
  {
@@ -1247,13 +1537,16 @@
1247
1537
  "BTC",
1248
1538
  "ETH"
1249
1539
  ],
1250
- "intent": "Close selected Perps positions before continuing",
1251
- "network": "testnet"
1540
+ "intent": "Close selected Perps positions before continuing"
1252
1541
  }
1253
1542
  }
1254
1543
  ],
1255
1544
  "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
1256
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof."
1545
+ "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof.",
1546
+ "execution_capabilities": [
1547
+ "app-mutation",
1548
+ "external-mutation"
1549
+ ]
1257
1550
  },
1258
1551
  {
1259
1552
  "name": "metamask.perps.close_orders",
@@ -1262,9 +1555,6 @@
1262
1555
  "schema": {
1263
1556
  "type": "object",
1264
1557
  "properties": {
1265
- "action": {
1266
- "const": "metamask.perps.close_orders"
1267
- },
1268
1558
  "market": {
1269
1559
  "type": "string",
1270
1560
  "description": "Single market symbol, e.g. BTC or ETH. Alias: symbol."
@@ -1297,7 +1587,41 @@
1297
1587
  },
1298
1588
  "selector": {
1299
1589
  "type": "object",
1300
- "description": "Optional structured selector with mode, symbols/markets, and side."
1590
+ "properties": {
1591
+ "mode": {
1592
+ "type": "string",
1593
+ "enum": [
1594
+ "matching",
1595
+ "all"
1596
+ ]
1597
+ },
1598
+ "market": {
1599
+ "type": "string"
1600
+ },
1601
+ "symbol": {
1602
+ "type": "string"
1603
+ },
1604
+ "markets": {
1605
+ "type": "array",
1606
+ "items": {
1607
+ "type": "string"
1608
+ }
1609
+ },
1610
+ "symbols": {
1611
+ "type": "array",
1612
+ "items": {
1613
+ "type": "string"
1614
+ }
1615
+ },
1616
+ "side": {
1617
+ "type": "string",
1618
+ "enum": [
1619
+ "long",
1620
+ "short"
1621
+ ]
1622
+ }
1623
+ },
1624
+ "additionalProperties": false
1301
1625
  },
1302
1626
  "side": {
1303
1627
  "type": "string",
@@ -1311,9 +1635,7 @@
1311
1635
  "type": "number"
1312
1636
  }
1313
1637
  },
1314
- "required": [
1315
- "action"
1316
- ]
1638
+ "additionalProperties": false
1317
1639
  },
1318
1640
  "examples": [
1319
1641
  {
@@ -1321,8 +1643,7 @@
1321
1643
  "node": {
1322
1644
  "action": "metamask.perps.close_orders",
1323
1645
  "mode": "all",
1324
- "intent": "Cancel selected Perps orders before continuing",
1325
- "network": "testnet"
1646
+ "intent": "Cancel selected Perps orders before continuing"
1326
1647
  }
1327
1648
  },
1328
1649
  {
@@ -1330,13 +1651,16 @@
1330
1651
  "node": {
1331
1652
  "action": "metamask.perps.close_orders",
1332
1653
  "market": "BTC",
1333
- "intent": "Cancel selected Perps orders before continuing",
1334
- "network": "testnet"
1654
+ "intent": "Cancel selected Perps orders before continuing"
1335
1655
  }
1336
1656
  }
1337
1657
  ],
1338
1658
  "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
1339
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof."
1659
+ "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof.",
1660
+ "execution_capabilities": [
1661
+ "app-mutation",
1662
+ "external-mutation"
1663
+ ]
1340
1664
  },
1341
1665
  {
1342
1666
  "name": "metamask.perps.place_order",
@@ -1345,9 +1669,6 @@
1345
1669
  "schema": {
1346
1670
  "type": "object",
1347
1671
  "properties": {
1348
- "action": {
1349
- "const": "metamask.perps.place_order"
1350
- },
1351
1672
  "market": {
1352
1673
  "type": "string",
1353
1674
  "description": "Single market symbol, e.g. BTC or ETH. Alias: symbol."
@@ -1380,7 +1701,41 @@
1380
1701
  },
1381
1702
  "selector": {
1382
1703
  "type": "object",
1383
- "description": "Optional structured selector with mode, symbols/markets, and side."
1704
+ "properties": {
1705
+ "mode": {
1706
+ "type": "string",
1707
+ "enum": [
1708
+ "matching",
1709
+ "all"
1710
+ ]
1711
+ },
1712
+ "market": {
1713
+ "type": "string"
1714
+ },
1715
+ "symbol": {
1716
+ "type": "string"
1717
+ },
1718
+ "markets": {
1719
+ "type": "array",
1720
+ "items": {
1721
+ "type": "string"
1722
+ }
1723
+ },
1724
+ "symbols": {
1725
+ "type": "array",
1726
+ "items": {
1727
+ "type": "string"
1728
+ }
1729
+ },
1730
+ "side": {
1731
+ "type": "string",
1732
+ "enum": [
1733
+ "long",
1734
+ "short"
1735
+ ]
1736
+ }
1737
+ },
1738
+ "additionalProperties": false
1384
1739
  },
1385
1740
  "side": {
1386
1741
  "type": "string",
@@ -1406,12 +1761,11 @@
1406
1761
  "description": "Base asset size when supported."
1407
1762
  },
1408
1763
  "leverage": {
1409
- "type": "number"
1764
+ "type": "number",
1765
+ "default": 3
1410
1766
  }
1411
1767
  },
1412
- "required": [
1413
- "action"
1414
- ]
1768
+ "additionalProperties": false
1415
1769
  },
1416
1770
  "examples": [
1417
1771
  {
@@ -1422,24 +1776,24 @@
1422
1776
  "side": "long",
1423
1777
  "notional": "10",
1424
1778
  "leverage": 2,
1425
- "intent": "Place the requested Perps order",
1426
- "network": "testnet"
1779
+ "intent": "Place the requested Perps order"
1427
1780
  }
1428
1781
  }
1429
1782
  ],
1430
1783
  "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
1431
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof."
1784
+ "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof.",
1785
+ "execution_capabilities": [
1786
+ "app-mutation",
1787
+ "external-mutation"
1788
+ ]
1432
1789
  },
1433
1790
  {
1434
1791
  "name": "metamask.perps.assert_positions",
1435
1792
  "owner": "metamask",
1436
- "description": "mobile Assert selected live Perps positions are present or absent.",
1793
+ "description": "mobile Assert live Perps positions are present or absent for an explicit market selection or mode=all.",
1437
1794
  "schema": {
1438
1795
  "type": "object",
1439
1796
  "properties": {
1440
- "action": {
1441
- "const": "metamask.perps.assert_positions"
1442
- },
1443
1797
  "market": {
1444
1798
  "type": "string",
1445
1799
  "description": "Single market symbol, e.g. BTC or ETH. Alias: symbol."
@@ -1472,7 +1826,41 @@
1472
1826
  },
1473
1827
  "selector": {
1474
1828
  "type": "object",
1475
- "description": "Optional structured selector with mode, symbols/markets, and side."
1829
+ "properties": {
1830
+ "mode": {
1831
+ "type": "string",
1832
+ "enum": [
1833
+ "matching",
1834
+ "all"
1835
+ ]
1836
+ },
1837
+ "market": {
1838
+ "type": "string"
1839
+ },
1840
+ "symbol": {
1841
+ "type": "string"
1842
+ },
1843
+ "markets": {
1844
+ "type": "array",
1845
+ "items": {
1846
+ "type": "string"
1847
+ }
1848
+ },
1849
+ "symbols": {
1850
+ "type": "array",
1851
+ "items": {
1852
+ "type": "string"
1853
+ }
1854
+ },
1855
+ "side": {
1856
+ "type": "string",
1857
+ "enum": [
1858
+ "long",
1859
+ "short"
1860
+ ]
1861
+ }
1862
+ },
1863
+ "additionalProperties": false
1476
1864
  },
1477
1865
  "side": {
1478
1866
  "type": "string",
@@ -1498,8 +1886,9 @@
1498
1886
  }
1499
1887
  },
1500
1888
  "required": [
1501
- "action"
1502
- ]
1889
+ "state"
1890
+ ],
1891
+ "additionalProperties": false
1503
1892
  },
1504
1893
  "examples": [
1505
1894
  {
@@ -1513,18 +1902,16 @@
1513
1902
  }
1514
1903
  ],
1515
1904
  "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
1516
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof."
1905
+ "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof.",
1906
+ "execution_capabilities": []
1517
1907
  },
1518
1908
  {
1519
1909
  "name": "metamask.perps.assert_orders",
1520
1910
  "owner": "metamask",
1521
- "description": "mobile Assert selected live Perps open orders are present or absent.",
1911
+ "description": "mobile Assert live Perps open orders are present or absent for an explicit market selection or mode=all.",
1522
1912
  "schema": {
1523
1913
  "type": "object",
1524
1914
  "properties": {
1525
- "action": {
1526
- "const": "metamask.perps.assert_orders"
1527
- },
1528
1915
  "market": {
1529
1916
  "type": "string",
1530
1917
  "description": "Single market symbol, e.g. BTC or ETH. Alias: symbol."
@@ -1557,7 +1944,41 @@
1557
1944
  },
1558
1945
  "selector": {
1559
1946
  "type": "object",
1560
- "description": "Optional structured selector with mode, symbols/markets, and side."
1947
+ "properties": {
1948
+ "mode": {
1949
+ "type": "string",
1950
+ "enum": [
1951
+ "matching",
1952
+ "all"
1953
+ ]
1954
+ },
1955
+ "market": {
1956
+ "type": "string"
1957
+ },
1958
+ "symbol": {
1959
+ "type": "string"
1960
+ },
1961
+ "markets": {
1962
+ "type": "array",
1963
+ "items": {
1964
+ "type": "string"
1965
+ }
1966
+ },
1967
+ "symbols": {
1968
+ "type": "array",
1969
+ "items": {
1970
+ "type": "string"
1971
+ }
1972
+ },
1973
+ "side": {
1974
+ "type": "string",
1975
+ "enum": [
1976
+ "long",
1977
+ "short"
1978
+ ]
1979
+ }
1980
+ },
1981
+ "additionalProperties": false
1561
1982
  },
1562
1983
  "side": {
1563
1984
  "type": "string",
@@ -1583,8 +2004,9 @@
1583
2004
  }
1584
2005
  },
1585
2006
  "required": [
1586
- "action"
1587
- ]
2007
+ "state"
2008
+ ],
2009
+ "additionalProperties": false
1588
2010
  },
1589
2011
  "examples": [
1590
2012
  {
@@ -1598,7 +2020,8 @@
1598
2020
  }
1599
2021
  ],
1600
2022
  "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
1601
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof."
2023
+ "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof.",
2024
+ "execution_capabilities": []
1602
2025
  },
1603
2026
  {
1604
2027
  "name": "metamask.perps.ensure_positions",
@@ -1607,9 +2030,6 @@
1607
2030
  "schema": {
1608
2031
  "type": "object",
1609
2032
  "properties": {
1610
- "action": {
1611
- "const": "metamask.perps.ensure_positions"
1612
- },
1613
2033
  "market": {
1614
2034
  "type": "string",
1615
2035
  "description": "Single market symbol, e.g. BTC or ETH. Alias: symbol."
@@ -1642,7 +2062,41 @@
1642
2062
  },
1643
2063
  "selector": {
1644
2064
  "type": "object",
1645
- "description": "Optional structured selector with mode, symbols/markets, and side."
2065
+ "properties": {
2066
+ "mode": {
2067
+ "type": "string",
2068
+ "enum": [
2069
+ "matching",
2070
+ "all"
2071
+ ]
2072
+ },
2073
+ "market": {
2074
+ "type": "string"
2075
+ },
2076
+ "symbol": {
2077
+ "type": "string"
2078
+ },
2079
+ "markets": {
2080
+ "type": "array",
2081
+ "items": {
2082
+ "type": "string"
2083
+ }
2084
+ },
2085
+ "symbols": {
2086
+ "type": "array",
2087
+ "items": {
2088
+ "type": "string"
2089
+ }
2090
+ },
2091
+ "side": {
2092
+ "type": "string",
2093
+ "enum": [
2094
+ "long",
2095
+ "short"
2096
+ ]
2097
+ }
2098
+ },
2099
+ "additionalProperties": false
1646
2100
  },
1647
2101
  "side": {
1648
2102
  "type": "string",
@@ -1668,8 +2122,9 @@
1668
2122
  }
1669
2123
  },
1670
2124
  "required": [
1671
- "action"
1672
- ]
2125
+ "state"
2126
+ ],
2127
+ "additionalProperties": false
1673
2128
  },
1674
2129
  "examples": [
1675
2130
  {
@@ -1678,8 +2133,7 @@
1678
2133
  "action": "metamask.perps.ensure_positions",
1679
2134
  "state": "none",
1680
2135
  "market": "BTC",
1681
- "intent": "Converge Perps positions to the requested state",
1682
- "network": "testnet"
2136
+ "intent": "Converge Perps positions to the requested state"
1683
2137
  }
1684
2138
  },
1685
2139
  {
@@ -1688,24 +2142,24 @@
1688
2142
  "action": "metamask.perps.ensure_positions",
1689
2143
  "state": "none",
1690
2144
  "mode": "all",
1691
- "intent": "Converge Perps positions to the requested state",
1692
- "network": "testnet"
2145
+ "intent": "Converge Perps positions to the requested state"
1693
2146
  }
1694
2147
  }
1695
2148
  ],
1696
2149
  "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
1697
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof."
2150
+ "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof.",
2151
+ "execution_capabilities": [
2152
+ "app-mutation",
2153
+ "external-mutation"
2154
+ ]
1698
2155
  },
1699
2156
  {
1700
2157
  "name": "metamask.perps.ensure_orders",
1701
2158
  "owner": "metamask",
1702
- "description": "mobile Default to testnet for Perps mutations; mainnet is read-only unless explicitly requested. Higher-level wrapper that reads selected orders, cancels when needed, then asserts final order state.",
2159
+ "description": "mobile Converge selected testnet orders to open or absent, then independently assert the final state. Creating an open state uses a resting limit order.",
1703
2160
  "schema": {
1704
2161
  "type": "object",
1705
2162
  "properties": {
1706
- "action": {
1707
- "const": "metamask.perps.ensure_orders"
1708
- },
1709
2163
  "market": {
1710
2164
  "type": "string",
1711
2165
  "description": "Single market symbol, e.g. BTC or ETH. Alias: symbol."
@@ -1736,141 +2190,433 @@
1736
2190
  ],
1737
2191
  "description": "matching selects requested symbols; all selects every live item returned by the product."
1738
2192
  },
1739
- "selector": {
2193
+ "selector": {
2194
+ "type": "object",
2195
+ "properties": {
2196
+ "mode": {
2197
+ "type": "string",
2198
+ "enum": [
2199
+ "matching",
2200
+ "all"
2201
+ ]
2202
+ },
2203
+ "market": {
2204
+ "type": "string"
2205
+ },
2206
+ "symbol": {
2207
+ "type": "string"
2208
+ },
2209
+ "markets": {
2210
+ "type": "array",
2211
+ "items": {
2212
+ "type": "string"
2213
+ }
2214
+ },
2215
+ "symbols": {
2216
+ "type": "array",
2217
+ "items": {
2218
+ "type": "string"
2219
+ }
2220
+ },
2221
+ "side": {
2222
+ "type": "string",
2223
+ "enum": [
2224
+ "long",
2225
+ "short"
2226
+ ]
2227
+ }
2228
+ },
2229
+ "additionalProperties": false
2230
+ },
2231
+ "side": {
2232
+ "type": "string",
2233
+ "enum": [
2234
+ "long",
2235
+ "short"
2236
+ ],
2237
+ "description": "Optional position/order side filter."
2238
+ },
2239
+ "timeout_ms": {
2240
+ "type": "number"
2241
+ },
2242
+ "notional": {
2243
+ "type": "number",
2244
+ "default": 10,
2245
+ "description": "Small testnet USD notional used only when creating a missing open order."
2246
+ },
2247
+ "leverage": {
2248
+ "type": "number",
2249
+ "default": 3,
2250
+ "description": "Testnet leverage used only when creating a missing open order."
2251
+ },
2252
+ "state": {
2253
+ "type": "string",
2254
+ "enum": [
2255
+ "none",
2256
+ "absent",
2257
+ "closed",
2258
+ "open",
2259
+ "present"
2260
+ ],
2261
+ "description": "Desired/expected selected collection state."
2262
+ }
2263
+ },
2264
+ "required": [
2265
+ "state"
2266
+ ],
2267
+ "additionalProperties": false
2268
+ },
2269
+ "examples": [
2270
+ {
2271
+ "description": "Ensure no open orders before proof window",
2272
+ "node": {
2273
+ "action": "metamask.perps.ensure_orders",
2274
+ "state": "none",
2275
+ "mode": "all",
2276
+ "intent": "Converge Perps orders to the requested state"
2277
+ }
2278
+ },
2279
+ {
2280
+ "description": "Ensure one small resting ETH short order exists",
2281
+ "node": {
2282
+ "action": "metamask.perps.ensure_orders",
2283
+ "market": "ETH",
2284
+ "side": "short",
2285
+ "state": "open",
2286
+ "notional": 10,
2287
+ "leverage": 2,
2288
+ "intent": "Converge ETH open orders to one deterministic testnet precondition"
2289
+ }
2290
+ }
2291
+ ],
2292
+ "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
2293
+ "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof.",
2294
+ "execution_capabilities": [
2295
+ "app-mutation",
2296
+ "external-mutation"
2297
+ ]
2298
+ },
2299
+ {
2300
+ "name": "metamask.perps.start_state",
2301
+ "owner": "metamask",
2302
+ "description": "mobile Default to testnet for Perps mutations; mainnet is read-only unless explicitly requested. Recommended configurable Perps start state that first restores the fixture-backed unlocked wallet, then composes reusable domain operations before the proof window.",
2303
+ "schema": {
2304
+ "type": "object",
2305
+ "properties": {
2306
+ "profile": {
2307
+ "type": "string",
2308
+ "description": "Optional profile such as clean_market_testnet, open_position_testnet, open_order_testnet, or provider_mainnet_readonly."
2309
+ },
2310
+ "account": {
2311
+ "type": "string"
2312
+ },
2313
+ "provider": {
2314
+ "type": "string",
2315
+ "description": "Requested Perps provider, e.g. hyperliquid."
2316
+ },
2317
+ "network": {
2318
+ "type": "string",
2319
+ "enum": [
2320
+ "testnet",
2321
+ "mainnet"
2322
+ ]
2323
+ },
2324
+ "market": {
2325
+ "type": "string"
2326
+ },
2327
+ "symbol": {
2328
+ "type": "string"
2329
+ },
2330
+ "markets": {
2331
+ "type": "array",
2332
+ "items": {
2333
+ "type": "string"
2334
+ }
2335
+ },
2336
+ "symbols": {
2337
+ "type": "array",
2338
+ "items": {
2339
+ "type": "string"
2340
+ }
2341
+ },
2342
+ "page": {
2343
+ "description": "Initial Perps page target; omit it to use the profile or market default.",
2344
+ "type": "string",
2345
+ "enum": [
2346
+ "home",
2347
+ "perps",
2348
+ "perps_home",
2349
+ "market",
2350
+ "market_details"
2351
+ ]
2352
+ },
2353
+ "positions": {
2354
+ "type": [
2355
+ "object",
2356
+ "boolean"
2357
+ ],
2358
+ "properties": {
2359
+ "mode": {
2360
+ "type": "string",
2361
+ "enum": [
2362
+ "matching",
2363
+ "all"
2364
+ ]
2365
+ },
2366
+ "market": {
2367
+ "type": "string"
2368
+ },
2369
+ "symbol": {
2370
+ "type": "string"
2371
+ },
2372
+ "markets": {
2373
+ "type": "array",
2374
+ "items": {
2375
+ "type": "string"
2376
+ }
2377
+ },
2378
+ "symbols": {
2379
+ "type": "array",
2380
+ "items": {
2381
+ "type": "string"
2382
+ }
2383
+ },
2384
+ "side": {
2385
+ "type": "string",
2386
+ "enum": [
2387
+ "long",
2388
+ "short"
2389
+ ]
2390
+ },
2391
+ "state": {
2392
+ "type": "string",
2393
+ "enum": [
2394
+ "none",
2395
+ "closed",
2396
+ "absent",
2397
+ "open",
2398
+ "present"
2399
+ ]
2400
+ },
2401
+ "amount": {
2402
+ "type": [
2403
+ "string",
2404
+ "number"
2405
+ ]
2406
+ },
2407
+ "notional": {
2408
+ "type": [
2409
+ "string",
2410
+ "number"
2411
+ ]
2412
+ },
2413
+ "size": {
2414
+ "type": [
2415
+ "string",
2416
+ "number"
2417
+ ]
2418
+ },
2419
+ "leverage": {
2420
+ "type": "number"
2421
+ },
2422
+ "order_type": {
2423
+ "type": "string"
2424
+ },
2425
+ "orderType": {
2426
+ "type": "string"
2427
+ },
2428
+ "limit_price": {
2429
+ "type": "number"
2430
+ },
2431
+ "limitPrice": {
2432
+ "type": "number"
2433
+ },
2434
+ "offset_pct": {
2435
+ "type": "number"
2436
+ },
2437
+ "offsetPct": {
2438
+ "type": "number"
2439
+ },
2440
+ "current_price": {
2441
+ "type": "number"
2442
+ },
2443
+ "currentPrice": {
2444
+ "type": "number"
2445
+ },
2446
+ "max_slippage_bps": {
2447
+ "type": "number"
2448
+ },
2449
+ "maxSlippageBps": {
2450
+ "type": "number"
2451
+ },
2452
+ "close_attempts": {
2453
+ "type": "integer"
2454
+ },
2455
+ "close_retry_delay_ms": {
2456
+ "type": "integer"
2457
+ },
2458
+ "timeout_ms": {
2459
+ "type": "number"
2460
+ }
2461
+ },
2462
+ "additionalProperties": false
2463
+ },
2464
+ "orders": {
2465
+ "type": [
2466
+ "object",
2467
+ "boolean"
2468
+ ],
2469
+ "properties": {
2470
+ "mode": {
2471
+ "type": "string",
2472
+ "enum": [
2473
+ "matching",
2474
+ "all"
2475
+ ]
2476
+ },
2477
+ "market": {
2478
+ "type": "string"
2479
+ },
2480
+ "symbol": {
2481
+ "type": "string"
2482
+ },
2483
+ "markets": {
2484
+ "type": "array",
2485
+ "items": {
2486
+ "type": "string"
2487
+ }
2488
+ },
2489
+ "symbols": {
2490
+ "type": "array",
2491
+ "items": {
2492
+ "type": "string"
2493
+ }
2494
+ },
2495
+ "side": {
2496
+ "type": "string",
2497
+ "enum": [
2498
+ "long",
2499
+ "short"
2500
+ ]
2501
+ },
2502
+ "state": {
2503
+ "type": "string",
2504
+ "enum": [
2505
+ "none",
2506
+ "closed",
2507
+ "absent",
2508
+ "open",
2509
+ "present"
2510
+ ]
2511
+ },
2512
+ "amount": {
2513
+ "type": [
2514
+ "string",
2515
+ "number"
2516
+ ]
2517
+ },
2518
+ "notional": {
2519
+ "type": [
2520
+ "string",
2521
+ "number"
2522
+ ]
2523
+ },
2524
+ "size": {
2525
+ "type": [
2526
+ "string",
2527
+ "number"
2528
+ ]
2529
+ },
2530
+ "leverage": {
2531
+ "type": "number"
2532
+ },
2533
+ "order_type": {
2534
+ "type": "string"
2535
+ },
2536
+ "orderType": {
2537
+ "type": "string"
2538
+ },
2539
+ "limit_price": {
2540
+ "type": "number"
2541
+ },
2542
+ "limitPrice": {
2543
+ "type": "number"
2544
+ },
2545
+ "offset_pct": {
2546
+ "type": "number"
2547
+ },
2548
+ "offsetPct": {
2549
+ "type": "number"
2550
+ },
2551
+ "current_price": {
2552
+ "type": "number"
2553
+ },
2554
+ "currentPrice": {
2555
+ "type": "number"
2556
+ },
2557
+ "max_slippage_bps": {
2558
+ "type": "number"
2559
+ },
2560
+ "maxSlippageBps": {
2561
+ "type": "number"
2562
+ },
2563
+ "close_attempts": {
2564
+ "type": "integer"
2565
+ },
2566
+ "close_retry_delay_ms": {
2567
+ "type": "integer"
2568
+ },
2569
+ "timeout_ms": {
2570
+ "type": "number"
2571
+ }
2572
+ },
2573
+ "additionalProperties": false
2574
+ },
2575
+ "hud": {
1740
2576
  "type": "object",
1741
- "description": "Optional structured selector with mode, symbols/markets, and side."
1742
- },
1743
- "side": {
1744
- "type": "string",
1745
- "enum": [
1746
- "long",
1747
- "short"
1748
- ],
1749
- "description": "Optional position/order side filter."
2577
+ "properties": {},
2578
+ "additionalProperties": false
1750
2579
  },
1751
2580
  "timeout_ms": {
1752
2581
  "type": "number"
1753
2582
  },
1754
- "state": {
1755
- "type": "string",
1756
- "enum": [
1757
- "none",
1758
- "absent",
1759
- "closed",
1760
- "open",
1761
- "present"
1762
- ],
1763
- "description": "Desired/expected selected collection state."
1764
- }
1765
- },
1766
- "required": [
1767
- "action"
1768
- ]
1769
- },
1770
- "examples": [
1771
- {
1772
- "description": "Ensure no open orders before proof window",
1773
- "node": {
1774
- "action": "metamask.perps.ensure_orders",
1775
- "state": "none",
1776
- "mode": "all",
1777
- "intent": "Converge Perps orders to the requested state",
1778
- "network": "testnet"
1779
- }
1780
- }
1781
- ],
1782
- "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
1783
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof."
1784
- },
1785
- {
1786
- "name": "metamask.perps.start_state",
1787
- "owner": "metamask",
1788
- "description": "mobile Default to testnet for Perps mutations; mainnet is read-only unless explicitly requested. Recommended configurable Perps start state that first restores the fixture-backed unlocked wallet, then composes reusable domain operations before the proof window.",
1789
- "schema": {
1790
- "type": "object",
1791
- "properties": {
1792
- "action": {
1793
- "const": "metamask.perps.start_state"
1794
- },
1795
- "profile": {
1796
- "type": "string",
1797
- "description": "Optional profile such as clean_market_testnet, open_position_testnet, open_order_testnet, or provider_mainnet_readonly."
1798
- },
1799
- "account": {
1800
- "type": "string"
1801
- },
1802
- "provider": {
1803
- "type": "string",
1804
- "description": "Requested Perps provider, e.g. hyperliquid."
1805
- },
1806
- "network": {
2583
+ "side": {
1807
2584
  "type": "string",
1808
2585
  "enum": [
1809
- "testnet",
1810
- "mainnet"
2586
+ "long",
2587
+ "short"
1811
2588
  ]
1812
2589
  },
1813
- "market": {
1814
- "type": "string"
1815
- },
1816
- "symbol": {
2590
+ "account_name": {
1817
2591
  "type": "string"
1818
2592
  },
1819
- "markets": {
1820
- "type": "array",
1821
- "items": {
1822
- "type": "string"
1823
- }
1824
- },
1825
- "symbols": {
1826
- "type": "array",
1827
- "items": {
1828
- "type": "string"
1829
- }
1830
- },
1831
- "page": {
1832
- "oneOf": [
1833
- {
1834
- "type": "string"
1835
- },
1836
- {
1837
- "const": false
1838
- }
1839
- ],
1840
- "description": "Final page target; false skips navigation for teardown."
2593
+ "readyToTrade": {
2594
+ "type": "boolean"
1841
2595
  },
1842
- "positions": {
1843
- "oneOf": [
1844
- {
1845
- "type": "object"
2596
+ "balance": {
2597
+ "type": "object",
2598
+ "properties": {
2599
+ "minWithdrawableUsd": {
2600
+ "type": "number"
1846
2601
  },
1847
- {
1848
- "const": false
1849
- }
1850
- ],
1851
- "description": "Desired position baseline or false to skip."
1852
- },
1853
- "orders": {
1854
- "oneOf": [
1855
- {
1856
- "type": "object"
2602
+ "minUsd": {
2603
+ "type": "number"
1857
2604
  },
1858
- {
1859
- "const": false
2605
+ "minSpendableUsd": {
2606
+ "type": "number"
1860
2607
  }
1861
- ],
1862
- "description": "Desired open-order baseline or false to skip."
1863
- },
1864
- "hud": {
1865
- "type": "object"
2608
+ },
2609
+ "additionalProperties": false
1866
2610
  },
1867
- "timeout_ms": {
1868
- "type": "number"
2611
+ "mode": {
2612
+ "type": "string",
2613
+ "enum": [
2614
+ "matching",
2615
+ "all"
2616
+ ]
1869
2617
  }
1870
2618
  },
1871
- "required": [
1872
- "action"
1873
- ]
2619
+ "additionalProperties": false
1874
2620
  },
1875
2621
  "examples": [
1876
2622
  {
@@ -1907,7 +2653,11 @@
1907
2653
  }
1908
2654
  ],
1909
2655
  "proof_effect": "Runs in setup/teardown phase and must be visible in trace.json; proof videos can keep this as trace-only setup.",
1910
- "safety_notes": "Composes supported app/API paths and must not mutate React/Redux/MobX/DOM/local storage to fabricate proof."
2656
+ "safety_notes": "Composes supported app/API paths and must not mutate React/Redux/MobX/DOM/local storage to fabricate proof.",
2657
+ "execution_capabilities": [
2658
+ "app-mutation",
2659
+ "external-mutation"
2660
+ ]
1911
2661
  },
1912
2662
  {
1913
2663
  "name": "metamask.perps.teardown_state",
@@ -1916,9 +2666,6 @@
1916
2666
  "schema": {
1917
2667
  "type": "object",
1918
2668
  "properties": {
1919
- "action": {
1920
- "const": "metamask.perps.teardown_state"
1921
- },
1922
2669
  "profile": {
1923
2670
  "type": "string",
1924
2671
  "description": "Optional profile such as clean_market_testnet, open_position_testnet, open_order_testnet, or provider_mainnet_readonly."
@@ -1956,48 +2703,287 @@
1956
2703
  }
1957
2704
  },
1958
2705
  "page": {
1959
- "oneOf": [
1960
- {
1961
- "type": "string"
1962
- },
1963
- {
1964
- "const": false
1965
- }
2706
+ "description": "Final page target; false skips navigation for teardown.",
2707
+ "type": [
2708
+ "string",
2709
+ "boolean"
1966
2710
  ],
1967
- "description": "Final page target; false skips navigation for teardown."
2711
+ "enum": [
2712
+ "home",
2713
+ "perps",
2714
+ "perps_home",
2715
+ "market",
2716
+ "market_details",
2717
+ false
2718
+ ]
1968
2719
  },
1969
2720
  "positions": {
1970
- "oneOf": [
1971
- {
1972
- "type": "object"
2721
+ "type": [
2722
+ "object",
2723
+ "boolean"
2724
+ ],
2725
+ "properties": {
2726
+ "mode": {
2727
+ "type": "string",
2728
+ "enum": [
2729
+ "matching",
2730
+ "all"
2731
+ ]
1973
2732
  },
1974
- {
1975
- "const": false
2733
+ "market": {
2734
+ "type": "string"
2735
+ },
2736
+ "symbol": {
2737
+ "type": "string"
2738
+ },
2739
+ "markets": {
2740
+ "type": "array",
2741
+ "items": {
2742
+ "type": "string"
2743
+ }
2744
+ },
2745
+ "symbols": {
2746
+ "type": "array",
2747
+ "items": {
2748
+ "type": "string"
2749
+ }
2750
+ },
2751
+ "side": {
2752
+ "type": "string",
2753
+ "enum": [
2754
+ "long",
2755
+ "short"
2756
+ ]
2757
+ },
2758
+ "state": {
2759
+ "type": "string",
2760
+ "enum": [
2761
+ "none",
2762
+ "closed",
2763
+ "absent",
2764
+ "open",
2765
+ "present"
2766
+ ]
2767
+ },
2768
+ "amount": {
2769
+ "type": [
2770
+ "string",
2771
+ "number"
2772
+ ]
2773
+ },
2774
+ "notional": {
2775
+ "type": [
2776
+ "string",
2777
+ "number"
2778
+ ]
2779
+ },
2780
+ "size": {
2781
+ "type": [
2782
+ "string",
2783
+ "number"
2784
+ ]
2785
+ },
2786
+ "leverage": {
2787
+ "type": "number"
2788
+ },
2789
+ "order_type": {
2790
+ "type": "string"
2791
+ },
2792
+ "orderType": {
2793
+ "type": "string"
2794
+ },
2795
+ "limit_price": {
2796
+ "type": "number"
2797
+ },
2798
+ "limitPrice": {
2799
+ "type": "number"
2800
+ },
2801
+ "offset_pct": {
2802
+ "type": "number"
2803
+ },
2804
+ "offsetPct": {
2805
+ "type": "number"
2806
+ },
2807
+ "current_price": {
2808
+ "type": "number"
2809
+ },
2810
+ "currentPrice": {
2811
+ "type": "number"
2812
+ },
2813
+ "max_slippage_bps": {
2814
+ "type": "number"
2815
+ },
2816
+ "maxSlippageBps": {
2817
+ "type": "number"
2818
+ },
2819
+ "close_attempts": {
2820
+ "type": "integer"
2821
+ },
2822
+ "close_retry_delay_ms": {
2823
+ "type": "integer"
2824
+ },
2825
+ "timeout_ms": {
2826
+ "type": "number"
1976
2827
  }
1977
- ],
1978
- "description": "Desired position baseline or false to skip."
2828
+ },
2829
+ "additionalProperties": false
1979
2830
  },
1980
2831
  "orders": {
1981
- "oneOf": [
1982
- {
1983
- "type": "object"
2832
+ "type": [
2833
+ "object",
2834
+ "boolean"
2835
+ ],
2836
+ "properties": {
2837
+ "mode": {
2838
+ "type": "string",
2839
+ "enum": [
2840
+ "matching",
2841
+ "all"
2842
+ ]
1984
2843
  },
1985
- {
1986
- "const": false
2844
+ "market": {
2845
+ "type": "string"
2846
+ },
2847
+ "symbol": {
2848
+ "type": "string"
2849
+ },
2850
+ "markets": {
2851
+ "type": "array",
2852
+ "items": {
2853
+ "type": "string"
2854
+ }
2855
+ },
2856
+ "symbols": {
2857
+ "type": "array",
2858
+ "items": {
2859
+ "type": "string"
2860
+ }
2861
+ },
2862
+ "side": {
2863
+ "type": "string",
2864
+ "enum": [
2865
+ "long",
2866
+ "short"
2867
+ ]
2868
+ },
2869
+ "state": {
2870
+ "type": "string",
2871
+ "enum": [
2872
+ "none",
2873
+ "closed",
2874
+ "absent",
2875
+ "open",
2876
+ "present"
2877
+ ]
2878
+ },
2879
+ "amount": {
2880
+ "type": [
2881
+ "string",
2882
+ "number"
2883
+ ]
2884
+ },
2885
+ "notional": {
2886
+ "type": [
2887
+ "string",
2888
+ "number"
2889
+ ]
2890
+ },
2891
+ "size": {
2892
+ "type": [
2893
+ "string",
2894
+ "number"
2895
+ ]
2896
+ },
2897
+ "leverage": {
2898
+ "type": "number"
2899
+ },
2900
+ "order_type": {
2901
+ "type": "string"
2902
+ },
2903
+ "orderType": {
2904
+ "type": "string"
2905
+ },
2906
+ "limit_price": {
2907
+ "type": "number"
2908
+ },
2909
+ "limitPrice": {
2910
+ "type": "number"
2911
+ },
2912
+ "offset_pct": {
2913
+ "type": "number"
2914
+ },
2915
+ "offsetPct": {
2916
+ "type": "number"
2917
+ },
2918
+ "current_price": {
2919
+ "type": "number"
2920
+ },
2921
+ "currentPrice": {
2922
+ "type": "number"
2923
+ },
2924
+ "max_slippage_bps": {
2925
+ "type": "number"
2926
+ },
2927
+ "maxSlippageBps": {
2928
+ "type": "number"
2929
+ },
2930
+ "close_attempts": {
2931
+ "type": "integer"
2932
+ },
2933
+ "close_retry_delay_ms": {
2934
+ "type": "integer"
2935
+ },
2936
+ "timeout_ms": {
2937
+ "type": "number"
1987
2938
  }
1988
- ],
1989
- "description": "Desired open-order baseline or false to skip."
2939
+ },
2940
+ "additionalProperties": false
1990
2941
  },
1991
2942
  "hud": {
1992
- "type": "object"
2943
+ "type": "object",
2944
+ "properties": {},
2945
+ "additionalProperties": false
1993
2946
  },
1994
2947
  "timeout_ms": {
1995
2948
  "type": "number"
2949
+ },
2950
+ "side": {
2951
+ "type": "string",
2952
+ "enum": [
2953
+ "long",
2954
+ "short"
2955
+ ]
2956
+ },
2957
+ "account_name": {
2958
+ "type": "string"
2959
+ },
2960
+ "readyToTrade": {
2961
+ "type": "boolean"
2962
+ },
2963
+ "balance": {
2964
+ "type": "object",
2965
+ "properties": {
2966
+ "minWithdrawableUsd": {
2967
+ "type": "number"
2968
+ },
2969
+ "minUsd": {
2970
+ "type": "number"
2971
+ },
2972
+ "minSpendableUsd": {
2973
+ "type": "number"
2974
+ }
2975
+ },
2976
+ "additionalProperties": false
2977
+ },
2978
+ "mode": {
2979
+ "type": "string",
2980
+ "enum": [
2981
+ "matching",
2982
+ "all"
2983
+ ]
1996
2984
  }
1997
2985
  },
1998
- "required": [
1999
- "action"
2000
- ]
2986
+ "additionalProperties": false
2001
2987
  },
2002
2988
  "examples": [
2003
2989
  {
@@ -2025,23 +3011,11 @@
2025
3011
  }
2026
3012
  ],
2027
3013
  "proof_effect": "Runs in setup/teardown phase and must be visible in trace.json; proof videos can keep this as trace-only setup.",
2028
- "safety_notes": "Composes supported app/API paths and must not mutate React/Redux/MobX/DOM/local storage to fabricate proof."
2029
- }
2030
- ],
2031
- "state_refs": [
2032
- {
2033
- "name": "wallet.redacted_state",
2034
- "description": "Redacted wallet state reported by metamask.wallet.read_state."
2035
- },
2036
- {
2037
- "name": "perps.positions",
2038
- "description": "Normalized Perps position state."
2039
- }
2040
- ],
2041
- "pre_conditions": [
2042
- {
2043
- "id": "no-mid-recipe-state-injection",
2044
- "description": "Recipes must drive real supported UI/app/API paths for proof state."
3014
+ "safety_notes": "Composes supported app/API paths and must not mutate React/Redux/MobX/DOM/local storage to fabricate proof.",
3015
+ "execution_capabilities": [
3016
+ "app-mutation",
3017
+ "external-mutation"
3018
+ ]
2045
3019
  }
2046
3020
  ],
2047
3021
  "native_bindings": [
@@ -2193,9 +3167,5 @@
2193
3167
  "action": "metamask.perps.teardown_state",
2194
3168
  "implementation": "@metamask/recipe-runner/mobile/perps-domain-dispatcher"
2195
3169
  }
2196
- ],
2197
- "capability_notes": [
2198
- "Perps safety: default to testnet for setup and order/position mutations. Mainnet is read-only unless the user explicitly requests a mainnet mutation."
2199
- ],
2200
- "action_profiles": []
3170
+ ]
2201
3171
  }