@deeeed/metamask-harness 0.86.1 → 0.87.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 +41 -0
- package/dist/adapters/mobile/action-inputs.js +20 -0
- package/dist/adapters/mobile/actions.js +18 -1
- package/dist/adapters/mobile/android-screenrecord.js +270 -0
- package/dist/adapters/mobile/readiness.js +2 -1
- package/dist/adapters/mobile/recording-outcome.js +103 -0
- package/dist/adapters/mobile/recording-target.js +11 -0
- package/dist/adapters/mobile/run.js +48 -11
- package/dist/adapters/mobile/ui.js +96 -2
- package/dist/adapters/perps-order-inputs.js +43 -28
- package/dist/adapters.js +4 -1
- package/dist/commands/doctor.js +33 -14
- package/dist/commands/status-probe.js +2 -1
- package/docs/RECIPES.md +62 -0
- package/library/actions/core/perps/read_account.mjs +4 -2
- package/library/actions/core/perps/read_orders.mjs +4 -2
- package/library/actions/core/perps/read_positions.mjs +4 -2
- package/library/actions/extension/analytics/consent.mjs +7 -2
- package/library/actions/extension/perps/close_visible_position.mjs +9 -5
- package/library/actions/extension/perps/compare_provider_market.mjs +12 -8
- package/library/actions/extension/perps/mutation-receipt.mjs +142 -24
- package/library/actions/extension/perps/perps.mjs +179 -40
- package/library/actions/extension/perps/read_snapshot.mjs +6 -2
- package/library/actions/extension/platform/cdp.mjs +6 -2
- package/library/actions/extension/ui/navigate.mjs +8 -0
- package/library/actions/mobile/perps/perps.mjs +109 -109
- package/library/actions/mobile/platform/bridge.mjs +192 -12
- package/library/actions/mobile/wallet/home.mjs +7 -2
- package/library/actions/mobile/wallet/select_account.mjs +4 -0
- package/library/actions/shared/app/live-adapter-deadline.mjs +8 -0
- package/library/actions/shared/perps/order-policy.mjs +19 -0
- package/library/actions/shared/perps/rate-limit.mjs +93 -0
- package/library/actions/shared/perps/visible-state.mjs +3 -2
- package/library/manifests/extension.action-manifest.json +3 -2
- package/library/manifests/mobile.action-manifest.json +30 -5
- package/package.json +1 -1
- package/site/assets/help-recipes.json +1 -0
|
@@ -22,6 +22,8 @@ import path from 'node:path';
|
|
|
22
22
|
|
|
23
23
|
import { captureActiveRecipeRecordingSnapshot, recipeHarnessPath } from '@farmslot/recipe-cli/harness';
|
|
24
24
|
import { importRecipeHarnessRuntimeBrowserExtension, importRecipeHarnessRuntimeCdp, extensionIdPath, walletFixturePath } from '../../harness-exports.mjs';
|
|
25
|
+
import { liveAdapterDeadline } from '../../shared/app/live-adapter-deadline.mjs';
|
|
26
|
+
import { withRateLimitRetry } from '../../shared/perps/rate-limit.mjs';
|
|
25
27
|
|
|
26
28
|
// Resolve the Farmslot harness through normal package dependencies by default.
|
|
27
29
|
// Local Farmslot source is only a dev override handled by the harness paths module.
|
|
@@ -843,6 +845,8 @@ export async function withExtensionPage(input, callback) {
|
|
|
843
845
|
const origin = `chrome-extension://${extensionId}`;
|
|
844
846
|
const runtimeIdentity = extensionRuntimeIdentity({ port, target, browserVersion });
|
|
845
847
|
const page = new ExtensionPage(session, origin, target, port, runtimeIdentity);
|
|
848
|
+
// Rate-limit retries on this page's reads stop before the runner's process limit.
|
|
849
|
+
page.actionDeadline = liveAdapterDeadline(input.node);
|
|
846
850
|
const recoverVisibleWallet = input.action === 'metamask.wallet.import' && input.node?.method === 'ui';
|
|
847
851
|
if (recoverVisibleWallet) {
|
|
848
852
|
try {
|
|
@@ -1030,7 +1034,7 @@ export class ExtensionPage extends CdpWebPage {
|
|
|
1030
1034
|
}
|
|
1031
1035
|
|
|
1032
1036
|
async readPositions() {
|
|
1033
|
-
return this.evaluate(`(async () => {
|
|
1037
|
+
return withRateLimitRetry('perpsGetPositions', () => this.evaluate(`(async () => {
|
|
1034
1038
|
const request = globalThis.stateHooks?.submitRequestToBackground;
|
|
1035
1039
|
const manager = globalThis.stateHooks?.getPerpsStreamManager?.();
|
|
1036
1040
|
const cached = manager?.positions?.cache;
|
|
@@ -1050,7 +1054,7 @@ export class ExtensionPage extends CdpWebPage {
|
|
|
1050
1054
|
connected: Boolean(manager?.positions?.isConnected),
|
|
1051
1055
|
positions: Array.isArray(cached) ? cached : [],
|
|
1052
1056
|
};
|
|
1053
|
-
})()`);
|
|
1057
|
+
})()`), { deadline: this.actionDeadline });
|
|
1054
1058
|
}
|
|
1055
1059
|
|
|
1056
1060
|
async screenshot(contextOrInput, relPath, metadata = {}) {
|
|
@@ -280,6 +280,8 @@ export async function openPerpsHome(page, timeoutMs) {
|
|
|
280
280
|
await pauseForUi(page);
|
|
281
281
|
}
|
|
282
282
|
|
|
283
|
+
// The sample is capped at 30 and the header and tabs come first in the DOM, so loading
|
|
284
|
+
// markers are listed first; otherwise a Perps view still loading reads as a wrong route.
|
|
283
285
|
const state = await page.evaluate(`({
|
|
284
286
|
href: location.href,
|
|
285
287
|
title: document.title,
|
|
@@ -291,8 +293,14 @@ export async function openPerpsHome(page, timeoutMs) {
|
|
|
291
293
|
})
|
|
292
294
|
.map((element) => element.getAttribute('data-testid'))
|
|
293
295
|
.filter(Boolean)
|
|
296
|
+
.sort((a, b) => Number(b.endsWith('-loading')) - Number(a.endsWith('-loading')))
|
|
294
297
|
.slice(0, 30),
|
|
295
298
|
})`);
|
|
299
|
+
if (await hasVisibleSelector(page, perpsLoading)) {
|
|
300
|
+
throw new Error(
|
|
301
|
+
`extension ui.navigate page=perps: Perps home stayed on its loading view (perps-view-loading visible) within the ${timeoutMs}ms budget; observed ${JSON.stringify(state)}.\nNext: check the Perps stream/provider, or raise timeout_ms; a raw hash route does not wait for Perps readiness.`,
|
|
302
|
+
);
|
|
303
|
+
}
|
|
296
304
|
throw new Error(
|
|
297
305
|
`extension ui.navigate page=perps could not resolve the Perps home through visible UI; observed ${JSON.stringify(state)}. Capture the current UI with mm-harness call ui.screenshot, then use a verified visible control or raw hash/path.`,
|
|
298
306
|
);
|
|
@@ -4,8 +4,13 @@ import { pathToFileURL } from 'node:url';
|
|
|
4
4
|
import {
|
|
5
5
|
MOBILE_BRIDGE_RESULT_ERROR_CODES,
|
|
6
6
|
bridgeCommand,
|
|
7
|
+
createSetupBudget,
|
|
8
|
+
deadlineFromTimeout,
|
|
9
|
+
earliestDeadline,
|
|
7
10
|
evalAsync,
|
|
8
|
-
|
|
11
|
+
inheritedDeadline,
|
|
12
|
+
isUnansweredRead,
|
|
13
|
+
mobileNavigationTimeoutMs,
|
|
9
14
|
navigate,
|
|
10
15
|
routeSatisfiesNavigationTarget,
|
|
11
16
|
runAdapter,
|
|
@@ -22,6 +27,7 @@ import {
|
|
|
22
27
|
requireHouseNotional,
|
|
23
28
|
requirePerpsOrderAllowed,
|
|
24
29
|
} from '../../shared/perps/order-policy.mjs';
|
|
30
|
+
import { isRateLimited, rateLimitedError, withRateLimitRetry } from '../../shared/perps/rate-limit.mjs';
|
|
25
31
|
|
|
26
32
|
function sleep(ms) {
|
|
27
33
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
@@ -94,23 +100,6 @@ const MODE_ROOT_TEST_IDS = {
|
|
|
94
100
|
};
|
|
95
101
|
const READ_ATTEMPT_TIMEOUT_MS = 10_000;
|
|
96
102
|
|
|
97
|
-
function inheritedDeadline(input) {
|
|
98
|
-
const deadline = Number(input.node?._action_deadline_epoch_ms);
|
|
99
|
-
return Number.isFinite(deadline) ? deadline : null;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function earliestDeadline(input, deadline) {
|
|
103
|
-
const inherited = inheritedDeadline(input);
|
|
104
|
-
return inherited === null ? deadline : Math.min(deadline, inherited);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function deadlineFromTimeout(input, timeoutMs) {
|
|
108
|
-
return earliestDeadline(
|
|
109
|
-
input,
|
|
110
|
-
Date.now() + Math.max(0, Number(timeoutMs)),
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
103
|
async function sleepBeforeDeadline(deadline, maximumMs, wait = sleep) {
|
|
115
104
|
const remainingMs = deadline - Date.now();
|
|
116
105
|
if (remainingMs <= 0) return;
|
|
@@ -183,7 +172,7 @@ async function readModeState(input, mode, deadline) {
|
|
|
183
172
|
try {
|
|
184
173
|
return await queryModeState(input, mode, deadline);
|
|
185
174
|
} catch (error) {
|
|
186
|
-
if (!
|
|
175
|
+
if (!isUnansweredRead(error)) throw error;
|
|
187
176
|
return null;
|
|
188
177
|
}
|
|
189
178
|
}
|
|
@@ -251,6 +240,8 @@ export async function ensureMode(input) {
|
|
|
251
240
|
throw new Error(`Perps did not reach ${requestedMode} mode before timeout.`);
|
|
252
241
|
}
|
|
253
242
|
|
|
243
|
+
// Retries the client-not-initialized race until the deadline, and a 429 with
|
|
244
|
+
// the shared rate-limit backoff.
|
|
254
245
|
export async function retryPerpsClientRead(
|
|
255
246
|
read,
|
|
256
247
|
{
|
|
@@ -258,6 +249,8 @@ export async function retryPerpsClientRead(
|
|
|
258
249
|
intervalMs = 500,
|
|
259
250
|
sleep: wait = sleep,
|
|
260
251
|
deadlineEpochMs,
|
|
252
|
+
call = 'PerpsController read',
|
|
253
|
+
random,
|
|
261
254
|
} = {},
|
|
262
255
|
) {
|
|
263
256
|
const localDeadline = Date.now() + Math.max(0, timeoutMs);
|
|
@@ -268,7 +261,7 @@ export async function retryPerpsClientRead(
|
|
|
268
261
|
: Math.min(localDeadline, configuredDeadline);
|
|
269
262
|
while (true) {
|
|
270
263
|
try {
|
|
271
|
-
return await read
|
|
264
|
+
return await withRateLimitRetry(call, read, { deadline, sleep: wait, random });
|
|
272
265
|
} catch (error) {
|
|
273
266
|
if (!/CLIENT_NOT_INITIALIZED/u.test(String(error?.message ?? error))) {
|
|
274
267
|
throw error;
|
|
@@ -400,7 +393,7 @@ async function readPositions(input) {
|
|
|
400
393
|
}
|
|
401
394
|
return positions;
|
|
402
395
|
},
|
|
403
|
-
{ deadlineEpochMs: inheritedDeadline(input) },
|
|
396
|
+
{ deadlineEpochMs: inheritedDeadline(input), call: 'PerpsController.getPositions' },
|
|
404
397
|
);
|
|
405
398
|
}
|
|
406
399
|
|
|
@@ -427,7 +420,7 @@ async function readOpenOrders(input) {
|
|
|
427
420
|
}
|
|
428
421
|
return orders;
|
|
429
422
|
},
|
|
430
|
-
{ deadlineEpochMs: inheritedDeadline(input) },
|
|
423
|
+
{ deadlineEpochMs: inheritedDeadline(input), call: 'PerpsController.getOpenOrders' },
|
|
431
424
|
);
|
|
432
425
|
}
|
|
433
426
|
|
|
@@ -459,7 +452,7 @@ async function readStrategyOrders(input) {
|
|
|
459
452
|
}
|
|
460
453
|
return orders;
|
|
461
454
|
},
|
|
462
|
-
{ deadlineEpochMs: inheritedDeadline(input) },
|
|
455
|
+
{ deadlineEpochMs: inheritedDeadline(input), call: 'PerpsController.getTwapOrders/getChaseOrders' },
|
|
463
456
|
);
|
|
464
457
|
}
|
|
465
458
|
|
|
@@ -917,7 +910,7 @@ async function readCurrentRoute(input) {
|
|
|
917
910
|
try {
|
|
918
911
|
return await bridgeCommand(input, ['get-route']);
|
|
919
912
|
} catch (error) {
|
|
920
|
-
if (!
|
|
913
|
+
if (!isUnansweredRead(error)) throw error;
|
|
921
914
|
return null;
|
|
922
915
|
}
|
|
923
916
|
}
|
|
@@ -975,8 +968,7 @@ async function waitForPerpsUiTarget(input, testId, deadline) {
|
|
|
975
968
|
}
|
|
976
969
|
|
|
977
970
|
async function selectPerpsMarketFromHome(input, symbol, previousRoute) {
|
|
978
|
-
const
|
|
979
|
-
const deadline = deadlineFromTimeout(input, timeoutMs);
|
|
971
|
+
const deadline = deadlineFromTimeout(input, mobileNavigationTimeoutMs(input));
|
|
980
972
|
const params = { market: { symbol } };
|
|
981
973
|
const rowTestId = `perps-market-row-item-${symbol}`;
|
|
982
974
|
const recentlyViewedTestId = `perps-recently-viewed-tile-${symbol}`;
|
|
@@ -1059,8 +1051,7 @@ async function waitForRouteChange(input, previousRoute, deadline) {
|
|
|
1059
1051
|
}
|
|
1060
1052
|
|
|
1061
1053
|
async function navigateFromMarketDetailsToHome(input, previousRoute) {
|
|
1062
|
-
const
|
|
1063
|
-
const deadline = deadlineFromTimeout(input, timeoutMs);
|
|
1054
|
+
const deadline = deadlineFromTimeout(input, mobileNavigationTimeoutMs(input));
|
|
1064
1055
|
let visibleTestIds = await readVisiblePerpsTestIds(input, deadline);
|
|
1065
1056
|
let backTestId = PERPS_MARKET_DETAILS_BACK_TEST_IDS.find((testId) =>
|
|
1066
1057
|
visibleTestIds.has(testId),
|
|
@@ -1482,6 +1473,16 @@ export async function placeOrder(input) {
|
|
|
1482
1473
|
);
|
|
1483
1474
|
}
|
|
1484
1475
|
if (result?.success === false || result == null) {
|
|
1476
|
+
// Mobile keeps no placement receipt, so a 429 is never retried: nothing proves the venue saw no order.
|
|
1477
|
+
if (isRateLimited(result?.error)) {
|
|
1478
|
+
throw rateLimitedError({
|
|
1479
|
+
call: 'PerpsController.placeOrder',
|
|
1480
|
+
attempts: 1,
|
|
1481
|
+
waitedMs: 0,
|
|
1482
|
+
reason: `no retry, since nothing proves the ${symbol} order was not placed`,
|
|
1483
|
+
next: `read the ${symbol} positions and orders, then wait a minute and rerun only if nothing was placed`,
|
|
1484
|
+
});
|
|
1485
|
+
}
|
|
1485
1486
|
throw new Error(`Failed to place ${symbol} ${side}: ${result?.error || JSON.stringify(result)}`);
|
|
1486
1487
|
}
|
|
1487
1488
|
if (orderType === 'limit') {
|
|
@@ -1746,9 +1747,20 @@ function mergeStateConfig(defaults, params) {
|
|
|
1746
1747
|
};
|
|
1747
1748
|
}
|
|
1748
1749
|
|
|
1749
|
-
|
|
1750
|
+
// The policy owns positions, orders and strategies. An explicit request for one
|
|
1751
|
+
// of them would be dropped, so it fails before any phase runs.
|
|
1752
|
+
export function applyTradingStatePolicy(config, requested) {
|
|
1750
1753
|
const policy = config.trading_state;
|
|
1751
1754
|
if (policy === undefined) return config;
|
|
1755
|
+
const dropped = ['positions', 'orders', 'strategies'].filter(
|
|
1756
|
+
(key) => requested[key] !== undefined && requested[key] !== false,
|
|
1757
|
+
);
|
|
1758
|
+
if (dropped.length > 0) {
|
|
1759
|
+
throw new Error(
|
|
1760
|
+
`metamask.perps.start_state trading_state=${String(policy)} sets positions, orders and strategies itself, so the requested ${dropped.join(', ')} would be ignored. ` +
|
|
1761
|
+
`Omit trading_state to apply ${dropped.join(', ')}, or request ${dropped.join(', ')} in a separate metamask.perps.start_state node.`,
|
|
1762
|
+
);
|
|
1763
|
+
}
|
|
1752
1764
|
if (policy === 'preserve') {
|
|
1753
1765
|
return { ...config, positions: false, orders: false, strategies: false };
|
|
1754
1766
|
}
|
|
@@ -1983,16 +1995,15 @@ export async function startState(input) {
|
|
|
1983
1995
|
const params = paramsForState(input);
|
|
1984
1996
|
const config = applyTradingStatePolicy(
|
|
1985
1997
|
mergeStateConfig(profileDefaults(params.profile), params),
|
|
1998
|
+
params,
|
|
1986
1999
|
);
|
|
1987
|
-
const
|
|
1988
|
-
const
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
);
|
|
1995
|
-
}
|
|
2000
|
+
const budget = createSetupBudget(input, 'metamask.perps.start_state');
|
|
2001
|
+
const navigationSlice = { sliceMs: budget.navigationMs };
|
|
2002
|
+
// Product edits require an app restart before proof. Mobile relocks on restart,
|
|
2003
|
+
// so a deterministic Perps start state must restore the fixture-backed wallet
|
|
2004
|
+
// prerequisite before it navigates or converges controller state.
|
|
2005
|
+
const wallet = await budget.phase('unlock', (phaseInput) => {
|
|
2006
|
+
const remainingMs = phaseInput.node.timeout_ms;
|
|
1996
2007
|
const configuredTargetMs = Number(input.node?.target_timeout_ms);
|
|
1997
2008
|
const targetTimeoutMs = Math.min(
|
|
1998
2009
|
remainingMs,
|
|
@@ -2007,75 +2018,78 @@ export async function startState(input) {
|
|
|
2007
2018
|
? configuredUnlockMs
|
|
2008
2019
|
: remainingMs,
|
|
2009
2020
|
);
|
|
2010
|
-
return {
|
|
2011
|
-
...
|
|
2021
|
+
return ensureUnlocked({
|
|
2022
|
+
...phaseInput,
|
|
2023
|
+
action: 'metamask.wallet.ensure_unlocked',
|
|
2012
2024
|
node: {
|
|
2013
|
-
...
|
|
2014
|
-
|
|
2015
|
-
timeout_ms: remainingMs,
|
|
2016
|
-
bridge_timeout_ms: remainingMs,
|
|
2017
|
-
cdp_timeout_ms: remainingMs,
|
|
2018
|
-
controller_ready_timeout_ms: remainingMs,
|
|
2025
|
+
...phaseInput.node,
|
|
2026
|
+
action: 'metamask.wallet.ensure_unlocked',
|
|
2019
2027
|
target_timeout_ms: targetTimeoutMs,
|
|
2020
2028
|
unlock_timeout_ms: unlockTimeoutMs,
|
|
2021
2029
|
},
|
|
2022
|
-
};
|
|
2023
|
-
};
|
|
2024
|
-
// Product edits require an app restart before proof. Mobile relocks on restart,
|
|
2025
|
-
// so a deterministic Perps start state must restore the fixture-backed wallet
|
|
2026
|
-
// prerequisite before it navigates or converges controller state.
|
|
2027
|
-
const walletInput = stateInput();
|
|
2028
|
-
const wallet = await ensureUnlocked({
|
|
2029
|
-
...walletInput,
|
|
2030
|
-
action: 'metamask.wallet.ensure_unlocked',
|
|
2031
|
-
node: { ...walletInput.node, action: 'metamask.wallet.ensure_unlocked' },
|
|
2030
|
+
});
|
|
2032
2031
|
});
|
|
2033
|
-
const accountInput = stateInput();
|
|
2034
2032
|
const account = typeof config.account === 'string' && config.account.trim()
|
|
2035
|
-
? await selectAccount({
|
|
2036
|
-
...
|
|
2033
|
+
? await budget.phase('account', (phaseInput) => selectAccount({
|
|
2034
|
+
...phaseInput,
|
|
2037
2035
|
action: 'metamask.wallet.select_account',
|
|
2038
2036
|
node: {
|
|
2039
|
-
...
|
|
2037
|
+
...phaseInput.node,
|
|
2040
2038
|
action: 'metamask.wallet.select_account',
|
|
2041
2039
|
...(/^0x[0-9a-f]{40}$/iu.test(config.account.trim())
|
|
2042
2040
|
? { address: config.account.trim() }
|
|
2043
2041
|
: { name: config.account.trim() }),
|
|
2044
2042
|
},
|
|
2045
|
-
})
|
|
2043
|
+
}))
|
|
2046
2044
|
: { skipped: true };
|
|
2047
2045
|
// Entering Perps initializes the provider client on a freshly launched app.
|
|
2048
2046
|
// State reads before this navigation fail with CLIENT_NOT_INITIALIZED.
|
|
2049
|
-
const navigation = await
|
|
2050
|
-
|
|
2051
|
-
|
|
2047
|
+
const navigation = await budget.phase(
|
|
2048
|
+
'navigation',
|
|
2049
|
+
(phaseInput) => applyStateNavigation(phaseInput, config),
|
|
2050
|
+
navigationSlice,
|
|
2051
|
+
);
|
|
2052
|
+
const provider = await budget.phase('provider', (phaseInput) =>
|
|
2053
|
+
ensureProvider(phaseInput, config));
|
|
2054
|
+
const network = await budget.phase('network', (phaseInput) =>
|
|
2055
|
+
ensureNetwork(phaseInput, config));
|
|
2052
2056
|
let resolvedNavigation = provider.changed || network.changed
|
|
2053
|
-
? await
|
|
2057
|
+
? await budget.phase(
|
|
2058
|
+
'navigation after provider or network change',
|
|
2059
|
+
(phaseInput) => applyStateNavigation(phaseInput, config),
|
|
2060
|
+
navigationSlice,
|
|
2061
|
+
)
|
|
2054
2062
|
: navigation;
|
|
2055
|
-
const modeInput = stateInput();
|
|
2056
2063
|
const marketMode = config.market_mode
|
|
2057
|
-
? await ensureMode({
|
|
2058
|
-
...
|
|
2064
|
+
? await budget.phase('market mode', (phaseInput) => ensureMode({
|
|
2065
|
+
...phaseInput,
|
|
2059
2066
|
action: 'metamask.perps.ensure_mode',
|
|
2060
2067
|
node: {
|
|
2061
|
-
...
|
|
2068
|
+
...phaseInput.node,
|
|
2062
2069
|
action: 'metamask.perps.ensure_mode',
|
|
2063
2070
|
mode: config.market_mode,
|
|
2064
2071
|
},
|
|
2065
|
-
})
|
|
2072
|
+
}))
|
|
2066
2073
|
: { skipped: true };
|
|
2067
2074
|
if (marketMode.alreadySelected === false) {
|
|
2068
|
-
resolvedNavigation = await
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
config
|
|
2076
|
-
)
|
|
2077
|
-
|
|
2078
|
-
const
|
|
2075
|
+
resolvedNavigation = await budget.phase(
|
|
2076
|
+
'navigation after mode change',
|
|
2077
|
+
(phaseInput) => applyStateNavigation(phaseInput, config),
|
|
2078
|
+
navigationSlice,
|
|
2079
|
+
);
|
|
2080
|
+
}
|
|
2081
|
+
const tutorial = await budget.phase('tutorial', (phaseInput) =>
|
|
2082
|
+
applyTutorialState(phaseInput, config));
|
|
2083
|
+
const readyToTrade = await budget.phase('ready to trade', (phaseInput) =>
|
|
2084
|
+
assertReadyToTrade(phaseInput, config));
|
|
2085
|
+
const balance = await budget.phase('balance', (phaseInput) =>
|
|
2086
|
+
assertBalance(phaseInput, config));
|
|
2087
|
+
const strategies = await budget.phase('strategies', (phaseInput) =>
|
|
2088
|
+
applyStrategiesState(phaseInput, config.strategies));
|
|
2089
|
+
const orders = await budget.phase('orders', (phaseInput) =>
|
|
2090
|
+
applyOrdersState(phaseInput, config.orders));
|
|
2091
|
+
const positions = await budget.phase('positions', (phaseInput) =>
|
|
2092
|
+
applyPositionsState(phaseInput, config.positions));
|
|
2079
2093
|
return {
|
|
2080
2094
|
action: input.action,
|
|
2081
2095
|
profile: config.profile ?? params.profile ?? null,
|
|
@@ -2104,16 +2118,7 @@ export async function startState(input) {
|
|
|
2104
2118
|
}
|
|
2105
2119
|
|
|
2106
2120
|
export async function teardownState(input) {
|
|
2107
|
-
const
|
|
2108
|
-
const deadline = deadlineFromTimeout(input, timeoutMs);
|
|
2109
|
-
const stateInput = (phase) => {
|
|
2110
|
-
if (Date.now() >= deadline) {
|
|
2111
|
-
throw new Error(
|
|
2112
|
-
`metamask.perps.teardown_state timed out before ${phase}.`,
|
|
2113
|
-
);
|
|
2114
|
-
}
|
|
2115
|
-
return withRemainingDeadline(input, deadline);
|
|
2116
|
-
};
|
|
2121
|
+
const budget = createSetupBudget(input, 'metamask.perps.teardown_state');
|
|
2117
2122
|
const params = paramsForState(input);
|
|
2118
2123
|
const defaults = {
|
|
2119
2124
|
provider: 'hyperliquid',
|
|
@@ -2124,28 +2129,23 @@ export async function teardownState(input) {
|
|
|
2124
2129
|
strategies: params.market || params.symbol || params.markets || params.symbols ? { state: 'none', mode: 'matching' } : false,
|
|
2125
2130
|
};
|
|
2126
2131
|
const config = mergeStateConfig(defaults, params);
|
|
2127
|
-
const environment = await
|
|
2128
|
-
|
|
2129
|
-
config,
|
|
2130
|
-
);
|
|
2131
|
-
const strategies = await applyStrategiesState(
|
|
2132
|
-
stateInput('cleaning strategies'),
|
|
2133
|
-
config.strategies,
|
|
2134
|
-
);
|
|
2135
|
-
const orders = await applyOrdersState(
|
|
2136
|
-
stateInput('cleaning orders'),
|
|
2137
|
-
config.orders,
|
|
2138
|
-
);
|
|
2139
|
-
const positions = await applyPositionsState(
|
|
2140
|
-
stateInput('closing positions'),
|
|
2141
|
-
config.positions,
|
|
2132
|
+
const environment = await budget.phase(
|
|
2133
|
+
'checking the requested environment',
|
|
2134
|
+
(phaseInput) => assertRequestedRuntimeState(phaseInput, config),
|
|
2142
2135
|
);
|
|
2136
|
+
const strategies = await budget.phase('cleaning strategies', (phaseInput) =>
|
|
2137
|
+
applyStrategiesState(phaseInput, config.strategies));
|
|
2138
|
+
const orders = await budget.phase('cleaning orders', (phaseInput) =>
|
|
2139
|
+
applyOrdersState(phaseInput, config.orders));
|
|
2140
|
+
const positions = await budget.phase('closing positions', (phaseInput) =>
|
|
2141
|
+
applyPositionsState(phaseInput, config.positions));
|
|
2143
2142
|
const navigation =
|
|
2144
2143
|
config.page === false
|
|
2145
2144
|
? { skipped: true }
|
|
2146
|
-
: await
|
|
2147
|
-
|
|
2148
|
-
config,
|
|
2145
|
+
: await budget.phase(
|
|
2146
|
+
'restoring the requested page',
|
|
2147
|
+
(phaseInput) => applyStateNavigation(phaseInput, config),
|
|
2148
|
+
{ sliceMs: budget.navigationMs },
|
|
2149
2149
|
);
|
|
2150
2150
|
return {
|
|
2151
2151
|
action: input.action,
|