@deeeed/metamask-harness 0.38.0 → 0.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +18 -0
- package/dist/adapters.js +20 -11
- package/library/actions/extension/assets/open_details.mjs +30 -0
- package/library/actions/extension/assets/set_token_visibility.mjs +5 -0
- package/library/actions/extension/perps/open_balance_action.mjs +1 -6
- package/library/actions/extension/perps/search_markets.mjs +32 -0
- package/library/actions/extension/perps/select_market_filter.mjs +3 -2
- package/library/actions/extension/perps/set_market_favorite.mjs +6 -1
- package/library/actions/extension/swap_bridge/read_visible_state.mjs +2 -6
- package/library/actions/extension/swap_bridge/set_amount.mjs +11 -2
- package/library/actions/extension/swap_bridge/submit_transaction.mjs +11 -11
- package/library/actions/mobile/assets/import_custom_token.mjs +181 -0
- package/library/actions/mobile/assets/open_details.mjs +43 -0
- package/library/actions/mobile/assets/read_details.mjs +96 -0
- package/library/actions/mobile/assets/set_token_visibility.mjs +302 -0
- package/library/actions/mobile/assets/verify_sorting.mjs +104 -0
- package/library/actions/mobile/networks/add_custom.mjs +125 -0
- package/library/actions/mobile/networks/network-management.mjs +132 -0
- package/library/actions/mobile/networks/read_visible_state.mjs +43 -0
- package/library/actions/mobile/networks/remove_custom.mjs +100 -0
- package/library/actions/mobile/perps/search_markets.mjs +46 -0
- package/library/actions/mobile/perps/set_market_favorite.mjs +117 -0
- package/library/actions/mobile/platform/native-session-name.mjs +14 -0
- package/library/actions/mobile/platform/native-session.mjs +59 -0
- package/library/actions/mobile/platform/observe-ui.mjs +9 -1
- package/library/actions/mobile/swap_bridge/native-session.mjs +10 -45
- package/library/actions/mobile/swap_bridge/select_assets.mjs +15 -1
- package/library/actions/mobile/swap_bridge/set_amount.mjs +1 -1
- package/library/actions/mobile/swap_bridge/set_max_amount.mjs +55 -0
- package/library/actions/mobile/swap_bridge/set_slippage.mjs +96 -0
- package/library/actions/mobile/swap_bridge/submit_transaction.mjs +141 -0
- package/library/actions/mobile/ui/native-navigation.mjs +75 -23
- package/library/actions/mobile/ui/navigate.mjs +13 -2
- package/library/actions/mobile/wallet/lock.mjs +14 -0
- package/library/actions/mobile/wallet/native-ui.mjs +67 -14
- package/library/actions/shared/perps/visible-state.mjs +8 -6
- package/library/actions/shared/swap-bridge/visible-state.mjs +15 -0
- package/library/manifests/extension.action-manifest.json +43 -2
- package/library/manifests/mobile.action-manifest.json +308 -1
- package/library/recipes/assets/release-token-details.recipe.json +5 -4
- package/library/recipes/assets/release-token-hide-readd.recipe.json +2 -0
- package/library/recipes/mobile/networks/release-custom-network-persistence.recipe.json +90 -0
- package/library/recipes/mobile/networks/release-multichain-visibility.recipe.json +58 -0
- package/library/recipes/perps/release-market-search.recipe.json +8 -4
- package/library/recipes/perps/release-watchlist.recipe.json +31 -19
- package/library/recipes/swap-bridge/release-custom-slippage.recipe.json +5 -13
- package/package.json +1 -1
- package/scripts/validate-release-report.mjs +28 -14
- /package/library/recipes/{assets → extension/assets}/release-custom-network-token-persistence.recipe.json +0 -0
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { createAgentDeviceUiTransport } from '@farmslot/expo-recipe';
|
|
2
2
|
import { createAgentDeviceClient } from 'agent-device';
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
nativeAgentDeviceStateDir,
|
|
5
|
+
nativeSessionName,
|
|
6
|
+
} from '../platform/native-session-name.mjs';
|
|
4
7
|
|
|
5
8
|
const TEST_IDS = {
|
|
6
9
|
existingWallet: 'wallet-setup-screen-have-an-existing-wallet-button-id',
|
|
@@ -85,6 +88,10 @@ function isWalletScreen(nodes) {
|
|
|
85
88
|
return nodes.some((node) => WALLET_SCREEN_IDS.includes(String(node.identifier ?? '')));
|
|
86
89
|
}
|
|
87
90
|
|
|
91
|
+
function visibleSnapshotNodes(nodes) {
|
|
92
|
+
return nodes.filter((node) => node.visibleToUser !== false);
|
|
93
|
+
}
|
|
94
|
+
|
|
88
95
|
function hasBlockingSystemUi(nodes) {
|
|
89
96
|
return nodes.some((node) =>
|
|
90
97
|
(node.bundleId === 'com.android.systemui' || PERMISSION_CONTROLLER_PACKAGES.has(node.bundleId)) &&
|
|
@@ -101,7 +108,7 @@ export async function importWalletThroughNativeUi(input, credentials, options) {
|
|
|
101
108
|
const native = nativeSession(input);
|
|
102
109
|
try {
|
|
103
110
|
const initial = await native.normalizeWalletSurface(options.timeoutMs);
|
|
104
|
-
if (initial.wallet) {
|
|
111
|
+
if (initial.wallet && !initial.visible.has(TEST_IDS.walletTourSkip)) {
|
|
105
112
|
if (options.requireFresh) {
|
|
106
113
|
throw new Error('metamask.wallet.import method=ui requires a fresh Mobile onboarding state.');
|
|
107
114
|
}
|
|
@@ -183,6 +190,27 @@ export async function unlockWalletThroughNativeUi(input, password, timeoutMs) {
|
|
|
183
190
|
}
|
|
184
191
|
}
|
|
185
192
|
|
|
193
|
+
export async function lockWalletThroughNativeUi(input) {
|
|
194
|
+
const native = nativeSession(input);
|
|
195
|
+
const timeoutMs = Number(input.node?.timeout_ms ?? 30_000);
|
|
196
|
+
try {
|
|
197
|
+
const state = await native.normalizeWalletSurface(timeoutMs);
|
|
198
|
+
if (state.visible.has(TEST_IDS.login)) {
|
|
199
|
+
return { alreadyLocked: true, provider: 'android-native-accessibility' };
|
|
200
|
+
}
|
|
201
|
+
if (!state.wallet) throw new Error('Opaque Mobile wallet lock could not reach wallet home.');
|
|
202
|
+
await native.press(TEST_IDS.hamburger, timeoutMs);
|
|
203
|
+
await native.press(TEST_IDS.lock, timeoutMs);
|
|
204
|
+
if (await native.exists(TEST_IDS.confirmLock, 3000)) {
|
|
205
|
+
await native.press(TEST_IDS.confirmLock, timeoutMs);
|
|
206
|
+
}
|
|
207
|
+
await native.wait(TEST_IDS.login, timeoutMs);
|
|
208
|
+
return { alreadyLocked: false, provider: 'android-native-accessibility' };
|
|
209
|
+
} finally {
|
|
210
|
+
await native.close();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
186
214
|
export async function resetWalletThroughNativeUi(input) {
|
|
187
215
|
const native = nativeSession(input);
|
|
188
216
|
const timeoutMs = Number(input.node?.timeout_ms ?? 60_000);
|
|
@@ -219,8 +247,10 @@ function nativeSession(input) {
|
|
|
219
247
|
if (!device) throw new Error('Opaque Mobile UI execution requires one explicit Android device serial.');
|
|
220
248
|
const app = String(env.ANDROID_PACKAGE_ID ?? 'io.metamask');
|
|
221
249
|
const session = nativeSessionName(env, device, app);
|
|
250
|
+
const stateDir = nativeAgentDeviceStateDir(env, input.context.projectRoot, device);
|
|
222
251
|
const client = createAgentDeviceClient({
|
|
223
252
|
session,
|
|
253
|
+
stateDir,
|
|
224
254
|
lockPolicy: 'reject',
|
|
225
255
|
lockPlatform: 'android',
|
|
226
256
|
});
|
|
@@ -230,6 +260,7 @@ function nativeSession(input) {
|
|
|
230
260
|
device,
|
|
231
261
|
app,
|
|
232
262
|
session,
|
|
263
|
+
stateDir,
|
|
233
264
|
client,
|
|
234
265
|
});
|
|
235
266
|
const context = {
|
|
@@ -240,7 +271,11 @@ function nativeSession(input) {
|
|
|
240
271
|
};
|
|
241
272
|
const execute = (action, node) => transport.execute(action, node, context);
|
|
242
273
|
return {
|
|
243
|
-
press: (testId, timeoutMs) => execute('ui.press', {
|
|
274
|
+
press: (testId, timeoutMs) => execute('ui.press', {
|
|
275
|
+
test_id: testId,
|
|
276
|
+
timeout_ms: timeoutMs,
|
|
277
|
+
settle: false,
|
|
278
|
+
}),
|
|
244
279
|
fill: (testId, value, timeoutMs) => execute('ui.set_input', {
|
|
245
280
|
test_id: testId,
|
|
246
281
|
value,
|
|
@@ -259,7 +294,7 @@ function nativeSession(input) {
|
|
|
259
294
|
interactiveOnly: false,
|
|
260
295
|
forceFull: true,
|
|
261
296
|
});
|
|
262
|
-
const actual = new Map(snapshot.nodes
|
|
297
|
+
const actual = new Map(visibleSnapshotNodes(snapshot.nodes)
|
|
263
298
|
.filter((node) => /^phrase-input-id_\d+$/u.test(String(node.identifier ?? '')))
|
|
264
299
|
.map((node) => [
|
|
265
300
|
Number(String(node.identifier).split('_').at(-1)),
|
|
@@ -288,11 +323,12 @@ function nativeSession(input) {
|
|
|
288
323
|
interactiveOnly: false,
|
|
289
324
|
forceFull: true,
|
|
290
325
|
});
|
|
326
|
+
const nodes = visibleSnapshotNodes(snapshot.nodes);
|
|
291
327
|
return {
|
|
292
|
-
nodes
|
|
293
|
-
visible: new Set(
|
|
294
|
-
wallet: isWalletScreen(
|
|
295
|
-
postPassword: isPostPasswordScreen(
|
|
328
|
+
nodes,
|
|
329
|
+
visible: new Set(nodes.map((node) => String(node.identifier ?? ''))),
|
|
330
|
+
wallet: isWalletScreen(nodes),
|
|
331
|
+
postPassword: isPostPasswordScreen(nodes) || isMetaMaskBiometricPrompt(nodes),
|
|
296
332
|
};
|
|
297
333
|
},
|
|
298
334
|
async normalizeWalletSurface(timeoutMs) {
|
|
@@ -333,13 +369,15 @@ function nativeSession(input) {
|
|
|
333
369
|
let walletTourSkipped = false;
|
|
334
370
|
let interestsHandled = false;
|
|
335
371
|
let awaitingNotificationPermission = false;
|
|
372
|
+
let stableWalletCaptures = 0;
|
|
336
373
|
while (Date.now() <= deadline) {
|
|
337
374
|
const snapshot = await client.capture.snapshot({
|
|
338
375
|
...selection,
|
|
339
376
|
interactiveOnly: false,
|
|
340
377
|
forceFull: true,
|
|
341
378
|
});
|
|
342
|
-
|
|
379
|
+
const nodes = visibleSnapshotNodes(snapshot.nodes);
|
|
380
|
+
if (isMetaMaskBiometricPrompt(nodes)) {
|
|
343
381
|
if (options.biometricsEnabled) {
|
|
344
382
|
throw new Error('Mobile biometrics=true requires manual device authentication and is not supported by an unattended recipe.');
|
|
345
383
|
}
|
|
@@ -351,7 +389,7 @@ function nativeSession(input) {
|
|
|
351
389
|
biometricPromptDismissed = true;
|
|
352
390
|
continue;
|
|
353
391
|
}
|
|
354
|
-
if (isMetaMaskNotificationPermissionPrompt(
|
|
392
|
+
if (isMetaMaskNotificationPermissionPrompt(nodes) && awaitingNotificationPermission) {
|
|
355
393
|
await client.command.alert({
|
|
356
394
|
...selection,
|
|
357
395
|
action: 'accept',
|
|
@@ -362,10 +400,12 @@ function nativeSession(input) {
|
|
|
362
400
|
notificationsEnabled = true;
|
|
363
401
|
continue;
|
|
364
402
|
}
|
|
365
|
-
if (hasBlockingSystemUi(
|
|
403
|
+
if (hasBlockingSystemUi(nodes)) {
|
|
366
404
|
throw new Error('Unexpected Android system UI blocked Mobile wallet creation; refusing to dismiss it as biometric authentication.');
|
|
367
405
|
}
|
|
368
|
-
const visible = new Set(
|
|
406
|
+
const visible = new Set(nodes.map((node) => String(node.identifier ?? '')));
|
|
407
|
+
const walletVisible = WALLET_SCREEN_IDS.some((testId) => visible.has(testId));
|
|
408
|
+
if (!walletVisible) stableWalletCaptures = 0;
|
|
369
409
|
if (visible.has(TEST_IDS.metricsContinue)) {
|
|
370
410
|
if (!options.metametricsEnabled) {
|
|
371
411
|
await this.press(TEST_IDS.metricsCheckbox, options.timeoutMs);
|
|
@@ -402,6 +442,7 @@ function nativeSession(input) {
|
|
|
402
442
|
continue;
|
|
403
443
|
}
|
|
404
444
|
if (visible.has(TEST_IDS.walletTourSkip)) {
|
|
445
|
+
stableWalletCaptures = 0;
|
|
405
446
|
if (options.skipWalletTour) {
|
|
406
447
|
await this.press(TEST_IDS.walletTourSkip, options.timeoutMs);
|
|
407
448
|
walletTourSkipped = true;
|
|
@@ -409,10 +450,15 @@ function nativeSession(input) {
|
|
|
409
450
|
}
|
|
410
451
|
throw new Error('Mobile wallet-home tour remains visible because skip_wallet_tour=false; wallet readiness cannot be proven.');
|
|
411
452
|
}
|
|
412
|
-
if (
|
|
453
|
+
if (walletVisible) {
|
|
413
454
|
if (options.interests.length > 0 && !interestsHandled) {
|
|
414
455
|
throw new Error('Mobile onboarding interests were requested, but the questionnaire is not enabled for this build.');
|
|
415
456
|
}
|
|
457
|
+
stableWalletCaptures += 1;
|
|
458
|
+
if (stableWalletCaptures < 2) {
|
|
459
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
460
|
+
continue;
|
|
461
|
+
}
|
|
416
462
|
return {
|
|
417
463
|
biometricsEnabled: options.biometricsEnabled,
|
|
418
464
|
biometricPromptDismissed,
|
|
@@ -433,8 +479,15 @@ function nativeSession(input) {
|
|
|
433
479
|
}),
|
|
434
480
|
async waitForWallet(timeoutMs) {
|
|
435
481
|
const deadline = Date.now() + timeoutMs;
|
|
482
|
+
let stableCaptures = 0;
|
|
436
483
|
while (Date.now() <= deadline) {
|
|
437
|
-
|
|
484
|
+
const state = await this.screenState();
|
|
485
|
+
if (state.wallet && state.visible.has(TEST_IDS.walletAddress)) {
|
|
486
|
+
stableCaptures += 1;
|
|
487
|
+
if (stableCaptures >= 2) return;
|
|
488
|
+
} else {
|
|
489
|
+
stableCaptures = 0;
|
|
490
|
+
}
|
|
438
491
|
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
439
492
|
}
|
|
440
493
|
throw new Error('Mobile wallet home did not become visible before the readiness timeout.');
|
|
@@ -132,6 +132,7 @@ function marketFromTestId(testId) {
|
|
|
132
132
|
for (const prefix of ['perps-watchlist-', 'explore-markets-', 'perps-market-row-item-', 'market-row-']) {
|
|
133
133
|
if (!testId.startsWith(prefix)) continue;
|
|
134
134
|
const raw = testId.slice(prefix.length);
|
|
135
|
+
if (/(?:-token-logo|-asset-label)$/u.test(raw)) return undefined;
|
|
135
136
|
if ((prefix === 'explore-markets-' || prefix === 'market-row-') && raw.startsWith('xyz-')) {
|
|
136
137
|
return `XYZ:${raw.slice(4).toUpperCase()}`;
|
|
137
138
|
}
|
|
@@ -173,18 +174,19 @@ function compactEntries(items, maximum) {
|
|
|
173
174
|
function visibleFeatures(items) {
|
|
174
175
|
const ids = new Set(items.map((item) => item.testId));
|
|
175
176
|
const has = (pattern) => [...ids].some((testId) => pattern.test(testId));
|
|
176
|
-
const long = has(/(?:long-cta-button|direction-long)$/u);
|
|
177
|
-
const short = has(/(?:short-cta-button|direction-short)$/u);
|
|
177
|
+
const long = has(/(?:long-cta-button|direction-long|market-details-long-button)$/u);
|
|
178
|
+
const short = has(/(?:short-cta-button|direction-short|market-details-short-button)$/u);
|
|
178
179
|
return {
|
|
179
180
|
activity: has(/^(?:transaction-card-|perps-(?:recent-activity|activity))/u),
|
|
180
181
|
balance: has(/^perps-balance-dropdown/u),
|
|
181
|
-
chart: has(
|
|
182
|
+
chart: has(/^(?:chart_surface|tv_chart_container)$/u) ||
|
|
183
|
+
has(/perps-(?:candlestick-chart|market-detail-chart|market-details-fullscreen-chart|pro-market-chart)/u),
|
|
182
184
|
'long-short': long && short,
|
|
183
185
|
'market-list': has(/^(?:market-list-view|perps-market-list|perps-watchlist-|explore-markets-|market-row-)/u),
|
|
184
186
|
'order-entry': has(/perps-(?:order-entry|pro-order-form)/u),
|
|
185
|
-
price: has(/perps-(?:market-detail-price|pro-market-price)$/u),
|
|
186
|
-
'price-change': has(/perps-(?:market-detail-change|pro-market-price-change)$/u),
|
|
187
|
-
stats: has(/perps-(?:stats-section|market-detail-oracle-price|pro-market-stats)/u),
|
|
187
|
+
price: has(/perps-(?:market-detail-price|market-header-price|pro-market-price)$/u),
|
|
188
|
+
'price-change': has(/perps-(?:market-detail-change|market-header-price-change|pro-market-price-change)$/u),
|
|
189
|
+
stats: has(/perps-(?:stats-section|market-details-market-summary|market-detail-oracle-price|pro-market-stats)/u),
|
|
188
190
|
};
|
|
189
191
|
}
|
|
190
192
|
|
|
@@ -17,6 +17,8 @@ export function resolveVisibleSwapBridgeOptions(node = {}) {
|
|
|
17
17
|
destinationSymbol: symbol(node.destination_symbol),
|
|
18
18
|
requireAmountInputs: node.require_amount_inputs !== false,
|
|
19
19
|
requireSettings: node.require_settings === true,
|
|
20
|
+
requireQuote: node.require_quote === true,
|
|
21
|
+
expectedSlippage: node.expected_slippage == null ? null : Number(node.expected_slippage),
|
|
20
22
|
};
|
|
21
23
|
}
|
|
22
24
|
|
|
@@ -42,6 +44,11 @@ function matchesSymbol(value, expected) {
|
|
|
42
44
|
.test(String(value ?? '').toUpperCase());
|
|
43
45
|
}
|
|
44
46
|
|
|
47
|
+
function slippagePercent(value) {
|
|
48
|
+
const match = String(value ?? '').match(/(\d+(?:\.\d+)?)\s*%/u);
|
|
49
|
+
return match ? Number(match[1]) : Number.NaN;
|
|
50
|
+
}
|
|
51
|
+
|
|
45
52
|
export function normalizeVisibleSwapBridgeState(raw, options, platform) {
|
|
46
53
|
const items = [...(raw.items ?? []), ...(raw.offscreenItems ?? [])]
|
|
47
54
|
.map(normalizedItem)
|
|
@@ -55,6 +62,7 @@ export function normalizeVisibleSwapBridgeState(raw, options, platform) {
|
|
|
55
62
|
const settings = byId.get(platform === 'extension' ? 'bridge__header-settings-button' : 'bridge-slippage-settings-button');
|
|
56
63
|
const noQuotes = byId.get(platform === 'extension' ? 'prepare-swap-page-error' : 'bridge-no-quotes');
|
|
57
64
|
const missingPrice = byId.get(platform === 'extension' ? 'bridge-missing-price-banner' : 'bridge-missing-price-banner');
|
|
65
|
+
const slippage = byId.get(platform === 'extension' ? 'slippage-edit-button' : 'edit-slippage-button');
|
|
58
66
|
const state = {
|
|
59
67
|
source: 'visible-ui',
|
|
60
68
|
platform,
|
|
@@ -65,6 +73,7 @@ export function normalizeVisibleSwapBridgeState(raw, options, platform) {
|
|
|
65
73
|
destinationAsset,
|
|
66
74
|
sourceAmount: text(sourceInput?.value ?? sourceInput?.label) ?? null,
|
|
67
75
|
destinationAmount: text(destinationInput?.value ?? destinationInput?.label) ?? null,
|
|
76
|
+
slippage: text(raw.slippage ?? raw.quote?.slippage ?? slippage?.value ?? slippage?.label) ?? null,
|
|
68
77
|
controls: unique.slice(0, 100),
|
|
69
78
|
features: {
|
|
70
79
|
'amount-inputs': Boolean(sourceInput && destinationInput),
|
|
@@ -88,6 +97,12 @@ function assertVisibleSwapBridgeState(state, options) {
|
|
|
88
97
|
if (options.requireSettings && !state.features.settings) {
|
|
89
98
|
throw new Error('Visible Swap settings control was not observed.');
|
|
90
99
|
}
|
|
100
|
+
if (options.requireQuote && !(Number(state.destinationAmount) > 0)) {
|
|
101
|
+
throw new Error('Visible Swap quote did not contain a positive destination amount.');
|
|
102
|
+
}
|
|
103
|
+
if (options.expectedSlippage !== null && slippagePercent(state.slippage) !== options.expectedSlippage) {
|
|
104
|
+
throw new Error(`Expected visible Swap slippage ${options.expectedSlippage}%, but observed ${JSON.stringify(state.slippage)}.`);
|
|
105
|
+
}
|
|
91
106
|
if (!matchesSymbol(state.sourceAsset, options.sourceSymbol)) {
|
|
92
107
|
throw new Error(`Expected visible Swap source ${options.sourceSymbol}, but observed ${JSON.stringify(state.sourceAsset)}.`);
|
|
93
108
|
}
|
|
@@ -1584,6 +1584,25 @@
|
|
|
1584
1584
|
],
|
|
1585
1585
|
"execution_capabilities": ["host-read-export"]
|
|
1586
1586
|
},
|
|
1587
|
+
"metamask.assets.open_details": {
|
|
1588
|
+
"description": "Open one visible Extension asset by symbol.",
|
|
1589
|
+
"schema": {
|
|
1590
|
+
"type": "object",
|
|
1591
|
+
"properties": {
|
|
1592
|
+
"symbol": { "type": "string" },
|
|
1593
|
+
"timeout_ms": { "type": "integer", "minimum": 1000, "default": 30000 }
|
|
1594
|
+
},
|
|
1595
|
+
"required": ["symbol"],
|
|
1596
|
+
"additionalProperties": false
|
|
1597
|
+
},
|
|
1598
|
+
"examples": [{
|
|
1599
|
+
"action": "metamask.assets.open_details",
|
|
1600
|
+
"symbol": "ETH",
|
|
1601
|
+
"intent": "Open the funded ETH details",
|
|
1602
|
+
"next": "done"
|
|
1603
|
+
}],
|
|
1604
|
+
"execution_capabilities": ["app-mutation"]
|
|
1605
|
+
},
|
|
1587
1606
|
"metamask.assets.read_details": {
|
|
1588
1607
|
"description": "Read and assert the current Extension asset details page through visible UI.",
|
|
1589
1608
|
"schema": {
|
|
@@ -1591,7 +1610,8 @@
|
|
|
1591
1610
|
"properties": {
|
|
1592
1611
|
"expected_symbol": { "type": "string" },
|
|
1593
1612
|
"require_chart": { "type": "boolean", "default": false },
|
|
1594
|
-
"require_market_cap": { "type": "boolean", "default": false }
|
|
1613
|
+
"require_market_cap": { "type": "boolean", "default": false },
|
|
1614
|
+
"timeout_ms": { "type": "integer", "minimum": 1000, "default": 30000 }
|
|
1595
1615
|
},
|
|
1596
1616
|
"additionalProperties": false
|
|
1597
1617
|
},
|
|
@@ -1631,6 +1651,7 @@
|
|
|
1631
1651
|
"query": { "type": "string" },
|
|
1632
1652
|
"visible": { "type": "boolean" },
|
|
1633
1653
|
"require_unchanged": { "type": "boolean", "default": false },
|
|
1654
|
+
"require_change": { "type": "boolean", "default": false },
|
|
1634
1655
|
"timeout_ms": { "type": "integer", "minimum": 1000, "default": 45000 }
|
|
1635
1656
|
},
|
|
1636
1657
|
"required": ["query", "visible"],
|
|
@@ -2177,6 +2198,25 @@
|
|
|
2177
2198
|
],
|
|
2178
2199
|
"execution_capabilities": ["host-read-export"]
|
|
2179
2200
|
},
|
|
2201
|
+
"metamask.perps.search_markets": {
|
|
2202
|
+
"description": "Enter a Perps market query through the visible search field and prove visible results are returned.",
|
|
2203
|
+
"schema": {
|
|
2204
|
+
"type": "object",
|
|
2205
|
+
"properties": {
|
|
2206
|
+
"query": { "type": "string" },
|
|
2207
|
+
"timeout_ms": { "type": "integer", "minimum": 1000, "default": 30000 }
|
|
2208
|
+
},
|
|
2209
|
+
"required": ["query"],
|
|
2210
|
+
"additionalProperties": false
|
|
2211
|
+
},
|
|
2212
|
+
"examples": [{
|
|
2213
|
+
"action": "metamask.perps.search_markets",
|
|
2214
|
+
"query": "Bitcoin",
|
|
2215
|
+
"intent": "Filter the visible Perps market list",
|
|
2216
|
+
"next": "done"
|
|
2217
|
+
}],
|
|
2218
|
+
"execution_capabilities": ["app-mutation"]
|
|
2219
|
+
},
|
|
2180
2220
|
"metamask.perps.select_market_filter": {
|
|
2181
2221
|
"description": "Select and assert a live Perps market category through trusted visible UI controls.",
|
|
2182
2222
|
"schema": {
|
|
@@ -2288,7 +2328,8 @@
|
|
|
2288
2328
|
"type": "object",
|
|
2289
2329
|
"properties": {
|
|
2290
2330
|
"market": { "type": "string" },
|
|
2291
|
-
"favorite": { "type": "boolean" }
|
|
2331
|
+
"favorite": { "type": "boolean" },
|
|
2332
|
+
"require_unchanged": { "type": "boolean", "default": false }
|
|
2292
2333
|
},
|
|
2293
2334
|
"required": ["market", "favorite"],
|
|
2294
2335
|
"additionalProperties": false
|