@deeeed/metamask-harness 0.8.0 → 0.9.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.
@@ -1,1282 +1,1522 @@
1
1
  {
2
- "runner_protocol_version": 1,
3
- "action_registry_version": 1,
4
- "supported_official_actions": [
5
- "command",
6
- "wait",
7
- "assert_file",
8
- "assert_json",
9
- "assert_exit_code",
10
- "assert_output",
11
- "watch_logs",
12
- "index_artifacts",
13
- "end",
14
- "call",
15
- "app.status"
16
- ],
17
- "action_metadata": {
18
- "command": {
19
- "description": "Run a shell command from the project root.",
20
- "examples": [
21
- {
22
- "node": {
23
- "action": "command",
24
- "cmd": "pwd",
25
- "intent": "Run a shell command from the project root"
26
- }
27
- }
28
- ]
29
- },
30
- "wait": {
31
- "description": "Base wait action.",
32
- "examples": [
33
- {
34
- "node": {
35
- "action": "wait",
36
- "duration_ms": 1,
37
- "intent": "Base wait action"
38
- }
39
- }
40
- ]
41
- },
42
- "watch_logs": {
43
- "description": "Base log assertion/capture action.",
44
- "examples": [
45
- {
46
- "node": {
47
- "action": "watch_logs",
48
- "path": "logs/app.log",
49
- "intent": "Base log assertion/capture action"
50
- }
2
+ "runner_protocol_version": 1,
3
+ "action_registry_version": 1,
4
+ "supported_official_actions": [
5
+ "command",
6
+ "wait",
7
+ "assert_file",
8
+ "assert_json",
9
+ "assert_exit_code",
10
+ "assert_output",
11
+ "watch_logs",
12
+ "index_artifacts",
13
+ "end",
14
+ "call",
15
+ "app.status"
16
+ ],
17
+ "action_metadata": {
18
+ "command": {
19
+ "description": "Run a shell command from the project root.",
20
+ "examples": [
21
+ {
22
+ "node": {
23
+ "action": "command",
24
+ "cmd": "pwd",
25
+ "intent": "Run a shell command from the project root"
26
+ }
27
+ }
28
+ ]
29
+ },
30
+ "wait": {
31
+ "description": "Base wait action.",
32
+ "examples": [
33
+ {
34
+ "node": {
35
+ "action": "wait",
36
+ "duration_ms": 1,
37
+ "intent": "Base wait action"
38
+ }
39
+ }
40
+ ]
41
+ },
42
+ "watch_logs": {
43
+ "description": "Base log assertion/capture action.",
44
+ "examples": [
45
+ {
46
+ "node": {
47
+ "action": "watch_logs",
48
+ "path": "logs/app.log",
49
+ "intent": "Base log assertion/capture action"
50
+ }
51
+ }
52
+ ]
53
+ },
54
+ "index_artifacts": {
55
+ "description": "Index extra recipe-authored artifacts that already exist on disk.",
56
+ "examples": [
57
+ {
58
+ "node": {
59
+ "action": "index_artifacts",
60
+ "artifacts": [
61
+ {
62
+ "path": "reports/result.json",
63
+ "type": "report",
64
+ "category": "validation",
65
+ "label": "Validation report"
66
+ }
67
+ ],
68
+ "intent": "Register a pre-existing report artifact"
69
+ }
70
+ }
71
+ ]
72
+ },
73
+ "end": {
74
+ "description": "Base terminal action.",
75
+ "examples": [
76
+ {
77
+ "node": {
78
+ "action": "end",
79
+ "status": "pass"
80
+ }
81
+ }
82
+ ]
83
+ },
84
+ "assert_file": {
85
+ "description": "Assert that a project file exists and optionally contains text.",
86
+ "examples": [
87
+ {
88
+ "node": {
89
+ "action": "assert_file",
90
+ "path": "reports/result.txt",
91
+ "contains": "ok",
92
+ "intent": "Assert that a project file exists and optionally contains text"
93
+ }
94
+ }
95
+ ]
96
+ },
97
+ "assert_json": {
98
+ "description": "Assert a JSON file value with a JSONPath-style selector.",
99
+ "examples": [
100
+ {
101
+ "node": {
102
+ "action": "assert_json",
103
+ "path": "reports/result.json",
104
+ "assert": {
105
+ "path": "$.status",
106
+ "operator": "eq",
107
+ "value": "ok"
108
+ },
109
+ "intent": "Assert a JSON file value with a JSONPath-style selector"
110
+ }
111
+ }
112
+ ]
113
+ },
114
+ "assert_exit_code": {
115
+ "description": "Assert the exit code captured from a command node output.",
116
+ "examples": [
117
+ {
118
+ "node": {
119
+ "action": "assert_exit_code",
120
+ "source": "run-tests",
121
+ "expected": 0,
122
+ "intent": "Assert the exit code captured from a command node output"
123
+ }
124
+ }
125
+ ]
126
+ },
127
+ "assert_output": {
128
+ "description": "Assert stdout or stderr captured from a command node output.",
129
+ "examples": [
130
+ {
131
+ "node": {
132
+ "action": "assert_output",
133
+ "source": "run-tests",
134
+ "stream": "stdout",
135
+ "contains": "PASS",
136
+ "intent": "Assert stdout or stderr captured from a command node output"
137
+ }
138
+ }
139
+ ]
140
+ },
141
+ "app.status": {
142
+ "description": "Report the core checkout shape and headless compatibility mode for the perps controller.",
143
+ "examples": [
144
+ {
145
+ "node": {
146
+ "action": "app.status",
147
+ "intent": "Report the resolved core checkout status",
148
+ "next": "done"
149
+ }
150
+ }
151
+ ]
152
+ },
153
+ "call": {
154
+ "description": "Invoke a named sub-recipe flow by ref and run its steps inline.",
155
+ "examples": [
156
+ {
157
+ "node": {
158
+ "action": "call",
159
+ "ref": "shared/setup",
160
+ "intent": "Run a shared sub-recipe flow"
161
+ }
162
+ }
163
+ ]
164
+ },
165
+ "metamask.perps.read_positions": {
166
+ "description": "Read live Perps positions directly from the perps controller (HyperLiquid testnet, HTTP read-only) and return the selected subset.",
167
+ "examples": [
168
+ {
169
+ "description": "Read every live position for the configured account",
170
+ "node": {
171
+ "action": "metamask.perps.read_positions",
172
+ "mode": "all",
173
+ "intent": "Read Perps positions from the headless controller"
174
+ }
175
+ }
176
+ ]
177
+ },
178
+ "metamask.perps.read_orders": {
179
+ "description": "Read live Perps open orders directly from the perps controller (HyperLiquid testnet, HTTP read-only) and return the selected subset.",
180
+ "examples": [
181
+ {
182
+ "description": "Read every live open order for the configured account",
183
+ "node": {
184
+ "action": "metamask.perps.read_orders",
185
+ "mode": "all",
186
+ "intent": "Read Perps open orders from the headless controller"
187
+ }
188
+ }
189
+ ]
190
+ },
191
+ "metamask.perps.read_account": {
192
+ "description": "Read live Perps account state (balances, margin) directly from the perps controller (HyperLiquid testnet, HTTP read-only).",
193
+ "examples": [
194
+ {
195
+ "description": "Read account state for the configured account",
196
+ "node": {
197
+ "action": "metamask.perps.read_account",
198
+ "intent": "Read Perps account state from the headless controller"
199
+ }
200
+ }
201
+ ]
202
+ },
203
+ "metamask.perps.place_order": {
204
+ "description": "Place a real Perps order on HyperLiquid testnet by driving the headless perps controller placeOrder() through the full signing/provider path. Testnet only Supports market (default) and resting limit orders (order_type=limit with price/offset_pct). 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.",
205
+ "examples": [
206
+ {
207
+ "description": "Place a small BTC long market order on testnet",
208
+ "node": {
209
+ "action": "metamask.perps.place_order",
210
+ "market": "BTC",
211
+ "side": "long",
212
+ "notional": "11",
213
+ "leverage": 2,
214
+ "intent": "Place the requested Perps order via the headless controller",
215
+ "network": "testnet"
216
+ }
217
+ },
218
+ {
219
+ "description": "Place a resting BTC limit BUY 30%% below mid on testnet (does not fill)",
220
+ "node": {
221
+ "action": "metamask.perps.place_order",
222
+ "market": "BTC",
223
+ "side": "long",
224
+ "order_type": "limit",
225
+ "offset_pct": -30,
226
+ "notional": "11",
227
+ "leverage": 2,
228
+ "intent": "Place a resting Perps limit order via the headless controller",
229
+ "network": "testnet"
230
+ }
231
+ }
232
+ ]
233
+ },
234
+ "metamask.perps.close_positions": {
235
+ "description": "Close selected live Perps positions on HyperLiquid testnet by driving the headless perps controller closePosition() through the full signing/provider path. Testnet only. 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.",
236
+ "examples": [
237
+ {
238
+ "description": "Close the BTC position on testnet",
239
+ "node": {
240
+ "action": "metamask.perps.close_positions",
241
+ "market": "BTC",
242
+ "intent": "Close selected Perps positions via the headless controller",
243
+ "network": "testnet"
244
+ }
245
+ }
246
+ ]
247
+ },
248
+ "metamask.perps.assert_positions": {
249
+ "description": "Assert selected live Perps positions are present or absent via the headless controller standalone read path. Throws on mismatch.",
250
+ "examples": [
251
+ {
252
+ "description": "Assert BTC position is open",
253
+ "node": {
254
+ "action": "metamask.perps.assert_positions",
255
+ "state": "open",
256
+ "market": "BTC",
257
+ "intent": "Assert the expected Perps position state via the headless controller"
258
+ }
259
+ }
260
+ ]
261
+ },
262
+ "metamask.perps.ensure_positions": {
263
+ "description": "Higher-level wrapper that reads selected positions, places or closes via the headless controller as needed, then asserts the final position state. Testnet only. 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.",
264
+ "examples": [
265
+ {
266
+ "description": "Ensure no BTC position before the proof window",
267
+ "node": {
268
+ "action": "metamask.perps.ensure_positions",
269
+ "state": "none",
270
+ "market": "BTC",
271
+ "intent": "Converge Perps positions to the requested state via the headless controller",
272
+ "network": "testnet"
273
+ }
274
+ }
275
+ ]
276
+ },
277
+ "metamask.perps.close_orders": {
278
+ "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.",
279
+ "examples": [
280
+ {
281
+ "description": "Cancel every open order",
282
+ "node": {
283
+ "action": "metamask.perps.close_orders",
284
+ "mode": "all",
285
+ "intent": "Cancel selected Perps orders before continuing",
286
+ "network": "testnet"
287
+ }
288
+ },
289
+ {
290
+ "description": "Cancel BTC orders only",
291
+ "node": {
292
+ "action": "metamask.perps.close_orders",
293
+ "market": "BTC",
294
+ "intent": "Cancel selected Perps orders before continuing",
295
+ "network": "testnet"
296
+ }
297
+ }
298
+ ]
299
+ },
300
+ "metamask.perps.assert_orders": {
301
+ "description": "Assert selected live Perps open orders are present or absent.",
302
+ "examples": [
303
+ {
304
+ "description": "Assert no BTC open orders",
305
+ "node": {
306
+ "action": "metamask.perps.assert_orders",
307
+ "state": "none",
308
+ "market": "BTC",
309
+ "intent": "Assert the expected Perps open order state"
310
+ }
311
+ }
312
+ ]
313
+ },
314
+ "metamask.perps.ensure_orders": {
315
+ "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.",
316
+ "examples": [
317
+ {
318
+ "description": "Ensure no open orders before proof window",
319
+ "node": {
320
+ "action": "metamask.perps.ensure_orders",
321
+ "state": "none",
322
+ "mode": "all",
323
+ "intent": "Converge Perps orders to the requested state",
324
+ "network": "testnet"
325
+ }
326
+ }
327
+ ]
328
+ },
329
+ "metamask.perps.start_state": {
330
+ "description": "Default to testnet for Perps mutations; mainnet is read-only unless explicitly requested.Recommended configurable Perps start state that composes reusable domain operations before the proof window. Headless: converges POSITION and ORDER baselines on testnet via the controller; no navigation/page/HUD.",
331
+ "examples": [
332
+ {
333
+ "description": "Clean BTC testnet market state",
334
+ "node": {
335
+ "action": "metamask.perps.start_state",
336
+ "profile": "clean_market_testnet",
337
+ "market": "BTC",
338
+ "positions": {
339
+ "state": "none"
340
+ },
341
+ "orders": {
342
+ "state": "none"
343
+ },
344
+ "intent": "Prepare the Perps start state for the proof",
345
+ "network": "testnet"
346
+ }
347
+ },
348
+ {
349
+ "description": "Start with an open ETH long position",
350
+ "node": {
351
+ "action": "metamask.perps.start_state",
352
+ "profile": "open_position_testnet",
353
+ "market": "ETH",
354
+ "side": "long",
355
+ "positions": {
356
+ "state": "open",
357
+ "notional": "10",
358
+ "leverage": 2
359
+ },
360
+ "intent": "Prepare the Perps start state for the proof",
361
+ "network": "testnet"
362
+ }
363
+ }
364
+ ]
365
+ },
366
+ "metamask.perps.teardown_state": {
367
+ "description": "Recommended configurable Perps teardown state that restores the selected environment after proof. Headless: converges POSITION and ORDER baselines on testnet via the controller; no navigation/page/HUD.",
368
+ "examples": [
369
+ {
370
+ "description": "Return BTC test account to clean state",
371
+ "node": {
372
+ "action": "metamask.perps.teardown_state",
373
+ "market": "BTC",
374
+ "positions": {
375
+ "state": "none"
376
+ },
377
+ "orders": {
378
+ "state": "none"
379
+ },
380
+ "intent": "Restore Perps state after the proof"
381
+ }
382
+ },
383
+ {
384
+ "description": "Teardown only state created by selected market",
385
+ "node": {
386
+ "action": "metamask.perps.teardown_state",
387
+ "market": "BTC",
388
+ "intent": "Restore Perps state after the proof"
389
+ }
390
+ }
391
+ ]
51
392
  }
52
- ]
53
393
  },
54
- "index_artifacts": {
55
- "description": "Index extra recipe-authored artifacts that already exist on disk.",
56
- "examples": [
394
+ "custom_actions": [
57
395
  {
58
- "node": {
59
- "action": "index_artifacts",
60
- "artifacts": [
61
- {
62
- "path": "reports/result.json",
63
- "type": "report",
64
- "category": "validation",
65
- "label": "Validation report"
66
- }
396
+ "name": "metamask.perps.read_positions",
397
+ "owner": "metamask",
398
+ "description": "core Read live Perps positions directly from the perps controller (HyperLiquid testnet, HTTP read-only) and return the selected subset.",
399
+ "schema": {
400
+ "type": "object",
401
+ "properties": {
402
+ "action": {
403
+ "const": "metamask.perps.read_positions"
404
+ },
405
+ "market": {
406
+ "type": "string",
407
+ "description": "Single market symbol, e.g. BTC or ETH. Alias: symbol."
408
+ },
409
+ "symbol": {
410
+ "type": "string",
411
+ "description": "Alias for market."
412
+ },
413
+ "markets": {
414
+ "type": "array",
415
+ "items": {
416
+ "type": "string"
417
+ },
418
+ "description": "Explicit list of market symbols."
419
+ },
420
+ "symbols": {
421
+ "type": "array",
422
+ "items": {
423
+ "type": "string"
424
+ },
425
+ "description": "Alias for markets."
426
+ },
427
+ "mode": {
428
+ "type": "string",
429
+ "enum": [
430
+ "matching",
431
+ "all"
432
+ ],
433
+ "description": "matching selects requested symbols; all selects every live item returned by the controller."
434
+ },
435
+ "selector": {
436
+ "type": "object",
437
+ "description": "Optional structured selector with mode, symbols/markets, and side."
438
+ },
439
+ "side": {
440
+ "type": "string",
441
+ "enum": [
442
+ "long",
443
+ "short"
444
+ ],
445
+ "description": "Optional position side filter."
446
+ },
447
+ "account": {
448
+ "type": "string",
449
+ "description": "EVM address to read positions for; defaults to MM_TEST_ACCOUNT_ADDRESS."
450
+ },
451
+ "timeout_ms": {
452
+ "type": "number"
453
+ }
454
+ },
455
+ "required": [
456
+ "action"
457
+ ]
458
+ },
459
+ "examples": [
460
+ {
461
+ "description": "Read every live position for the configured account",
462
+ "node": {
463
+ "action": "metamask.perps.read_positions",
464
+ "mode": "all",
465
+ "intent": "Read Perps positions from the headless controller"
466
+ }
467
+ }
67
468
  ],
68
- "intent": "Register a pre-existing report artifact"
69
- }
70
- }
71
- ]
72
- },
73
- "end": {
74
- "description": "Base terminal action.",
75
- "examples": [
76
- {
77
- "node": {
78
- "action": "end",
79
- "status": "pass"
80
- }
81
- }
82
- ]
83
- },
84
- "assert_file": {
85
- "description": "Assert that a project file exists and optionally contains text.",
86
- "examples": [
87
- {
88
- "node": {
89
- "action": "assert_file",
90
- "path": "reports/result.txt",
91
- "contains": "ok",
92
- "intent": "Assert that a project file exists and optionally contains text"
93
- }
94
- }
95
- ]
96
- },
97
- "assert_json": {
98
- "description": "Assert a JSON file value with a JSONPath-style selector.",
99
- "examples": [
469
+ "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or controller output.",
470
+ "safety_notes": "Read-only HyperLiquid testnet HTTP query; must not inject controller/UI state to fabricate proof."
471
+ },
100
472
  {
101
- "node": {
102
- "action": "assert_json",
103
- "path": "reports/result.json",
104
- "assert": {
105
- "path": "$.status",
106
- "operator": "eq",
107
- "value": "ok"
473
+ "name": "metamask.perps.read_orders",
474
+ "owner": "metamask",
475
+ "description": "core Read live Perps open orders directly from the perps controller (HyperLiquid testnet, HTTP read-only) and return the selected subset.",
476
+ "schema": {
477
+ "type": "object",
478
+ "properties": {
479
+ "action": {
480
+ "const": "metamask.perps.read_orders"
481
+ },
482
+ "market": {
483
+ "type": "string",
484
+ "description": "Single market symbol, e.g. BTC or ETH. Alias: symbol."
485
+ },
486
+ "symbol": {
487
+ "type": "string",
488
+ "description": "Alias for market."
489
+ },
490
+ "markets": {
491
+ "type": "array",
492
+ "items": {
493
+ "type": "string"
494
+ },
495
+ "description": "Explicit list of market symbols."
496
+ },
497
+ "symbols": {
498
+ "type": "array",
499
+ "items": {
500
+ "type": "string"
501
+ },
502
+ "description": "Alias for markets."
503
+ },
504
+ "mode": {
505
+ "type": "string",
506
+ "enum": [
507
+ "matching",
508
+ "all"
509
+ ],
510
+ "description": "matching selects requested symbols; all selects every live item returned by the controller."
511
+ },
512
+ "selector": {
513
+ "type": "object",
514
+ "description": "Optional structured selector with mode, symbols/markets, and side."
515
+ },
516
+ "side": {
517
+ "type": "string",
518
+ "enum": [
519
+ "long",
520
+ "short"
521
+ ],
522
+ "description": "Optional order side filter."
523
+ },
524
+ "account": {
525
+ "type": "string",
526
+ "description": "EVM address to read orders for; defaults to MM_TEST_ACCOUNT_ADDRESS."
527
+ },
528
+ "timeout_ms": {
529
+ "type": "number"
530
+ }
531
+ },
532
+ "required": [
533
+ "action"
534
+ ]
108
535
  },
109
- "intent": "Assert a JSON file value with a JSONPath-style selector"
110
- }
111
- }
112
- ]
113
- },
114
- "assert_exit_code": {
115
- "description": "Assert the exit code captured from a command node output.",
116
- "examples": [
117
- {
118
- "node": {
119
- "action": "assert_exit_code",
120
- "source": "run-tests",
121
- "expected": 0,
122
- "intent": "Assert the exit code captured from a command node output"
123
- }
124
- }
125
- ]
126
- },
127
- "assert_output": {
128
- "description": "Assert stdout or stderr captured from a command node output.",
129
- "examples": [
130
- {
131
- "node": {
132
- "action": "assert_output",
133
- "source": "run-tests",
134
- "stream": "stdout",
135
- "contains": "PASS",
136
- "intent": "Assert stdout or stderr captured from a command node output"
137
- }
138
- }
139
- ]
140
- },
141
- "app.status": {
142
- "description": "Report the core checkout shape and headless compatibility mode for the perps controller.",
143
- "examples": [
536
+ "examples": [
537
+ {
538
+ "description": "Read every live open order for the configured account",
539
+ "node": {
540
+ "action": "metamask.perps.read_orders",
541
+ "mode": "all",
542
+ "intent": "Read Perps open orders from the headless controller"
543
+ }
544
+ }
545
+ ],
546
+ "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or controller output.",
547
+ "safety_notes": "Read-only HyperLiquid testnet HTTP query; must not inject controller/UI state to fabricate proof."
548
+ },
144
549
  {
145
- "node": {
146
- "action": "app.status",
147
- "intent": "Report the resolved core checkout status",
148
- "next": "done"
149
- }
150
- }
151
- ]
152
- }
153
- },
154
- "custom_actions": [
155
- {
156
- "name": "metamask.perps.read_positions",
157
- "owner": "metamask",
158
- "description": "core Read live Perps positions directly from the perps controller (HyperLiquid testnet, HTTP read-only) and return the selected subset.",
159
- "schema": {
160
- "type": "object",
161
- "properties": {
162
- "action": {
163
- "const": "metamask.perps.read_positions"
164
- },
165
- "market": {
166
- "type": "string",
167
- "description": "Single market symbol, e.g. BTC or ETH. Alias: symbol."
168
- },
169
- "symbol": {
170
- "type": "string",
171
- "description": "Alias for market."
172
- },
173
- "markets": {
174
- "type": "array",
175
- "items": {
176
- "type": "string"
177
- },
178
- "description": "Explicit list of market symbols."
179
- },
180
- "symbols": {
181
- "type": "array",
182
- "items": {
183
- "type": "string"
550
+ "name": "metamask.perps.read_account",
551
+ "owner": "metamask",
552
+ "description": "core Read live Perps account state (balances, margin) directly from the perps controller (HyperLiquid testnet, HTTP read-only).",
553
+ "schema": {
554
+ "type": "object",
555
+ "properties": {
556
+ "action": {
557
+ "const": "metamask.perps.read_account"
558
+ },
559
+ "account": {
560
+ "type": "string",
561
+ "description": "EVM address to read account state for; defaults to MM_TEST_ACCOUNT_ADDRESS."
562
+ },
563
+ "timeout_ms": {
564
+ "type": "number"
565
+ }
566
+ },
567
+ "required": [
568
+ "action"
569
+ ]
184
570
  },
185
- "description": "Alias for markets."
186
- },
187
- "mode": {
188
- "type": "string",
189
- "enum": [
190
- "matching",
191
- "all"
571
+ "examples": [
572
+ {
573
+ "description": "Read account state for the configured account",
574
+ "node": {
575
+ "action": "metamask.perps.read_account",
576
+ "intent": "Read Perps account state from the headless controller"
577
+ }
578
+ }
192
579
  ],
193
- "description": "matching selects requested symbols; all selects every live item returned by the controller."
194
- },
195
- "selector": {
196
- "type": "object",
197
- "description": "Optional structured selector with mode, symbols/markets, and side."
198
- },
199
- "side": {
200
- "type": "string",
201
- "enum": [
202
- "long",
203
- "short"
204
- ],
205
- "description": "Optional position side filter."
206
- },
207
- "account": {
208
- "type": "string",
209
- "description": "EVM address to read positions for; defaults to MM_TEST_ACCOUNT_ADDRESS."
210
- },
211
- "timeout_ms": {
212
- "type": "number"
213
- }
580
+ "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or controller output.",
581
+ "safety_notes": "Read-only HyperLiquid testnet HTTP query; must not inject controller/UI state to fabricate proof."
214
582
  },
215
- "required": [
216
- "action"
217
- ]
218
- },
219
- "examples": [
220
583
  {
221
- "description": "Read every live position for the configured account",
222
- "node": {
223
- "action": "metamask.perps.read_positions",
224
- "mode": "all",
225
- "intent": "Read Perps positions from the headless controller"
226
- }
227
- }
228
- ],
229
- "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or controller output.",
230
- "safety_notes": "Read-only HyperLiquid testnet HTTP query; must not inject controller/UI state to fabricate proof."
231
- },
232
- {
233
- "name": "metamask.perps.read_orders",
234
- "owner": "metamask",
235
- "description": "core Read live Perps open orders directly from the perps controller (HyperLiquid testnet, HTTP read-only) and return the selected subset.",
236
- "schema": {
237
- "type": "object",
238
- "properties": {
239
- "action": {
240
- "const": "metamask.perps.read_orders"
241
- },
242
- "market": {
243
- "type": "string",
244
- "description": "Single market symbol, e.g. BTC or ETH. Alias: symbol."
245
- },
246
- "symbol": {
247
- "type": "string",
248
- "description": "Alias for market."
249
- },
250
- "markets": {
251
- "type": "array",
252
- "items": {
253
- "type": "string"
254
- },
255
- "description": "Explicit list of market symbols."
256
- },
257
- "symbols": {
258
- "type": "array",
259
- "items": {
260
- "type": "string"
584
+ "name": "metamask.perps.place_order",
585
+ "owner": "metamask",
586
+ "description": "core Place a real Perps order on HyperLiquid testnet by driving the headless perps controller placeOrder() through the full signing/provider path. Testnet only Supports market (default) and resting limit orders (order_type=limit with price/offset_pct).",
587
+ "schema": {
588
+ "type": "object",
589
+ "properties": {
590
+ "action": {
591
+ "const": "metamask.perps.place_order"
592
+ },
593
+ "market": {
594
+ "type": "string",
595
+ "description": "Single market symbol, e.g. BTC or ETH. Alias: symbol."
596
+ },
597
+ "symbol": {
598
+ "type": "string",
599
+ "description": "Alias for market."
600
+ },
601
+ "markets": {
602
+ "type": "array",
603
+ "items": {
604
+ "type": "string"
605
+ },
606
+ "description": "Explicit list of market symbols (place_order requires exactly one)."
607
+ },
608
+ "symbols": {
609
+ "type": "array",
610
+ "items": {
611
+ "type": "string"
612
+ },
613
+ "description": "Alias for markets."
614
+ },
615
+ "selector": {
616
+ "type": "object",
617
+ "description": "Optional structured selector with mode, symbols/markets, and side."
618
+ },
619
+ "side": {
620
+ "type": "string",
621
+ "enum": [
622
+ "long",
623
+ "short"
624
+ ],
625
+ "description": "Order direction (default: long)."
626
+ },
627
+ "order_type": {
628
+ "type": "string",
629
+ "enum": [
630
+ "market",
631
+ "limit"
632
+ ],
633
+ "description": "market (default) fills immediately; limit places a RESTING order at price/offset_pct that does not fill. Alias: orderType."
634
+ },
635
+ "limit_price": {
636
+ "type": "string",
637
+ "description": "Absolute resting limit price (limit orders). Takes precedence over offset_pct. Alias: price/limitPrice."
638
+ },
639
+ "offset_pct": {
640
+ "type": "number",
641
+ "description": "Resting limit price as a percent offset from live mid for limit orders (e.g. -30 = 30%% below mid for a non-filling BUY; default -30 buy / +30 sell). Alias: offsetPct."
642
+ },
643
+ "amount": {
644
+ "type": "string",
645
+ "description": "USD notional alias."
646
+ },
647
+ "notional": {
648
+ "type": "string",
649
+ "description": "USD notional alias."
650
+ },
651
+ "leverage": {
652
+ "type": "number"
653
+ },
654
+ "account": {
655
+ "type": "string",
656
+ "description": "EVM address to trade for; defaults to MM_TEST_ACCOUNT_ADDRESS."
657
+ },
658
+ "timeout_ms": {
659
+ "type": "number"
660
+ }
661
+ },
662
+ "required": [
663
+ "action"
664
+ ]
261
665
  },
262
- "description": "Alias for markets."
263
- },
264
- "mode": {
265
- "type": "string",
266
- "enum": [
267
- "matching",
268
- "all"
269
- ],
270
- "description": "matching selects requested symbols; all selects every live item returned by the controller."
271
- },
272
- "selector": {
273
- "type": "object",
274
- "description": "Optional structured selector with mode, symbols/markets, and side."
275
- },
276
- "side": {
277
- "type": "string",
278
- "enum": [
279
- "long",
280
- "short"
666
+ "examples": [
667
+ {
668
+ "description": "Place a small BTC long market order on testnet",
669
+ "node": {
670
+ "action": "metamask.perps.place_order",
671
+ "market": "BTC",
672
+ "side": "long",
673
+ "notional": "11",
674
+ "leverage": 2,
675
+ "intent": "Place the requested Perps order via the headless controller",
676
+ "network": "testnet"
677
+ }
678
+ },
679
+ {
680
+ "description": "Place a resting BTC limit BUY 30%% below mid on testnet (does not fill)",
681
+ "node": {
682
+ "action": "metamask.perps.place_order",
683
+ "market": "BTC",
684
+ "side": "long",
685
+ "order_type": "limit",
686
+ "offset_pct": -30,
687
+ "notional": "11",
688
+ "leverage": 2,
689
+ "intent": "Place a resting Perps limit order via the headless controller",
690
+ "network": "testnet"
691
+ }
692
+ }
281
693
  ],
282
- "description": "Optional order side filter."
283
- },
284
- "account": {
285
- "type": "string",
286
- "description": "EVM address to read orders for; defaults to MM_TEST_ACCOUNT_ADDRESS."
287
- },
288
- "timeout_ms": {
289
- "type": "number"
290
- }
694
+ "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or controller output (order id, opened position).",
695
+ "safety_notes": "Submits a REAL signed order to HyperLiquid testnet; must not mock the signer/provider or inject controller state to fabricate proof."
291
696
  },
292
- "required": [
293
- "action"
294
- ]
295
- },
296
- "examples": [
297
697
  {
298
- "description": "Read every live open order for the configured account",
299
- "node": {
300
- "action": "metamask.perps.read_orders",
301
- "mode": "all",
302
- "intent": "Read Perps open orders from the headless controller"
303
- }
304
- }
305
- ],
306
- "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or controller output.",
307
- "safety_notes": "Read-only HyperLiquid testnet HTTP query; must not inject controller/UI state to fabricate proof."
308
- },
309
- {
310
- "name": "metamask.perps.read_account",
311
- "owner": "metamask",
312
- "description": "core Read live Perps account state (balances, margin) directly from the perps controller (HyperLiquid testnet, HTTP read-only).",
313
- "schema": {
314
- "type": "object",
315
- "properties": {
316
- "action": {
317
- "const": "metamask.perps.read_account"
318
- },
319
- "account": {
320
- "type": "string",
321
- "description": "EVM address to read account state for; defaults to MM_TEST_ACCOUNT_ADDRESS."
322
- },
323
- "timeout_ms": {
324
- "type": "number"
325
- }
698
+ "name": "metamask.perps.close_positions",
699
+ "owner": "metamask",
700
+ "description": "core Close selected live Perps positions on HyperLiquid testnet by driving the headless perps controller closePosition() through the full signing/provider path. Testnet only.",
701
+ "schema": {
702
+ "type": "object",
703
+ "properties": {
704
+ "action": {
705
+ "const": "metamask.perps.close_positions"
706
+ },
707
+ "market": {
708
+ "type": "string",
709
+ "description": "Single market symbol, e.g. BTC or ETH. Alias: symbol."
710
+ },
711
+ "symbol": {
712
+ "type": "string",
713
+ "description": "Alias for market."
714
+ },
715
+ "markets": {
716
+ "type": "array",
717
+ "items": {
718
+ "type": "string"
719
+ },
720
+ "description": "Explicit list of market symbols."
721
+ },
722
+ "symbols": {
723
+ "type": "array",
724
+ "items": {
725
+ "type": "string"
726
+ },
727
+ "description": "Alias for markets."
728
+ },
729
+ "mode": {
730
+ "type": "string",
731
+ "enum": [
732
+ "matching",
733
+ "all"
734
+ ],
735
+ "description": "matching selects requested symbols; all selects every live position."
736
+ },
737
+ "selector": {
738
+ "type": "object",
739
+ "description": "Optional structured selector with mode, symbols/markets, and side."
740
+ },
741
+ "side": {
742
+ "type": "string",
743
+ "enum": [
744
+ "long",
745
+ "short"
746
+ ],
747
+ "description": "Optional position side filter."
748
+ },
749
+ "account": {
750
+ "type": "string",
751
+ "description": "EVM address to close for; defaults to MM_TEST_ACCOUNT_ADDRESS."
752
+ },
753
+ "timeout_ms": {
754
+ "type": "number"
755
+ }
756
+ },
757
+ "required": [
758
+ "action"
759
+ ]
760
+ },
761
+ "examples": [
762
+ {
763
+ "description": "Close the BTC position on testnet",
764
+ "node": {
765
+ "action": "metamask.perps.close_positions",
766
+ "market": "BTC",
767
+ "intent": "Close selected Perps positions via the headless controller",
768
+ "network": "testnet"
769
+ }
770
+ }
771
+ ],
772
+ "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or controller output (positions absent after close).",
773
+ "safety_notes": "Submits REAL signed close orders to HyperLiquid testnet; must not mock the signer/provider or inject controller state to fabricate proof."
326
774
  },
327
- "required": [
328
- "action"
329
- ]
330
- },
331
- "examples": [
332
775
  {
333
- "description": "Read account state for the configured account",
334
- "node": {
335
- "action": "metamask.perps.read_account",
336
- "intent": "Read Perps account state from the headless controller"
337
- }
338
- }
339
- ],
340
- "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or controller output.",
341
- "safety_notes": "Read-only HyperLiquid testnet HTTP query; must not inject controller/UI state to fabricate proof."
342
- },
343
- {
344
- "name": "metamask.perps.place_order",
345
- "owner": "metamask",
346
- "description": "core Place a real Perps order on HyperLiquid testnet by driving the headless perps controller placeOrder() through the full signing/provider path. Testnet only Supports market (default) and resting limit orders (order_type=limit with price/offset_pct).",
347
- "schema": {
348
- "type": "object",
349
- "properties": {
350
- "action": {
351
- "const": "metamask.perps.place_order"
352
- },
353
- "market": {
354
- "type": "string",
355
- "description": "Single market symbol, e.g. BTC or ETH. Alias: symbol."
356
- },
357
- "symbol": {
358
- "type": "string",
359
- "description": "Alias for market."
360
- },
361
- "markets": {
362
- "type": "array",
363
- "items": {
364
- "type": "string"
365
- },
366
- "description": "Explicit list of market symbols (place_order requires exactly one)."
367
- },
368
- "symbols": {
369
- "type": "array",
370
- "items": {
371
- "type": "string"
776
+ "name": "metamask.perps.assert_positions",
777
+ "owner": "metamask",
778
+ "description": "core Assert selected live Perps positions are present or absent via the headless controller standalone read path. Throws on mismatch.",
779
+ "schema": {
780
+ "type": "object",
781
+ "properties": {
782
+ "action": {
783
+ "const": "metamask.perps.assert_positions"
784
+ },
785
+ "market": {
786
+ "type": "string",
787
+ "description": "Single market symbol, e.g. BTC or ETH. Alias: symbol."
788
+ },
789
+ "symbol": {
790
+ "type": "string",
791
+ "description": "Alias for market."
792
+ },
793
+ "markets": {
794
+ "type": "array",
795
+ "items": {
796
+ "type": "string"
797
+ },
798
+ "description": "Explicit list of market symbols."
799
+ },
800
+ "symbols": {
801
+ "type": "array",
802
+ "items": {
803
+ "type": "string"
804
+ },
805
+ "description": "Alias for markets."
806
+ },
807
+ "mode": {
808
+ "type": "string",
809
+ "enum": [
810
+ "matching",
811
+ "all"
812
+ ],
813
+ "description": "matching selects requested symbols; all selects every live position."
814
+ },
815
+ "selector": {
816
+ "type": "object",
817
+ "description": "Optional structured selector with mode, symbols/markets, and side."
818
+ },
819
+ "side": {
820
+ "type": "string",
821
+ "enum": [
822
+ "long",
823
+ "short"
824
+ ],
825
+ "description": "Optional position side filter."
826
+ },
827
+ "state": {
828
+ "type": "string",
829
+ "enum": [
830
+ "none",
831
+ "absent",
832
+ "closed",
833
+ "open",
834
+ "present"
835
+ ],
836
+ "description": "Expected selected position state."
837
+ },
838
+ "account": {
839
+ "type": "string",
840
+ "description": "EVM address to read for; defaults to MM_TEST_ACCOUNT_ADDRESS."
841
+ },
842
+ "timeout_ms": {
843
+ "type": "number"
844
+ }
845
+ },
846
+ "required": [
847
+ "action"
848
+ ]
372
849
  },
373
- "description": "Alias for markets."
374
- },
375
- "selector": {
376
- "type": "object",
377
- "description": "Optional structured selector with mode, symbols/markets, and side."
378
- },
379
- "side": {
380
- "type": "string",
381
- "enum": [
382
- "long",
383
- "short"
384
- ],
385
- "description": "Order direction (default: long)."
386
- },
387
- "order_type": {
388
- "type": "string",
389
- "enum": [
390
- "market",
391
- "limit"
850
+ "examples": [
851
+ {
852
+ "description": "Assert BTC position is open",
853
+ "node": {
854
+ "action": "metamask.perps.assert_positions",
855
+ "state": "open",
856
+ "market": "BTC",
857
+ "intent": "Assert the expected Perps position state via the headless controller"
858
+ }
859
+ }
392
860
  ],
393
- "description": "market (default) fills immediately; limit places a RESTING order at price/offset_pct that does not fill. Alias: orderType."
394
- },
395
- "limit_price": {
396
- "type": "string",
397
- "description": "Absolute resting limit price (limit orders). Takes precedence over offset_pct. Alias: price/limitPrice."
398
- },
399
- "offset_pct": {
400
- "type": "number",
401
- "description": "Resting limit price as a percent offset from live mid for limit orders (e.g. -30 = 30%% below mid for a non-filling BUY; default -30 buy / +30 sell). Alias: offsetPct."
402
- },
403
- "amount": {
404
- "type": "string",
405
- "description": "USD notional alias."
406
- },
407
- "notional": {
408
- "type": "string",
409
- "description": "USD notional alias."
410
- },
411
- "leverage": {
412
- "type": "number"
413
- },
414
- "account": {
415
- "type": "string",
416
- "description": "EVM address to trade for; defaults to MM_TEST_ACCOUNT_ADDRESS."
417
- },
418
- "timeout_ms": {
419
- "type": "number"
420
- }
861
+ "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or controller output.",
862
+ "safety_notes": "Read-only HyperLiquid testnet HTTP query; must not inject controller state to fabricate proof."
421
863
  },
422
- "required": [
423
- "action"
424
- ]
425
- },
426
- "examples": [
427
864
  {
428
- "description": "Place a small BTC long market order on testnet",
429
- "node": {
430
- "action": "metamask.perps.place_order",
431
- "market": "BTC",
432
- "side": "long",
433
- "notional": "11",
434
- "leverage": 2,
435
- "intent": "Place the requested Perps order via the headless controller",
436
- "network": "testnet"
437
- }
865
+ "name": "metamask.perps.ensure_positions",
866
+ "owner": "metamask",
867
+ "description": "core Higher-level wrapper that reads selected positions, places or closes via the headless controller as needed, then asserts the final position state. Testnet only.",
868
+ "schema": {
869
+ "type": "object",
870
+ "properties": {
871
+ "action": {
872
+ "const": "metamask.perps.ensure_positions"
873
+ },
874
+ "market": {
875
+ "type": "string",
876
+ "description": "Single market symbol, e.g. BTC or ETH. Alias: symbol."
877
+ },
878
+ "symbol": {
879
+ "type": "string",
880
+ "description": "Alias for market."
881
+ },
882
+ "markets": {
883
+ "type": "array",
884
+ "items": {
885
+ "type": "string"
886
+ },
887
+ "description": "Explicit list of market symbols."
888
+ },
889
+ "symbols": {
890
+ "type": "array",
891
+ "items": {
892
+ "type": "string"
893
+ },
894
+ "description": "Alias for markets."
895
+ },
896
+ "mode": {
897
+ "type": "string",
898
+ "enum": [
899
+ "matching",
900
+ "all"
901
+ ],
902
+ "description": "matching selects requested symbols; all selects every live position."
903
+ },
904
+ "selector": {
905
+ "type": "object",
906
+ "description": "Optional structured selector with mode, symbols/markets, and side."
907
+ },
908
+ "side": {
909
+ "type": "string",
910
+ "enum": [
911
+ "long",
912
+ "short"
913
+ ],
914
+ "description": "Order/position direction (default: long when placing)."
915
+ },
916
+ "state": {
917
+ "type": "string",
918
+ "enum": [
919
+ "none",
920
+ "absent",
921
+ "closed",
922
+ "open",
923
+ "present"
924
+ ],
925
+ "description": "Desired selected position state to converge to."
926
+ },
927
+ "amount": {
928
+ "type": "string",
929
+ "description": "USD notional alias (used when placing to reach state open)."
930
+ },
931
+ "notional": {
932
+ "type": "string",
933
+ "description": "USD notional alias."
934
+ },
935
+ "leverage": {
936
+ "type": "number"
937
+ },
938
+ "account": {
939
+ "type": "string",
940
+ "description": "EVM address to trade for; defaults to MM_TEST_ACCOUNT_ADDRESS."
941
+ },
942
+ "timeout_ms": {
943
+ "type": "number"
944
+ }
945
+ },
946
+ "required": [
947
+ "action"
948
+ ]
949
+ },
950
+ "examples": [
951
+ {
952
+ "description": "Ensure no BTC position before the proof window",
953
+ "node": {
954
+ "action": "metamask.perps.ensure_positions",
955
+ "state": "none",
956
+ "market": "BTC",
957
+ "intent": "Converge Perps positions to the requested state via the headless controller",
958
+ "network": "testnet"
959
+ }
960
+ }
961
+ ],
962
+ "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or controller output.",
963
+ "safety_notes": "May submit REAL signed orders to HyperLiquid testnet; must not mock the signer/provider or inject controller state to fabricate proof."
438
964
  },
439
965
  {
440
- "description": "Place a resting BTC limit BUY 30%% below mid on testnet (does not fill)",
441
- "node": {
442
- "action": "metamask.perps.place_order",
443
- "market": "BTC",
444
- "side": "long",
445
- "order_type": "limit",
446
- "offset_pct": -30,
447
- "notional": "11",
448
- "leverage": 2,
449
- "intent": "Place a resting Perps limit order via the headless controller",
450
- "network": "testnet"
451
- }
452
- }
453
- ],
454
- "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or controller output (order id, opened position).",
455
- "safety_notes": "Submits a REAL signed order to HyperLiquid testnet; must not mock the signer/provider or inject controller state to fabricate proof."
456
- },
457
- {
458
- "name": "metamask.perps.close_positions",
459
- "owner": "metamask",
460
- "description": "core Close selected live Perps positions on HyperLiquid testnet by driving the headless perps controller closePosition() through the full signing/provider path. Testnet only.",
461
- "schema": {
462
- "type": "object",
463
- "properties": {
464
- "action": {
465
- "const": "metamask.perps.close_positions"
466
- },
467
- "market": {
468
- "type": "string",
469
- "description": "Single market symbol, e.g. BTC or ETH. Alias: symbol."
470
- },
471
- "symbol": {
472
- "type": "string",
473
- "description": "Alias for market."
474
- },
475
- "markets": {
476
- "type": "array",
477
- "items": {
478
- "type": "string"
966
+ "name": "metamask.perps.close_orders",
967
+ "owner": "metamask",
968
+ "description": "core 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.",
969
+ "schema": {
970
+ "type": "object",
971
+ "properties": {
972
+ "action": {
973
+ "const": "metamask.perps.close_orders"
974
+ },
975
+ "market": {
976
+ "type": "string",
977
+ "description": "Single market symbol, e.g. BTC or ETH. Alias: symbol."
978
+ },
979
+ "symbol": {
980
+ "type": "string",
981
+ "description": "Alias for market."
982
+ },
983
+ "markets": {
984
+ "type": "array",
985
+ "items": {
986
+ "type": "string"
987
+ },
988
+ "description": "Explicit list of market symbols."
989
+ },
990
+ "symbols": {
991
+ "type": "array",
992
+ "items": {
993
+ "type": "string"
994
+ },
995
+ "description": "Alias for markets."
996
+ },
997
+ "mode": {
998
+ "type": "string",
999
+ "enum": [
1000
+ "matching",
1001
+ "all"
1002
+ ],
1003
+ "description": "matching selects requested symbols; all selects every live item returned by the product."
1004
+ },
1005
+ "selector": {
1006
+ "type": "object",
1007
+ "description": "Optional structured selector with mode, symbols/markets, and side."
1008
+ },
1009
+ "side": {
1010
+ "type": "string",
1011
+ "enum": [
1012
+ "long",
1013
+ "short"
1014
+ ],
1015
+ "description": "Optional position/order side filter."
1016
+ },
1017
+ "timeout_ms": {
1018
+ "type": "number"
1019
+ }
1020
+ },
1021
+ "required": [
1022
+ "action"
1023
+ ]
479
1024
  },
480
- "description": "Explicit list of market symbols."
481
- },
482
- "symbols": {
483
- "type": "array",
484
- "items": {
485
- "type": "string"
486
- },
487
- "description": "Alias for markets."
488
- },
489
- "mode": {
490
- "type": "string",
491
- "enum": [
492
- "matching",
493
- "all"
494
- ],
495
- "description": "matching selects requested symbols; all selects every live position."
496
- },
497
- "selector": {
498
- "type": "object",
499
- "description": "Optional structured selector with mode, symbols/markets, and side."
500
- },
501
- "side": {
502
- "type": "string",
503
- "enum": [
504
- "long",
505
- "short"
1025
+ "examples": [
1026
+ {
1027
+ "description": "Cancel every open order",
1028
+ "node": {
1029
+ "action": "metamask.perps.close_orders",
1030
+ "mode": "all",
1031
+ "intent": "Cancel selected Perps orders before continuing",
1032
+ "network": "testnet"
1033
+ }
1034
+ },
1035
+ {
1036
+ "description": "Cancel BTC orders only",
1037
+ "node": {
1038
+ "action": "metamask.perps.close_orders",
1039
+ "market": "BTC",
1040
+ "intent": "Cancel selected Perps orders before continuing",
1041
+ "network": "testnet"
1042
+ }
1043
+ }
506
1044
  ],
507
- "description": "Optional position side filter."
508
- },
509
- "account": {
510
- "type": "string",
511
- "description": "EVM address to close for; defaults to MM_TEST_ACCOUNT_ADDRESS."
512
- },
513
- "timeout_ms": {
514
- "type": "number"
515
- }
1045
+ "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
1046
+ "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof."
516
1047
  },
517
- "required": [
518
- "action"
519
- ]
520
- },
521
- "examples": [
522
1048
  {
523
- "description": "Close the BTC position on testnet",
524
- "node": {
525
- "action": "metamask.perps.close_positions",
526
- "market": "BTC",
527
- "intent": "Close selected Perps positions via the headless controller",
528
- "network": "testnet"
529
- }
530
- }
531
- ],
532
- "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or controller output (positions absent after close).",
533
- "safety_notes": "Submits REAL signed close orders to HyperLiquid testnet; must not mock the signer/provider or inject controller state to fabricate proof."
534
- },
535
- {
536
- "name": "metamask.perps.assert_positions",
537
- "owner": "metamask",
538
- "description": "core Assert selected live Perps positions are present or absent via the headless controller standalone read path. Throws on mismatch.",
539
- "schema": {
540
- "type": "object",
541
- "properties": {
542
- "action": {
543
- "const": "metamask.perps.assert_positions"
544
- },
545
- "market": {
546
- "type": "string",
547
- "description": "Single market symbol, e.g. BTC or ETH. Alias: symbol."
548
- },
549
- "symbol": {
550
- "type": "string",
551
- "description": "Alias for market."
552
- },
553
- "markets": {
554
- "type": "array",
555
- "items": {
556
- "type": "string"
557
- },
558
- "description": "Explicit list of market symbols."
559
- },
560
- "symbols": {
561
- "type": "array",
562
- "items": {
563
- "type": "string"
1049
+ "name": "metamask.perps.assert_orders",
1050
+ "owner": "metamask",
1051
+ "description": "core Assert selected live Perps open orders are present or absent.",
1052
+ "schema": {
1053
+ "type": "object",
1054
+ "properties": {
1055
+ "action": {
1056
+ "const": "metamask.perps.assert_orders"
1057
+ },
1058
+ "market": {
1059
+ "type": "string",
1060
+ "description": "Single market symbol, e.g. BTC or ETH. Alias: symbol."
1061
+ },
1062
+ "symbol": {
1063
+ "type": "string",
1064
+ "description": "Alias for market."
1065
+ },
1066
+ "markets": {
1067
+ "type": "array",
1068
+ "items": {
1069
+ "type": "string"
1070
+ },
1071
+ "description": "Explicit list of market symbols."
1072
+ },
1073
+ "symbols": {
1074
+ "type": "array",
1075
+ "items": {
1076
+ "type": "string"
1077
+ },
1078
+ "description": "Alias for markets."
1079
+ },
1080
+ "mode": {
1081
+ "type": "string",
1082
+ "enum": [
1083
+ "matching",
1084
+ "all"
1085
+ ],
1086
+ "description": "matching selects requested symbols; all selects every live item returned by the product."
1087
+ },
1088
+ "selector": {
1089
+ "type": "object",
1090
+ "description": "Optional structured selector with mode, symbols/markets, and side."
1091
+ },
1092
+ "side": {
1093
+ "type": "string",
1094
+ "enum": [
1095
+ "long",
1096
+ "short"
1097
+ ],
1098
+ "description": "Optional position/order side filter."
1099
+ },
1100
+ "timeout_ms": {
1101
+ "type": "number"
1102
+ },
1103
+ "state": {
1104
+ "type": "string",
1105
+ "enum": [
1106
+ "none",
1107
+ "absent",
1108
+ "closed",
1109
+ "open",
1110
+ "present"
1111
+ ],
1112
+ "description": "Desired/expected selected collection state."
1113
+ }
1114
+ },
1115
+ "required": [
1116
+ "action"
1117
+ ]
564
1118
  },
565
- "description": "Alias for markets."
566
- },
567
- "mode": {
568
- "type": "string",
569
- "enum": [
570
- "matching",
571
- "all"
572
- ],
573
- "description": "matching selects requested symbols; all selects every live position."
574
- },
575
- "selector": {
576
- "type": "object",
577
- "description": "Optional structured selector with mode, symbols/markets, and side."
578
- },
579
- "side": {
580
- "type": "string",
581
- "enum": [
582
- "long",
583
- "short"
1119
+ "examples": [
1120
+ {
1121
+ "description": "Assert no BTC open orders",
1122
+ "node": {
1123
+ "action": "metamask.perps.assert_orders",
1124
+ "state": "none",
1125
+ "market": "BTC",
1126
+ "intent": "Assert the expected Perps open order state"
1127
+ }
1128
+ }
584
1129
  ],
585
- "description": "Optional position side filter."
586
- },
587
- "state": {
588
- "type": "string",
589
- "enum": [
590
- "none",
591
- "absent",
592
- "closed",
593
- "open",
594
- "present"
595
- ],
596
- "description": "Expected selected position state."
597
- },
598
- "account": {
599
- "type": "string",
600
- "description": "EVM address to read for; defaults to MM_TEST_ACCOUNT_ADDRESS."
601
- },
602
- "timeout_ms": {
603
- "type": "number"
604
- }
1130
+ "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
1131
+ "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof."
605
1132
  },
606
- "required": [
607
- "action"
608
- ]
609
- },
610
- "examples": [
611
1133
  {
612
- "description": "Assert BTC position is open",
613
- "node": {
614
- "action": "metamask.perps.assert_positions",
615
- "state": "open",
616
- "market": "BTC",
617
- "intent": "Assert the expected Perps position state via the headless controller"
618
- }
619
- }
620
- ],
621
- "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or controller output.",
622
- "safety_notes": "Read-only HyperLiquid testnet HTTP query; must not inject controller state to fabricate proof."
623
- },
624
- {
625
- "name": "metamask.perps.ensure_positions",
626
- "owner": "metamask",
627
- "description": "core Higher-level wrapper that reads selected positions, places or closes via the headless controller as needed, then asserts the final position state. Testnet only.",
628
- "schema": {
629
- "type": "object",
630
- "properties": {
631
- "action": {
632
- "const": "metamask.perps.ensure_positions"
633
- },
634
- "market": {
635
- "type": "string",
636
- "description": "Single market symbol, e.g. BTC or ETH. Alias: symbol."
637
- },
638
- "symbol": {
639
- "type": "string",
640
- "description": "Alias for market."
641
- },
642
- "markets": {
643
- "type": "array",
644
- "items": {
645
- "type": "string"
1134
+ "name": "metamask.perps.ensure_orders",
1135
+ "owner": "metamask",
1136
+ "description": "core 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.",
1137
+ "schema": {
1138
+ "type": "object",
1139
+ "properties": {
1140
+ "action": {
1141
+ "const": "metamask.perps.ensure_orders"
1142
+ },
1143
+ "market": {
1144
+ "type": "string",
1145
+ "description": "Single market symbol, e.g. BTC or ETH. Alias: symbol."
1146
+ },
1147
+ "symbol": {
1148
+ "type": "string",
1149
+ "description": "Alias for market."
1150
+ },
1151
+ "markets": {
1152
+ "type": "array",
1153
+ "items": {
1154
+ "type": "string"
1155
+ },
1156
+ "description": "Explicit list of market symbols."
1157
+ },
1158
+ "symbols": {
1159
+ "type": "array",
1160
+ "items": {
1161
+ "type": "string"
1162
+ },
1163
+ "description": "Alias for markets."
1164
+ },
1165
+ "mode": {
1166
+ "type": "string",
1167
+ "enum": [
1168
+ "matching",
1169
+ "all"
1170
+ ],
1171
+ "description": "matching selects requested symbols; all selects every live item returned by the product."
1172
+ },
1173
+ "selector": {
1174
+ "type": "object",
1175
+ "description": "Optional structured selector with mode, symbols/markets, and side."
1176
+ },
1177
+ "side": {
1178
+ "type": "string",
1179
+ "enum": [
1180
+ "long",
1181
+ "short"
1182
+ ],
1183
+ "description": "Optional position/order side filter."
1184
+ },
1185
+ "timeout_ms": {
1186
+ "type": "number"
1187
+ },
1188
+ "state": {
1189
+ "type": "string",
1190
+ "enum": [
1191
+ "none",
1192
+ "absent",
1193
+ "closed",
1194
+ "open",
1195
+ "present"
1196
+ ],
1197
+ "description": "Desired/expected selected collection state."
1198
+ }
1199
+ },
1200
+ "required": [
1201
+ "action"
1202
+ ]
646
1203
  },
647
- "description": "Explicit list of market symbols."
648
- },
649
- "symbols": {
650
- "type": "array",
651
- "items": {
652
- "type": "string"
653
- },
654
- "description": "Alias for markets."
655
- },
656
- "mode": {
657
- "type": "string",
658
- "enum": [
659
- "matching",
660
- "all"
661
- ],
662
- "description": "matching selects requested symbols; all selects every live position."
663
- },
664
- "selector": {
665
- "type": "object",
666
- "description": "Optional structured selector with mode, symbols/markets, and side."
667
- },
668
- "side": {
669
- "type": "string",
670
- "enum": [
671
- "long",
672
- "short"
673
- ],
674
- "description": "Order/position direction (default: long when placing)."
675
- },
676
- "state": {
677
- "type": "string",
678
- "enum": [
679
- "none",
680
- "absent",
681
- "closed",
682
- "open",
683
- "present"
1204
+ "examples": [
1205
+ {
1206
+ "description": "Ensure no open orders before proof window",
1207
+ "node": {
1208
+ "action": "metamask.perps.ensure_orders",
1209
+ "state": "none",
1210
+ "mode": "all",
1211
+ "intent": "Converge Perps orders to the requested state",
1212
+ "network": "testnet"
1213
+ }
1214
+ }
684
1215
  ],
685
- "description": "Desired selected position state to converge to."
686
- },
687
- "amount": {
688
- "type": "string",
689
- "description": "USD notional alias (used when placing to reach state open)."
690
- },
691
- "notional": {
692
- "type": "string",
693
- "description": "USD notional alias."
694
- },
695
- "leverage": {
696
- "type": "number"
697
- },
698
- "account": {
699
- "type": "string",
700
- "description": "EVM address to trade for; defaults to MM_TEST_ACCOUNT_ADDRESS."
701
- },
702
- "timeout_ms": {
703
- "type": "number"
704
- }
1216
+ "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
1217
+ "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof."
705
1218
  },
706
- "required": [
707
- "action"
708
- ]
709
- },
710
- "examples": [
711
1219
  {
712
- "description": "Ensure no BTC position before the proof window",
713
- "node": {
714
- "action": "metamask.perps.ensure_positions",
715
- "state": "none",
716
- "market": "BTC",
717
- "intent": "Converge Perps positions to the requested state via the headless controller",
718
- "network": "testnet"
719
- }
720
- }
721
- ],
722
- "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or controller output.",
723
- "safety_notes": "May submit REAL signed orders to HyperLiquid testnet; must not mock the signer/provider or inject controller state to fabricate proof."
724
- },
725
- {
726
- "name": "metamask.perps.close_orders",
727
- "owner": "metamask",
728
- "description": "core 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.",
729
- "schema": {
730
- "type": "object",
731
- "properties": {
732
- "action": {
733
- "const": "metamask.perps.close_orders"
734
- },
735
- "market": {
736
- "type": "string",
737
- "description": "Single market symbol, e.g. BTC or ETH. Alias: symbol."
738
- },
739
- "symbol": {
740
- "type": "string",
741
- "description": "Alias for market."
742
- },
743
- "markets": {
744
- "type": "array",
745
- "items": {
746
- "type": "string"
747
- },
748
- "description": "Explicit list of market symbols."
749
- },
750
- "symbols": {
751
- "type": "array",
752
- "items": {
753
- "type": "string"
1220
+ "name": "metamask.perps.start_state",
1221
+ "owner": "metamask",
1222
+ "description": "core Default to testnet for Perps mutations; mainnet is read-only unless explicitly requested.Recommended configurable Perps start state that composes reusable domain operations before the proof window. Headless: converges POSITION and ORDER baselines on testnet via the controller; no navigation/page/HUD.",
1223
+ "schema": {
1224
+ "type": "object",
1225
+ "properties": {
1226
+ "action": {
1227
+ "const": "metamask.perps.start_state"
1228
+ },
1229
+ "profile": {
1230
+ "type": "string",
1231
+ "description": "Optional profile such as clean_market_testnet, open_position_testnet, open_order_testnet, or provider_mainnet_readonly."
1232
+ },
1233
+ "account": {
1234
+ "type": "string"
1235
+ },
1236
+ "provider": {
1237
+ "type": "string",
1238
+ "description": "Requested Perps provider, e.g. hyperliquid."
1239
+ },
1240
+ "network": {
1241
+ "type": "string",
1242
+ "enum": [
1243
+ "testnet",
1244
+ "mainnet"
1245
+ ]
1246
+ },
1247
+ "market": {
1248
+ "type": "string"
1249
+ },
1250
+ "symbol": {
1251
+ "type": "string"
1252
+ },
1253
+ "markets": {
1254
+ "type": "array",
1255
+ "items": {
1256
+ "type": "string"
1257
+ }
1258
+ },
1259
+ "symbols": {
1260
+ "type": "array",
1261
+ "items": {
1262
+ "type": "string"
1263
+ }
1264
+ },
1265
+ "positions": {
1266
+ "oneOf": [
1267
+ {
1268
+ "type": "object"
1269
+ },
1270
+ {
1271
+ "const": false
1272
+ }
1273
+ ],
1274
+ "description": "Desired position baseline or false to skip."
1275
+ },
1276
+ "orders": {
1277
+ "oneOf": [
1278
+ {
1279
+ "type": "object"
1280
+ },
1281
+ {
1282
+ "const": false
1283
+ }
1284
+ ],
1285
+ "description": "Desired open-order baseline or false to skip."
1286
+ },
1287
+ "timeout_ms": {
1288
+ "type": "number"
1289
+ }
1290
+ },
1291
+ "required": [
1292
+ "action"
1293
+ ]
754
1294
  },
755
- "description": "Alias for markets."
756
- },
757
- "mode": {
758
- "type": "string",
759
- "enum": [
760
- "matching",
761
- "all"
1295
+ "examples": [
1296
+ {
1297
+ "description": "Clean BTC testnet market state",
1298
+ "node": {
1299
+ "action": "metamask.perps.start_state",
1300
+ "profile": "clean_market_testnet",
1301
+ "market": "BTC",
1302
+ "positions": {
1303
+ "state": "none"
1304
+ },
1305
+ "orders": {
1306
+ "state": "none"
1307
+ },
1308
+ "intent": "Prepare the Perps start state for the proof",
1309
+ "network": "testnet"
1310
+ }
1311
+ },
1312
+ {
1313
+ "description": "Start with an open ETH long position",
1314
+ "node": {
1315
+ "action": "metamask.perps.start_state",
1316
+ "profile": "open_position_testnet",
1317
+ "market": "ETH",
1318
+ "side": "long",
1319
+ "positions": {
1320
+ "state": "open",
1321
+ "notional": "10",
1322
+ "leverage": 2
1323
+ },
1324
+ "intent": "Prepare the Perps start state for the proof",
1325
+ "network": "testnet"
1326
+ }
1327
+ }
762
1328
  ],
763
- "description": "matching selects requested symbols; all selects every live item returned by the product."
764
- },
765
- "selector": {
766
- "type": "object",
767
- "description": "Optional structured selector with mode, symbols/markets, and side."
768
- },
769
- "side": {
770
- "type": "string",
771
- "enum": [
772
- "long",
773
- "short"
1329
+ "proof_effect": "Runs in setup/teardown phase and must be visible in trace.json; proof videos can keep this as trace-only setup.",
1330
+ "safety_notes": "Composes supported app/API paths and must not mutate React/Redux/MobX/DOM/local storage to fabricate proof."
1331
+ },
1332
+ {
1333
+ "name": "metamask.perps.teardown_state",
1334
+ "owner": "metamask",
1335
+ "description": "core Recommended configurable Perps teardown state that restores the selected environment after proof. Headless: converges POSITION and ORDER baselines on testnet via the controller; no navigation/page/HUD.",
1336
+ "schema": {
1337
+ "type": "object",
1338
+ "properties": {
1339
+ "action": {
1340
+ "const": "metamask.perps.teardown_state"
1341
+ },
1342
+ "profile": {
1343
+ "type": "string",
1344
+ "description": "Optional profile such as clean_market_testnet, open_position_testnet, open_order_testnet, or provider_mainnet_readonly."
1345
+ },
1346
+ "account": {
1347
+ "type": "string"
1348
+ },
1349
+ "provider": {
1350
+ "type": "string",
1351
+ "description": "Requested Perps provider, e.g. hyperliquid."
1352
+ },
1353
+ "network": {
1354
+ "type": "string",
1355
+ "enum": [
1356
+ "testnet",
1357
+ "mainnet"
1358
+ ]
1359
+ },
1360
+ "market": {
1361
+ "type": "string"
1362
+ },
1363
+ "symbol": {
1364
+ "type": "string"
1365
+ },
1366
+ "markets": {
1367
+ "type": "array",
1368
+ "items": {
1369
+ "type": "string"
1370
+ }
1371
+ },
1372
+ "symbols": {
1373
+ "type": "array",
1374
+ "items": {
1375
+ "type": "string"
1376
+ }
1377
+ },
1378
+ "positions": {
1379
+ "oneOf": [
1380
+ {
1381
+ "type": "object"
1382
+ },
1383
+ {
1384
+ "const": false
1385
+ }
1386
+ ],
1387
+ "description": "Desired position baseline or false to skip."
1388
+ },
1389
+ "orders": {
1390
+ "oneOf": [
1391
+ {
1392
+ "type": "object"
1393
+ },
1394
+ {
1395
+ "const": false
1396
+ }
1397
+ ],
1398
+ "description": "Desired open-order baseline or false to skip."
1399
+ },
1400
+ "timeout_ms": {
1401
+ "type": "number"
1402
+ }
1403
+ },
1404
+ "required": [
1405
+ "action"
1406
+ ]
1407
+ },
1408
+ "examples": [
1409
+ {
1410
+ "description": "Return BTC test account to clean state",
1411
+ "node": {
1412
+ "action": "metamask.perps.teardown_state",
1413
+ "market": "BTC",
1414
+ "positions": {
1415
+ "state": "none"
1416
+ },
1417
+ "orders": {
1418
+ "state": "none"
1419
+ },
1420
+ "intent": "Restore Perps state after the proof"
1421
+ }
1422
+ },
1423
+ {
1424
+ "description": "Teardown only state created by selected market",
1425
+ "node": {
1426
+ "action": "metamask.perps.teardown_state",
1427
+ "market": "BTC",
1428
+ "intent": "Restore Perps state after the proof"
1429
+ }
1430
+ }
774
1431
  ],
775
- "description": "Optional position/order side filter."
776
- },
777
- "timeout_ms": {
778
- "type": "number"
779
- }
1432
+ "proof_effect": "Runs in setup/teardown phase and must be visible in trace.json; proof videos can keep this as trace-only setup.",
1433
+ "safety_notes": "Composes supported app/API paths and must not mutate React/Redux/MobX/DOM/local storage to fabricate proof."
1434
+ }
1435
+ ],
1436
+ "state_refs": [
1437
+ {
1438
+ "name": "perps.positions",
1439
+ "description": "Normalized Perps position state read from the headless controller."
1440
+ }
1441
+ ],
1442
+ "pre_conditions": [
1443
+ {
1444
+ "id": "no-mid-recipe-state-injection",
1445
+ "description": "Recipes must drive the real perps controller read path for proof state."
1446
+ }
1447
+ ],
1448
+ "native_bindings": [
1449
+ {
1450
+ "action": "command",
1451
+ "implementation": "@farmslot/recipe-harness/adapters/core"
780
1452
  },
781
- "required": [
782
- "action"
783
- ]
784
- },
785
- "examples": [
786
1453
  {
787
- "description": "Cancel every open order",
788
- "node": {
789
- "action": "metamask.perps.close_orders",
790
- "mode": "all",
791
- "intent": "Cancel selected Perps orders before continuing",
792
- "network": "testnet"
793
- }
1454
+ "action": "wait",
1455
+ "implementation": "@farmslot/recipe-harness/adapters/core"
794
1456
  },
795
1457
  {
796
- "description": "Cancel BTC orders only",
797
- "node": {
798
- "action": "metamask.perps.close_orders",
799
- "market": "BTC",
800
- "intent": "Cancel selected Perps orders before continuing",
801
- "network": "testnet"
802
- }
803
- }
804
- ],
805
- "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
806
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof."
807
- },
808
- {
809
- "name": "metamask.perps.assert_orders",
810
- "owner": "metamask",
811
- "description": "core Assert selected live Perps open orders are present or absent.",
812
- "schema": {
813
- "type": "object",
814
- "properties": {
815
- "action": {
816
- "const": "metamask.perps.assert_orders"
817
- },
818
- "market": {
819
- "type": "string",
820
- "description": "Single market symbol, e.g. BTC or ETH. Alias: symbol."
821
- },
822
- "symbol": {
823
- "type": "string",
824
- "description": "Alias for market."
825
- },
826
- "markets": {
827
- "type": "array",
828
- "items": {
829
- "type": "string"
830
- },
831
- "description": "Explicit list of market symbols."
832
- },
833
- "symbols": {
834
- "type": "array",
835
- "items": {
836
- "type": "string"
837
- },
838
- "description": "Alias for markets."
839
- },
840
- "mode": {
841
- "type": "string",
842
- "enum": [
843
- "matching",
844
- "all"
845
- ],
846
- "description": "matching selects requested symbols; all selects every live item returned by the product."
847
- },
848
- "selector": {
849
- "type": "object",
850
- "description": "Optional structured selector with mode, symbols/markets, and side."
851
- },
852
- "side": {
853
- "type": "string",
854
- "enum": [
855
- "long",
856
- "short"
857
- ],
858
- "description": "Optional position/order side filter."
859
- },
860
- "timeout_ms": {
861
- "type": "number"
862
- },
863
- "state": {
864
- "type": "string",
865
- "enum": [
866
- "none",
867
- "absent",
868
- "closed",
869
- "open",
870
- "present"
871
- ],
872
- "description": "Desired/expected selected collection state."
873
- }
1458
+ "action": "assert_file",
1459
+ "implementation": "@farmslot/recipe-harness/adapters/core"
874
1460
  },
875
- "required": [
876
- "action"
877
- ]
878
- },
879
- "examples": [
880
1461
  {
881
- "description": "Assert no BTC open orders",
882
- "node": {
883
- "action": "metamask.perps.assert_orders",
884
- "state": "none",
885
- "market": "BTC",
886
- "intent": "Assert the expected Perps open order state"
887
- }
888
- }
889
- ],
890
- "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
891
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof."
892
- },
893
- {
894
- "name": "metamask.perps.ensure_orders",
895
- "owner": "metamask",
896
- "description": "core 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.",
897
- "schema": {
898
- "type": "object",
899
- "properties": {
900
- "action": {
901
- "const": "metamask.perps.ensure_orders"
902
- },
903
- "market": {
904
- "type": "string",
905
- "description": "Single market symbol, e.g. BTC or ETH. Alias: symbol."
906
- },
907
- "symbol": {
908
- "type": "string",
909
- "description": "Alias for market."
910
- },
911
- "markets": {
912
- "type": "array",
913
- "items": {
914
- "type": "string"
915
- },
916
- "description": "Explicit list of market symbols."
917
- },
918
- "symbols": {
919
- "type": "array",
920
- "items": {
921
- "type": "string"
922
- },
923
- "description": "Alias for markets."
924
- },
925
- "mode": {
926
- "type": "string",
927
- "enum": [
928
- "matching",
929
- "all"
930
- ],
931
- "description": "matching selects requested symbols; all selects every live item returned by the product."
932
- },
933
- "selector": {
934
- "type": "object",
935
- "description": "Optional structured selector with mode, symbols/markets, and side."
936
- },
937
- "side": {
938
- "type": "string",
939
- "enum": [
940
- "long",
941
- "short"
942
- ],
943
- "description": "Optional position/order side filter."
944
- },
945
- "timeout_ms": {
946
- "type": "number"
947
- },
948
- "state": {
949
- "type": "string",
950
- "enum": [
951
- "none",
952
- "absent",
953
- "closed",
954
- "open",
955
- "present"
956
- ],
957
- "description": "Desired/expected selected collection state."
958
- }
1462
+ "action": "assert_json",
1463
+ "implementation": "@farmslot/recipe-harness/adapters/core"
959
1464
  },
960
- "required": [
961
- "action"
962
- ]
963
- },
964
- "examples": [
965
1465
  {
966
- "description": "Ensure no open orders before proof window",
967
- "node": {
968
- "action": "metamask.perps.ensure_orders",
969
- "state": "none",
970
- "mode": "all",
971
- "intent": "Converge Perps orders to the requested state",
972
- "network": "testnet"
973
- }
974
- }
975
- ],
976
- "proof_effect": "E2E validation must record this action in trace.json; live-proof actions include liveAdapter/proofPath or target runtime output.",
977
- "safety_notes": "Must not inject mid-recipe UI/app state to fabricate proof."
978
- },
979
- {
980
- "name": "metamask.perps.start_state",
981
- "owner": "metamask",
982
- "description": "core Default to testnet for Perps mutations; mainnet is read-only unless explicitly requested.Recommended configurable Perps start state that composes reusable domain operations before the proof window. Headless: converges POSITION and ORDER baselines on testnet via the controller; no navigation/page/HUD.",
983
- "schema": {
984
- "type": "object",
985
- "properties": {
986
- "action": {
987
- "const": "metamask.perps.start_state"
988
- },
989
- "profile": {
990
- "type": "string",
991
- "description": "Optional profile such as clean_market_testnet, open_position_testnet, open_order_testnet, or provider_mainnet_readonly."
992
- },
993
- "account": {
994
- "type": "string"
995
- },
996
- "provider": {
997
- "type": "string",
998
- "description": "Requested Perps provider, e.g. hyperliquid."
999
- },
1000
- "network": {
1001
- "type": "string",
1002
- "enum": [
1003
- "testnet",
1004
- "mainnet"
1005
- ]
1006
- },
1007
- "market": {
1008
- "type": "string"
1009
- },
1010
- "symbol": {
1011
- "type": "string"
1012
- },
1013
- "markets": {
1014
- "type": "array",
1015
- "items": {
1016
- "type": "string"
1017
- }
1018
- },
1019
- "symbols": {
1020
- "type": "array",
1021
- "items": {
1022
- "type": "string"
1023
- }
1024
- },
1025
- "positions": {
1026
- "oneOf": [
1027
- {
1028
- "type": "object"
1029
- },
1030
- {
1031
- "const": false
1032
- }
1033
- ],
1034
- "description": "Desired position baseline or false to skip."
1035
- },
1036
- "orders": {
1037
- "oneOf": [
1038
- {
1039
- "type": "object"
1040
- },
1041
- {
1042
- "const": false
1043
- }
1044
- ],
1045
- "description": "Desired open-order baseline or false to skip."
1046
- },
1047
- "timeout_ms": {
1048
- "type": "number"
1049
- }
1466
+ "action": "assert_exit_code",
1467
+ "implementation": "@farmslot/recipe-harness/adapters/core"
1050
1468
  },
1051
- "required": [
1052
- "action"
1053
- ]
1054
- },
1055
- "examples": [
1056
1469
  {
1057
- "description": "Clean BTC testnet market state",
1058
- "node": {
1059
- "action": "metamask.perps.start_state",
1060
- "profile": "clean_market_testnet",
1061
- "market": "BTC",
1062
- "positions": {
1063
- "state": "none"
1064
- },
1065
- "orders": {
1066
- "state": "none"
1067
- },
1068
- "intent": "Prepare the Perps start state for the proof",
1069
- "network": "testnet"
1070
- }
1470
+ "action": "assert_output",
1471
+ "implementation": "@farmslot/recipe-harness/adapters/core"
1071
1472
  },
1072
1473
  {
1073
- "description": "Start with an open ETH long position",
1074
- "node": {
1075
- "action": "metamask.perps.start_state",
1076
- "profile": "open_position_testnet",
1077
- "market": "ETH",
1078
- "side": "long",
1079
- "positions": {
1080
- "state": "open",
1081
- "notional": "10",
1082
- "leverage": 2
1083
- },
1084
- "intent": "Prepare the Perps start state for the proof",
1085
- "network": "testnet"
1086
- }
1087
- }
1088
- ],
1089
- "proof_effect": "Runs in setup/teardown phase and must be visible in trace.json; proof videos can keep this as trace-only setup.",
1090
- "safety_notes": "Composes supported app/API paths and must not mutate React/Redux/MobX/DOM/local storage to fabricate proof."
1091
- },
1092
- {
1093
- "name": "metamask.perps.teardown_state",
1094
- "owner": "metamask",
1095
- "description": "core Recommended configurable Perps teardown state that restores the selected environment after proof. Headless: converges POSITION and ORDER baselines on testnet via the controller; no navigation/page/HUD.",
1096
- "schema": {
1097
- "type": "object",
1098
- "properties": {
1099
- "action": {
1100
- "const": "metamask.perps.teardown_state"
1101
- },
1102
- "profile": {
1103
- "type": "string",
1104
- "description": "Optional profile such as clean_market_testnet, open_position_testnet, open_order_testnet, or provider_mainnet_readonly."
1105
- },
1106
- "account": {
1107
- "type": "string"
1108
- },
1109
- "provider": {
1110
- "type": "string",
1111
- "description": "Requested Perps provider, e.g. hyperliquid."
1112
- },
1113
- "network": {
1114
- "type": "string",
1115
- "enum": [
1116
- "testnet",
1117
- "mainnet"
1118
- ]
1119
- },
1120
- "market": {
1121
- "type": "string"
1122
- },
1123
- "symbol": {
1124
- "type": "string"
1125
- },
1126
- "markets": {
1127
- "type": "array",
1128
- "items": {
1129
- "type": "string"
1130
- }
1131
- },
1132
- "symbols": {
1133
- "type": "array",
1134
- "items": {
1135
- "type": "string"
1136
- }
1137
- },
1138
- "positions": {
1139
- "oneOf": [
1140
- {
1141
- "type": "object"
1142
- },
1143
- {
1144
- "const": false
1145
- }
1146
- ],
1147
- "description": "Desired position baseline or false to skip."
1148
- },
1149
- "orders": {
1150
- "oneOf": [
1151
- {
1152
- "type": "object"
1153
- },
1154
- {
1155
- "const": false
1156
- }
1157
- ],
1158
- "description": "Desired open-order baseline or false to skip."
1159
- },
1160
- "timeout_ms": {
1161
- "type": "number"
1162
- }
1474
+ "action": "watch_logs",
1475
+ "implementation": "@farmslot/recipe-harness/adapters/core"
1163
1476
  },
1164
- "required": [
1165
- "action"
1166
- ]
1167
- },
1168
- "examples": [
1169
1477
  {
1170
- "description": "Return BTC test account to clean state",
1171
- "node": {
1172
- "action": "metamask.perps.teardown_state",
1173
- "market": "BTC",
1174
- "positions": {
1175
- "state": "none"
1176
- },
1177
- "orders": {
1178
- "state": "none"
1179
- },
1180
- "intent": "Restore Perps state after the proof"
1181
- }
1478
+ "action": "index_artifacts",
1479
+ "implementation": "@farmslot/recipe-harness/adapters/core"
1480
+ },
1481
+ {
1482
+ "action": "end",
1483
+ "implementation": "@farmslot/recipe-harness/adapters/core"
1484
+ },
1485
+ {
1486
+ "action": "app.status",
1487
+ "implementation": "@metamask/recipe-runner/platform-status"
1488
+ },
1489
+ {
1490
+ "action": "metamask.perps.read_positions",
1491
+ "implementation": "@metamask/recipe-runner/core/perps"
1492
+ },
1493
+ {
1494
+ "action": "metamask.perps.read_orders",
1495
+ "implementation": "@metamask/recipe-runner/core/perps"
1496
+ },
1497
+ {
1498
+ "action": "metamask.perps.read_account",
1499
+ "implementation": "@metamask/recipe-runner/core/perps"
1182
1500
  },
1183
1501
  {
1184
- "description": "Teardown only state created by selected market",
1185
- "node": {
1186
- "action": "metamask.perps.teardown_state",
1187
- "market": "BTC",
1188
- "intent": "Restore Perps state after the proof"
1189
- }
1502
+ "action": "metamask.perps.place_order",
1503
+ "implementation": "@metamask/recipe-runner/core/perps"
1504
+ },
1505
+ {
1506
+ "action": "metamask.perps.close_positions",
1507
+ "implementation": "@metamask/recipe-runner/core/perps"
1508
+ },
1509
+ {
1510
+ "action": "metamask.perps.assert_positions",
1511
+ "implementation": "@metamask/recipe-runner/core/perps"
1512
+ },
1513
+ {
1514
+ "action": "metamask.perps.ensure_positions",
1515
+ "implementation": "@metamask/recipe-runner/core/perps"
1190
1516
  }
1191
- ],
1192
- "proof_effect": "Runs in setup/teardown phase and must be visible in trace.json; proof videos can keep this as trace-only setup.",
1193
- "safety_notes": "Composes supported app/API paths and must not mutate React/Redux/MobX/DOM/local storage to fabricate proof."
1194
- }
1195
- ],
1196
- "state_refs": [
1197
- {
1198
- "name": "perps.positions",
1199
- "description": "Normalized Perps position state read from the headless controller."
1200
- }
1201
- ],
1202
- "pre_conditions": [
1203
- {
1204
- "id": "no-mid-recipe-state-injection",
1205
- "description": "Recipes must drive the real perps controller read path for proof state."
1206
- }
1207
- ],
1208
- "native_bindings": [
1209
- {
1210
- "action": "command",
1211
- "implementation": "@farmslot/recipe-harness/adapters/core"
1212
- },
1213
- {
1214
- "action": "wait",
1215
- "implementation": "@farmslot/recipe-harness/adapters/core"
1216
- },
1217
- {
1218
- "action": "assert_file",
1219
- "implementation": "@farmslot/recipe-harness/adapters/core"
1220
- },
1221
- {
1222
- "action": "assert_json",
1223
- "implementation": "@farmslot/recipe-harness/adapters/core"
1224
- },
1225
- {
1226
- "action": "assert_exit_code",
1227
- "implementation": "@farmslot/recipe-harness/adapters/core"
1228
- },
1229
- {
1230
- "action": "assert_output",
1231
- "implementation": "@farmslot/recipe-harness/adapters/core"
1232
- },
1233
- {
1234
- "action": "watch_logs",
1235
- "implementation": "@farmslot/recipe-harness/adapters/core"
1236
- },
1237
- {
1238
- "action": "index_artifacts",
1239
- "implementation": "@farmslot/recipe-harness/adapters/core"
1240
- },
1241
- {
1242
- "action": "end",
1243
- "implementation": "@farmslot/recipe-harness/adapters/core"
1244
- },
1245
- {
1246
- "action": "app.status",
1247
- "implementation": "@metamask/recipe-runner/platform-status"
1248
- },
1249
- {
1250
- "action": "metamask.perps.read_positions",
1251
- "implementation": "@metamask/recipe-runner/core/perps"
1252
- },
1253
- {
1254
- "action": "metamask.perps.read_orders",
1255
- "implementation": "@metamask/recipe-runner/core/perps"
1256
- },
1257
- {
1258
- "action": "metamask.perps.read_account",
1259
- "implementation": "@metamask/recipe-runner/core/perps"
1260
- },
1261
- {
1262
- "action": "metamask.perps.place_order",
1263
- "implementation": "@metamask/recipe-runner/core/perps"
1264
- },
1265
- {
1266
- "action": "metamask.perps.close_positions",
1267
- "implementation": "@metamask/recipe-runner/core/perps"
1268
- },
1269
- {
1270
- "action": "metamask.perps.assert_positions",
1271
- "implementation": "@metamask/recipe-runner/core/perps"
1272
- },
1273
- {
1274
- "action": "metamask.perps.ensure_positions",
1275
- "implementation": "@metamask/recipe-runner/core/perps"
1276
- }
1277
- ],
1278
- "capability_notes": [
1279
- "core is a headless adapter: it instantiates @metamask/perps-controller against a resolved MetaMask/core checkout and reads HyperLiquid testnet over HTTP. No CDP, bridge, or UI is involved. Slice 1 implements read-only perps actions. Slice 2 adds a default viem signer + write actions (place_order, close_positions, ensure_positions, assert_positions) that submit real signed orders to HyperLiquid testnet via the controller's full provider path."
1280
- ],
1281
- "action_profiles": []
1517
+ ],
1518
+ "capability_notes": [
1519
+ "core is a headless adapter: it instantiates @metamask/perps-controller against a resolved MetaMask/core checkout and reads HyperLiquid testnet over HTTP. No CDP, bridge, or UI is involved. Slice 1 implements read-only perps actions. Slice 2 adds a default viem signer + write actions (place_order, close_positions, ensure_positions, assert_positions) that submit real signed orders to HyperLiquid testnet via the controller's full provider path."
1520
+ ],
1521
+ "action_profiles": []
1282
1522
  }