@deeeed/metamask-harness 0.47.4 → 0.49.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 +12 -0
- package/README.md +3 -2
- package/adapters/extension/lib/macos-focus.cjs +2 -2
- package/adapters/extension/live.sh +6 -8
- package/adapters/mobile/bridge-runtime/lib/bridge-errors.cjs +1 -0
- package/adapters/shared/ensure-runner-deps.sh +6 -0
- package/dist/adapters/extension/runtime-decision.js +18 -1
- package/dist/adapters/extension/runtime.js +5 -5
- package/dist/adapters/extension/surface.js +1 -0
- package/dist/app-lifecycle.js +3 -1
- package/dist/command-contract.js +2 -0
- package/dist/commands/call.js +1 -8
- package/dist/commands/device-target.js +12 -3
- package/dist/commands/doctor.js +7 -5
- package/dist/commands/fixtures.js +1 -1
- package/dist/commands/help.js +17 -5
- package/dist/commands/launch/index.js +10 -0
- package/dist/commands/manifest.js +3 -1
- package/dist/commands/mobile-device-view.js +13 -3
- package/dist/commands/parse-args.js +2 -0
- package/dist/commands/run-engine.js +29 -4
- package/dist/commands/run.js +1 -1
- package/dist/commands/runtime-launch.js +10 -1
- package/dist/commands/status.js +8 -4
- package/dist/devices.js +31 -9
- package/dist/execution-provenance.js +8 -7
- package/dist/funding-execution-context.js +62 -17
- package/dist/heal-bounds.js +3 -3
- package/dist/live-adapter-contract.js +4 -0
- package/dist/manifest.js +2 -13
- package/dist/metamask-action-validation.js +3 -2
- package/dist/mm-harness-cli.js +4 -2
- package/dist/runner.js +14 -3
- package/docs/RECIPES.md +135 -24
- package/docs/RELEASE-QA-CAPABILITY-MAP.md +3 -2
- package/library/actions/core/perps/_controller.mjs +24 -24
- package/library/actions/core/perps/assert_orders.mjs +3 -5
- package/library/actions/core/perps/assert_positions.mjs +3 -5
- package/library/actions/core/perps/close_orders.mjs +39 -19
- package/library/actions/core/perps/close_positions.mjs +53 -12
- package/library/actions/core/perps/edit_order.mjs +2 -0
- package/library/actions/core/perps/ensure_orders.mjs +3 -4
- package/library/actions/core/perps/ensure_positions.mjs +4 -5
- package/library/actions/core/perps/place_order.mjs +7 -14
- package/library/actions/core/perps/read_account.mjs +2 -0
- package/library/actions/core/perps/read_orders.mjs +2 -0
- package/library/actions/core/perps/read_positions.mjs +3 -1
- package/library/actions/core/perps/read_snapshot.mjs +3 -0
- package/library/actions/core/perps/start_state.mjs +3 -14
- package/library/actions/core/perps/teardown_state.mjs +3 -14
- package/library/actions/core/perps/update_position_tpsl.mjs +13 -19
- package/library/actions/core/wallet/list_accounts.mjs +3 -0
- package/library/actions/extension/analytics/consent.mjs +2 -0
- package/library/actions/extension/analytics/set_consent.mjs +2 -0
- package/library/actions/extension/assets/home-token-identity.mjs +31 -0
- package/library/actions/extension/assets/import_custom_token.mjs +63 -79
- package/library/actions/extension/assets/open_details.mjs +19 -7
- package/library/actions/extension/assets/prepare_send.mjs +1 -0
- package/library/actions/extension/assets/read_details.mjs +62 -29
- package/library/actions/extension/assets/read_visible_state.mjs +5 -1
- package/library/actions/extension/assets/review_send.mjs +1 -0
- package/library/actions/extension/assets/select_network_scope.mjs +17 -9
- package/library/actions/extension/assets/send.mjs +2 -0
- package/library/actions/extension/assets/set_custom_gas.mjs +1 -0
- package/library/actions/extension/assets/set_token_visibility.mjs +86 -17
- package/library/actions/extension/assets/verify_sorting.mjs +63 -14
- package/library/actions/extension/deeplink/open.mjs +1 -0
- package/library/actions/extension/networks/add_chainlist.mjs +2 -0
- package/library/actions/extension/networks/add_custom.mjs +21 -17
- package/library/actions/extension/networks/custom_network.mjs +10 -3
- package/library/actions/extension/networks/read_visible_state.mjs +2 -0
- package/library/actions/extension/networks/remove_custom.mjs +1 -0
- package/library/actions/extension/performance/_navigation-memory.mjs +37 -6
- package/library/actions/extension/performance/compare_idle_navigation_memory.mjs +5 -2
- package/library/actions/extension/performance/measure_detached_dom.mjs +5 -2
- package/library/actions/extension/performance/measure_navigation_memory.mjs +5 -2
- package/library/actions/extension/perps/assert_orders.mjs +1 -0
- package/library/actions/extension/perps/assert_positions.mjs +1 -0
- package/library/actions/extension/perps/assert_visible_consistency.mjs +11 -4
- package/library/actions/extension/perps/close_orders.mjs +4 -1
- package/library/actions/extension/perps/close_positions.mjs +1 -0
- package/library/actions/extension/perps/close_visible_position.mjs +160 -11
- package/library/actions/extension/perps/compare_provider_market.mjs +2 -0
- package/library/actions/extension/perps/edit_margin.mjs +3 -1
- package/library/actions/extension/perps/ensure_orders.mjs +1 -0
- package/library/actions/extension/perps/ensure_positions.mjs +1 -0
- package/library/actions/extension/perps/mutation-receipt.mjs +54 -3
- package/library/actions/extension/perps/open_balance_action.mjs +17 -7
- package/library/actions/extension/perps/open_position_action.mjs +1 -0
- package/library/actions/extension/perps/perps.mjs +550 -69
- package/library/actions/extension/perps/place_order.mjs +1 -0
- package/library/actions/extension/perps/read_eligibility.mjs +1 -0
- package/library/actions/extension/perps/read_funds_confirmation.mjs +10 -0
- package/library/actions/extension/perps/read_orders.mjs +1 -0
- package/library/actions/extension/perps/read_positions.mjs +1 -0
- package/library/actions/extension/perps/read_snapshot.mjs +4 -1
- package/library/actions/extension/perps/read_visible_state.mjs +12 -6
- package/library/actions/extension/perps/search_markets.mjs +8 -2
- package/library/actions/extension/perps/select_activity_filter.mjs +2 -1
- package/library/actions/extension/perps/select_market_filter.mjs +75 -45
- package/library/actions/extension/perps/set_market_favorite.mjs +7 -3
- package/library/actions/extension/perps/state.mjs +2 -0
- package/library/actions/extension/perps/update_position_tpsl.mjs +3 -1
- package/library/actions/extension/perps/visible-mutation-identity.mjs +6 -2
- package/library/actions/extension/platform/cdp.mjs +50 -142
- package/library/actions/extension/settings/set_basic_functionality.mjs +1 -0
- package/library/actions/extension/swap_bridge/quote-state.mjs +56 -0
- package/library/actions/extension/swap_bridge/read_visible_state.mjs +5 -1
- package/library/actions/extension/swap_bridge/select_assets.mjs +12 -1
- package/library/actions/extension/swap_bridge/set_amount.mjs +11 -8
- package/library/actions/extension/swap_bridge/set_max_amount.mjs +18 -18
- package/library/actions/extension/swap_bridge/set_slippage.mjs +1 -9
- package/library/actions/extension/ui/locators.mjs +1 -0
- package/library/actions/extension/ui/navigate.mjs +60 -14
- package/library/actions/extension/wallet/ensure_unlocked.mjs +2 -0
- package/library/actions/extension/wallet/import.mjs +2 -0
- package/library/actions/extension/wallet/list_accounts.mjs +1 -0
- package/library/actions/extension/wallet/lock.mjs +4 -1
- package/library/actions/extension/wallet/read_state.mjs +1 -0
- package/library/actions/extension/wallet/reset.mjs +2 -0
- package/library/actions/extension/wallet/secret-input.mjs +2 -0
- package/library/actions/extension/wallet/select_account.mjs +2 -0
- package/library/actions/extension/wallet/setup.mjs +1 -0
- package/library/actions/extension/wallet/state.mjs +2 -0
- package/library/actions/extension/wallet/visible-session.mjs +2 -0
- package/library/actions/mobile/analytics/consent-settings.mjs +1 -0
- package/library/actions/mobile/analytics/set_consent.mjs +1 -0
- package/library/actions/mobile/app/network-control.mjs +2 -0
- package/library/actions/mobile/app/network.mjs +1 -0
- package/library/actions/mobile/app/network_assert.mjs +1 -0
- package/library/actions/mobile/app/network_capture.mjs +1 -0
- package/library/actions/mobile/assets/import_custom_token.mjs +3 -0
- package/library/actions/mobile/assets/open_details.mjs +1 -0
- package/library/actions/mobile/assets/read_details.mjs +1 -0
- package/library/actions/mobile/assets/read_visible_state.mjs +4 -1
- package/library/actions/mobile/assets/set_token_visibility.mjs +15 -9
- package/library/actions/mobile/assets/verify_sorting.mjs +90 -7
- package/library/actions/mobile/deeplink/open.mjs +1 -0
- package/library/actions/mobile/networks/add_custom.mjs +1 -0
- package/library/actions/mobile/networks/network-management.mjs +23 -15
- package/library/actions/mobile/networks/read_visible_state.mjs +1 -0
- package/library/actions/mobile/networks/remove_custom.mjs +7 -2
- package/library/actions/mobile/perps/assert_orders.mjs +1 -0
- package/library/actions/mobile/perps/assert_positions.mjs +1 -0
- package/library/actions/mobile/perps/capture_performance.mjs +1 -0
- package/library/actions/mobile/perps/close_orders.mjs +1 -0
- package/library/actions/mobile/perps/close_positions.mjs +1 -0
- package/library/actions/mobile/perps/ensure_orders.mjs +1 -0
- package/library/actions/mobile/perps/ensure_positions.mjs +1 -0
- package/library/actions/mobile/perps/measure_homepage_visible.mjs +1 -0
- package/library/actions/mobile/perps/performance-capture.mjs +2 -0
- package/library/actions/mobile/perps/perps.mjs +8 -1
- package/library/actions/mobile/perps/place_order.mjs +1 -0
- package/library/actions/mobile/perps/prepare_local_snapshot_endpoint.mjs +2 -0
- package/library/actions/mobile/perps/read-visible-state-loop.mjs +3 -0
- package/library/actions/mobile/perps/read_orders.mjs +1 -0
- package/library/actions/mobile/perps/read_positions.mjs +1 -0
- package/library/actions/mobile/perps/read_visible_state.mjs +1 -0
- package/library/actions/mobile/perps/search_markets.mjs +1 -0
- package/library/actions/mobile/perps/set_market_favorite.mjs +17 -19
- package/library/actions/mobile/platform/bridge.mjs +16 -4
- package/library/actions/mobile/platform/native-session-name.mjs +1 -0
- package/library/actions/mobile/platform/native-session.mjs +28 -17
- package/library/actions/mobile/platform/observe-ui.mjs +4 -2
- package/library/actions/mobile/platform/tool-paths.mjs +1 -0
- package/library/actions/mobile/swap_bridge/native-session.mjs +1 -0
- package/library/actions/mobile/swap_bridge/read_visible_state.mjs +1 -0
- package/library/actions/mobile/swap_bridge/select_assets.mjs +2 -0
- package/library/actions/mobile/swap_bridge/set_amount.mjs +1 -0
- package/library/actions/mobile/swap_bridge/set_max_amount.mjs +1 -0
- package/library/actions/mobile/swap_bridge/set_slippage.mjs +1 -0
- package/library/actions/mobile/swap_bridge/submit_transaction.mjs +2 -0
- package/library/actions/mobile/ui/locators.mjs +1 -0
- package/library/actions/mobile/ui/native-navigation.mjs +2 -0
- package/library/actions/mobile/ui/navigate.mjs +3 -1
- package/library/actions/mobile/wallet/ensure_unlocked.mjs +2 -0
- package/library/actions/mobile/wallet/home.mjs +1 -0
- package/library/actions/mobile/wallet/import.mjs +2 -0
- package/library/actions/mobile/wallet/list_accounts.mjs +1 -0
- package/library/actions/mobile/wallet/lock.mjs +1 -0
- package/library/actions/mobile/wallet/native-ui.mjs +2 -0
- package/library/actions/mobile/wallet/read_state.mjs +2 -0
- package/library/actions/mobile/wallet/reset-helper.mjs +1 -0
- package/library/actions/mobile/wallet/reset.mjs +1 -0
- package/library/actions/mobile/wallet/select_account.mjs +2 -0
- package/library/actions/mobile/wallet/setup.mjs +2 -0
- package/library/actions/shared/analytics/assert_events.mjs +3 -0
- package/library/actions/shared/analytics/consent.mjs +3 -0
- package/library/actions/shared/analytics/read_events.mjs +2 -0
- package/library/actions/shared/analytics/start_capture.mjs +3 -0
- package/library/actions/shared/app/network-artifact.mjs +2 -0
- package/library/actions/shared/app/network-assert.mjs +2 -0
- package/library/actions/shared/assets/visible-state.mjs +2 -0
- package/library/actions/shared/deeplink/url.mjs +3 -0
- package/library/actions/shared/networks/visible-state.mjs +2 -0
- package/library/actions/shared/perps/visible-state.mjs +16 -3
- package/library/actions/shared/swap-bridge/transaction.mjs +3 -0
- package/library/actions/shared/swap-bridge/visible-state.mjs +2 -0
- package/library/actions/shared/ui/locators.mjs +2 -0
- package/library/actions/shared/wallet/import-source.mjs +3 -0
- package/library/manifests/core.action-manifest.json +34 -2
- package/library/manifests/extension.action-manifest.json +50 -17
- package/library/manifests/mobile.action-manifest.json +11 -10
- package/library/recipes/core/perps/smoke.recipe.json +23 -0
- package/library/recipes/core/perps/snapshot.recipe.json +26 -1
- package/library/recipes/extension/assets/release-custom-network-token-persistence.recipe.json +25 -8
- package/library/recipes/extension/performance/navigation-memory-suite.recipe.json +219 -0
- package/library/recipes/extension/performance/navigation-memory.recipe.json +61 -156
- package/library/recipes/extension/wallet/smoke.recipe.json +17 -8
- package/library/recipes/mobile/app/lifecycle-smoke.recipe.json +89 -0
- package/library/recipes/mobile/networks/release-custom-network-persistence.recipe.json +31 -6
- package/library/recipes/mobile/networks/release-multichain-visibility.recipe.json +5 -4
- package/library/recipes/mobile/wallet/smoke.recipe.json +17 -8
- package/library/recipes/{assets → shared/assets}/release-token-address-import.recipe.json +7 -0
- package/library/recipes/{assets → shared/assets}/release-token-details.recipe.json +2 -2
- package/library/recipes/shared/assets/release-token-hide-readd.recipe.json +91 -0
- package/library/recipes/{assets → shared/assets}/release-token-persistence.recipe.json +3 -0
- package/library/recipes/{assets → shared/assets}/release-token-search-import.recipe.json +4 -1
- package/library/recipes/{assets → shared/assets}/release-token-sorting.recipe.json +13 -2
- package/library/recipes/{swap-bridge → shared/swap-bridge}/release-quote.recipe.json +29 -3
- package/package.json +3 -3
- package/site/assets/help-recipes.json +220 -27
- package/library/recipes/assets/release-token-hide-readd.recipe.json +0 -57
- package/library/recipes/core/perps/clean-market-testnet.recipe.json +0 -44
- package/library/recipes/core/perps/order-lifecycle.recipe.json +0 -82
- package/library/recipes/core/perps/read-markets.recipe.json +0 -36
- package/library/recipes/core/perps/trading-lifecycle.recipe.json +0 -80
- package/library/recipes/extension/perps/release-activity-filters.recipe.json +0 -51
- package/library/recipes/extension/perps/release-funds-flows.recipe.json +0 -76
- package/library/recipes/extension/perps/release-live-limit-order.recipe.json +0 -109
- package/library/recipes/extension/perps/release-market-filters.recipe.json +0 -97
- package/library/recipes/extension/perps/release-order-entry.recipe.json +0 -158
- package/library/recipes/extension/perps/release-order-validation.recipe.json +0 -259
- package/library/recipes/extension/perps/release-visible-consistency.recipe.json +0 -47
- package/library/recipes/extension/perps/source-dev-snapshot-consistency.recipe.json +0 -76
- package/library/recipes/mobile/app/lifecycle.android-smoke.recipe.json +0 -69
- package/library/recipes/mobile/perps/chase-assert-running.recipe.json +0 -110
- package/library/recipes/mobile/perps/chase-place.recipe.json +0 -145
- package/library/recipes/mobile/perps/chase-terminate.recipe.json +0 -98
- package/library/recipes/mobile/perps/performance.recipe.json +0 -722
- package/library/recipes/mobile/perps/pro-order-setup.recipe.json +0 -111
- package/library/recipes/mobile/perps/pro-order-start-state.recipe.json +0 -53
- package/library/recipes/mobile/perps/scale-assert-orders.recipe.json +0 -126
- package/library/recipes/mobile/perps/scale-place.recipe.json +0 -186
- package/library/recipes/mobile/perps/twap-assert-active.recipe.json +0 -97
- package/library/recipes/mobile/perps/twap-place.recipe.json +0 -146
- package/library/recipes/mobile/runner/action-validation.recipe.json +0 -336
- package/library/recipes/perps/clean-market-testnet.recipe.json +0 -49
- package/library/recipes/perps/lifecycle.recipe.json +0 -144
- package/library/recipes/perps/release-live-market-order.recipe.json +0 -139
- package/library/recipes/perps/release-watchlist.recipe.json +0 -111
- package/library/recipes/swap-bridge/release-max-quote.recipe.json +0 -72
- /package/library/recipes/{assets → shared/assets}/release-token-list.recipe.json +0 -0
- /package/library/recipes/{perps → shared/perps}/release-market-details.recipe.json +0 -0
- /package/library/recipes/{perps → shared/perps}/release-market-search.recipe.json +0 -0
- /package/library/recipes/{swap-bridge → shared/swap-bridge}/release-custom-slippage.recipe.json +0 -0
- /package/library/recipes/{swap-bridge → shared/swap-bridge}/release-live-max-swap.recipe.json +0 -0
- /package/library/recipes/{swap-bridge → shared/swap-bridge}/release-live-swap.recipe.json +0 -0
- /package/library/recipes/{swap-bridge → shared/swap-bridge}/release-surface.recipe.json +0 -0
- /package/library/recipes/{wallet → shared/wallet}/import.recipe.json +0 -0
- /package/library/recipes/{wallet → shared/wallet}/reset-import.recipe.json +0 -0
|
@@ -1,9 +1,40 @@
|
|
|
1
|
+
// Runs navigation and idle memory measurements with forced GC, CDP metrics, and heap snapshots.
|
|
2
|
+
// Checks configured growth bounds and writes diagnostic artifacts for failed measurements.
|
|
3
|
+
import { mkdtemp, writeFile } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
1
6
|
const DEFAULT_SCREEN_TIMEOUT_MS = 15_000;
|
|
2
7
|
const DEFAULT_SETTLE_MS = 1_500;
|
|
3
8
|
const DEFAULT_GC_SETTLE_MS = 300;
|
|
4
9
|
const DEFAULT_ACTION_TIMEOUT_MS = 400_000;
|
|
5
10
|
const DEFAULT_ROUND_DURATION_MS = 10_000;
|
|
6
11
|
|
|
12
|
+
export async function withMemoryFailureReport(input, measure) {
|
|
13
|
+
try {
|
|
14
|
+
return await measure();
|
|
15
|
+
} catch (error) {
|
|
16
|
+
if (!error?.memoryMeasurement) throw error;
|
|
17
|
+
const directory = await mkdtemp(path.join(input.context.artifactsDir, 'navigation-memory-'));
|
|
18
|
+
const report = path.join(directory, 'measurement.json');
|
|
19
|
+
await writeFile(report, `${JSON.stringify({
|
|
20
|
+
nodeId: input.context.nodeId,
|
|
21
|
+
action: input.action,
|
|
22
|
+
...error.memoryMeasurement,
|
|
23
|
+
}, null, 2)}\n`, { mode: 0o600 });
|
|
24
|
+
await writeFile(input.outputPath, `${JSON.stringify({ artifacts: [{
|
|
25
|
+
path: path.relative(input.context.artifactsDir, report),
|
|
26
|
+
type: 'report',
|
|
27
|
+
nodeId: input.context.nodeId,
|
|
28
|
+
label: 'Failed memory measurement',
|
|
29
|
+
category: 'diagnostic',
|
|
30
|
+
mimeType: 'application/json',
|
|
31
|
+
}] })}\n`, { mode: 0o600 });
|
|
32
|
+
delete error.memoryMeasurement;
|
|
33
|
+
error.message += `\nNext: jq . ${JSON.stringify(report)}`;
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
7
38
|
function finiteNumber(value, fallback, label) {
|
|
8
39
|
const parsed = value ?? fallback;
|
|
9
40
|
if (!Number.isFinite(parsed)) throw new Error(`${label} must be a finite number.`);
|
|
@@ -217,9 +248,9 @@ export async function measureNavigationSlope(page, options, bounds) {
|
|
|
217
248
|
passed,
|
|
218
249
|
};
|
|
219
250
|
if (!passed.nodes || !passed.listeners) {
|
|
220
|
-
throw new Error(
|
|
251
|
+
throw Object.assign(new Error(
|
|
221
252
|
`Navigation memory bounds exceeded: nodes ${slopes.nodes.toFixed(2)}/${bounds.maxNodesPerCycle} per cycle; listeners ${slopes.listeners.toFixed(2)}/${bounds.maxListenersPerCycle} per cycle.`,
|
|
222
|
-
);
|
|
253
|
+
), { memoryMeasurement: result });
|
|
223
254
|
}
|
|
224
255
|
return result;
|
|
225
256
|
}
|
|
@@ -282,9 +313,9 @@ export async function measureDetachedDom(page, options, maxDetachedGrowth) {
|
|
|
282
313
|
passed: after - before <= maxDetachedGrowth,
|
|
283
314
|
};
|
|
284
315
|
if (!result.passed) {
|
|
285
|
-
throw new Error(
|
|
316
|
+
throw Object.assign(new Error(
|
|
286
317
|
`Detached DOM bound exceeded: growth ${result.growth}/${maxDetachedGrowth} nodes.`,
|
|
287
|
-
);
|
|
318
|
+
), { memoryMeasurement: result });
|
|
288
319
|
}
|
|
289
320
|
return result;
|
|
290
321
|
}
|
|
@@ -333,9 +364,9 @@ export async function compareIdleNavigation(page, options, maxAttributableListen
|
|
|
333
364
|
passed: attributableListenersPerCycle <= maxAttributableListenersPerCycle,
|
|
334
365
|
};
|
|
335
366
|
if (!result.passed) {
|
|
336
|
-
throw new Error(
|
|
367
|
+
throw Object.assign(new Error(
|
|
337
368
|
`Idle-control bound exceeded: attributable listeners ${attributableListenersPerCycle.toFixed(2)}/${maxAttributableListenersPerCycle} per cycle (navigation ${navigation.slopes.listeners.toFixed(2)}, idle ${idleListenersPerCycle.toFixed(2)}).`,
|
|
338
|
-
);
|
|
369
|
+
), { memoryMeasurement: result });
|
|
339
370
|
}
|
|
340
371
|
return result;
|
|
341
372
|
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
// Compares post-GC listener growth across equal-duration idle and navigation rounds against a configured bound.
|
|
2
|
+
// Writes a diagnostic report when the measurement exceeds that bound.
|
|
1
3
|
import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
4
|
import {
|
|
3
5
|
compareIdleNavigation,
|
|
4
6
|
idleControlBound,
|
|
5
7
|
navigationMemoryOptions,
|
|
8
|
+
withMemoryFailureReport,
|
|
6
9
|
} from './_navigation-memory.mjs';
|
|
7
10
|
|
|
8
11
|
runAdapter((input) =>
|
|
9
|
-
withExtensionPage(input, async (page) => ({
|
|
12
|
+
withMemoryFailureReport(input, () => withExtensionPage(input, async (page) => ({
|
|
10
13
|
action: input.action,
|
|
11
14
|
status: 'pass',
|
|
12
15
|
...(await compareIdleNavigation(
|
|
@@ -14,5 +17,5 @@ runAdapter((input) =>
|
|
|
14
17
|
navigationMemoryOptions(input.node, 'rounds', 8),
|
|
15
18
|
idleControlBound(input.node),
|
|
16
19
|
)),
|
|
17
|
-
})),
|
|
20
|
+
}))),
|
|
18
21
|
);
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
// Checks detached-node growth between post-GC heap snapshots before and after repeated screen navigation.
|
|
2
|
+
// Writes a diagnostic report when growth exceeds the configured bound.
|
|
1
3
|
import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
4
|
import {
|
|
3
5
|
detachedDomBound,
|
|
4
6
|
measureDetachedDom,
|
|
5
7
|
navigationMemoryOptions,
|
|
8
|
+
withMemoryFailureReport,
|
|
6
9
|
} from './_navigation-memory.mjs';
|
|
7
10
|
|
|
8
11
|
runAdapter((input) =>
|
|
9
|
-
withExtensionPage(input, async (page) => ({
|
|
12
|
+
withMemoryFailureReport(input, () => withExtensionPage(input, async (page) => ({
|
|
10
13
|
action: input.action,
|
|
11
14
|
status: 'pass',
|
|
12
15
|
...(await measureDetachedDom(
|
|
@@ -14,5 +17,5 @@ runAdapter((input) =>
|
|
|
14
17
|
navigationMemoryOptions(input.node, 'cycles', 5),
|
|
15
18
|
detachedDomBound(input.node),
|
|
16
19
|
)),
|
|
17
|
-
})),
|
|
20
|
+
}))),
|
|
18
21
|
);
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
// Repeatedly navigates a screen and measures post-GC DOM-node, listener, and heap growth slopes.
|
|
2
|
+
// Enforces node and listener slope bounds and writes a diagnostic report when either fails.
|
|
1
3
|
import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
4
|
import {
|
|
3
5
|
measureNavigationSlope,
|
|
4
6
|
navigationMemoryOptions,
|
|
5
7
|
navigationSlopeBounds,
|
|
8
|
+
withMemoryFailureReport,
|
|
6
9
|
} from './_navigation-memory.mjs';
|
|
7
10
|
|
|
8
11
|
runAdapter((input) =>
|
|
9
|
-
withExtensionPage(input, async (page) => ({
|
|
12
|
+
withMemoryFailureReport(input, () => withExtensionPage(input, async (page) => ({
|
|
10
13
|
action: input.action,
|
|
11
14
|
status: 'pass',
|
|
12
15
|
...(await measureNavigationSlope(
|
|
@@ -14,5 +17,5 @@ runAdapter((input) =>
|
|
|
14
17
|
navigationMemoryOptions(input.node),
|
|
15
18
|
navigationSlopeBounds(input.node),
|
|
16
19
|
)),
|
|
17
|
-
})),
|
|
20
|
+
}))),
|
|
18
21
|
);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Sample changing Perps UI values or check recent activity containment across filters.
|
|
2
|
+
// Some modes navigate lists, select filters, or open watchlist market details.
|
|
1
3
|
import { pathToFileURL } from 'node:url';
|
|
2
4
|
|
|
3
5
|
import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
@@ -86,7 +88,8 @@ async function visiblePrice(page, surface, market) {
|
|
|
86
88
|
if (!element) return false;
|
|
87
89
|
const style = getComputedStyle(element);
|
|
88
90
|
const rect = element.getBoundingClientRect();
|
|
89
|
-
return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0
|
|
91
|
+
return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0
|
|
92
|
+
&& rect.bottom > 0 && rect.right > 0 && rect.top < innerHeight && rect.left < innerWidth;
|
|
90
93
|
};
|
|
91
94
|
const root = document.querySelector('[data-testid=${JSON.stringify(rootTestId)}]');
|
|
92
95
|
const price = ${priceTestId
|
|
@@ -111,7 +114,8 @@ async function marketRows(page, maxItems) {
|
|
|
111
114
|
if (testId.startsWith('market-row-ticker-') || /-(?:token-logo|asset-label)$/u.test(testId)) return false;
|
|
112
115
|
const style = getComputedStyle(element);
|
|
113
116
|
const rect = element.getBoundingClientRect();
|
|
114
|
-
return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0
|
|
117
|
+
return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0
|
|
118
|
+
&& rect.bottom > 0 && rect.right > 0 && rect.top < innerHeight && rect.left < innerWidth;
|
|
115
119
|
}).slice(0, ${JSON.stringify(maxItems)}).map((element) => ({
|
|
116
120
|
testId: String(element.getAttribute('data-testid') || '').slice(0, 500),
|
|
117
121
|
label: String(element.innerText || '').replace(/\\s+/gu, ' ').trim().slice(0, 500),
|
|
@@ -124,7 +128,8 @@ async function watchlistRows(page, maxItems) {
|
|
|
124
128
|
].filter((element) => {
|
|
125
129
|
const style = getComputedStyle(element);
|
|
126
130
|
const rect = element.getBoundingClientRect();
|
|
127
|
-
return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0
|
|
131
|
+
return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0
|
|
132
|
+
&& rect.bottom > 0 && rect.right > 0 && rect.top < innerHeight && rect.left < innerWidth;
|
|
128
133
|
}).slice(0, ${JSON.stringify(maxItems)}).map((element) => ({
|
|
129
134
|
testId: String(element.getAttribute('data-testid') || '').slice(0, 500),
|
|
130
135
|
label: String(element.innerText || '').replace(/\\s+/gu, ' ').trim().slice(0, 500),
|
|
@@ -138,6 +143,7 @@ function changedRows(first, last) {
|
|
|
138
143
|
.map((entry) => entry.testId);
|
|
139
144
|
}
|
|
140
145
|
|
|
146
|
+
// Containment checks loaded full-list rows, including rows below the viewport.
|
|
141
147
|
async function activityIds(page, maxItems) {
|
|
142
148
|
const selector = '[data-testid^="transaction-card-"]';
|
|
143
149
|
return page.evaluate(`[
|
|
@@ -155,7 +161,8 @@ async function recentActivityState(page, maxItems) {
|
|
|
155
161
|
if (!element) return false;
|
|
156
162
|
const style = getComputedStyle(element);
|
|
157
163
|
const rect = element.getBoundingClientRect();
|
|
158
|
-
return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0
|
|
164
|
+
return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0
|
|
165
|
+
&& rect.bottom > 0 && rect.right > 0 && rect.top < innerHeight && rect.left < innerWidth;
|
|
159
166
|
};
|
|
160
167
|
return {
|
|
161
168
|
ids: [...document.querySelectorAll('[data-testid^="transaction-card-"]')]
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Cancel selected Perps orders through guarded mutation helpers and verify their absence.
|
|
1
2
|
import { runAdapter } from '../platform/cdp.mjs';
|
|
2
3
|
import { assertOrders, closeOrders } from './perps.mjs';
|
|
3
4
|
|
|
@@ -5,5 +6,7 @@ runAdapter(async (input) => {
|
|
|
5
6
|
const close = await closeOrders(input);
|
|
6
7
|
if (close.proofPath === 'visible-product-controls') return close;
|
|
7
8
|
const assertion = await assertOrders(input, false);
|
|
8
|
-
return
|
|
9
|
+
return close.proofPath === 'accepted-order-status'
|
|
10
|
+
? { ...assertion, ...close }
|
|
11
|
+
: { ...assertion, close };
|
|
9
12
|
});
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// Close visible Perps positions with funding checks and receipt-bound identity.
|
|
2
|
+
// Accepted fills require lineage checks; full closes retain their own venue identity.
|
|
3
|
+
|
|
1
4
|
import { dataTestId, runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
5
|
import {
|
|
3
6
|
assertTrustedExternalMutationTarget,
|
|
@@ -10,7 +13,7 @@ import {
|
|
|
10
13
|
trustedExternalMutationPlanIsExact,
|
|
11
14
|
withExternalMutationRecovery,
|
|
12
15
|
} from './mutation-receipt.mjs';
|
|
13
|
-
import { requireCurrentVisibleFundingBalance, requirePerpsTradingContext } from './perps.mjs';
|
|
16
|
+
import { observeHyperliquidSubmission, placementReceiptSelection, requireCurrentVisibleFundingBalance, requirePerpsTradingContext } from './perps.mjs';
|
|
14
17
|
import {
|
|
15
18
|
absoluteDelta,
|
|
16
19
|
parsePositionDetail,
|
|
@@ -58,6 +61,97 @@ async function readVisibleClosePosition(page, market) {
|
|
|
58
61
|
})()`);
|
|
59
62
|
}
|
|
60
63
|
|
|
64
|
+
async function readAcceptedFill(page, placement, market, side, visibleSize) {
|
|
65
|
+
const accepted = placement.record?.acceptance;
|
|
66
|
+
if (!accepted?.venueOrderId) throw new Error('Position cleanup requires the exact accepted venue order ID.');
|
|
67
|
+
const deadline = Date.now() + 10_000;
|
|
68
|
+
let startTime = null;
|
|
69
|
+
// Explicit end windows read current fills instead of the controller's recent-history cache.
|
|
70
|
+
while (Date.now() < deadline) {
|
|
71
|
+
const result = await page.session.call('Runtime.evaluate', {
|
|
72
|
+
expression: `(async () => {
|
|
73
|
+
const request = globalThis.stateHooks?.submitRequestToBackground;
|
|
74
|
+
if (typeof request !== 'function') throw new Error('Read-only Perps fill API is unavailable.');
|
|
75
|
+
const recent = await request('perpsGetOrderFills', [{ aggregateByTime: false, endTime: ${JSON.stringify(Date.now())} }]);
|
|
76
|
+
if (!Array.isArray(recent) || recent.some(fill => !fill || typeof fill !== 'object'
|
|
77
|
+
|| Array.isArray(fill) || !['string', 'number'].includes(typeof fill.orderId))) {
|
|
78
|
+
throw new Error('Read-only Perps fill history is malformed.');
|
|
79
|
+
}
|
|
80
|
+
const owned = recent.filter(fill => String(fill?.orderId) === ${JSON.stringify(accepted.venueOrderId)});
|
|
81
|
+
if (owned.some(fill => !Number.isSafeInteger(fill.timestamp) || fill.timestamp <= 0)) {
|
|
82
|
+
throw new Error('Accepted order has malformed fill timestamps.');
|
|
83
|
+
}
|
|
84
|
+
return owned.length ? Math.min(...owned.map(fill => fill.timestamp)) : null;
|
|
85
|
+
})()`,
|
|
86
|
+
awaitPromise: true, returnByValue: true,
|
|
87
|
+
}, { timeoutMs: Math.max(1, deadline - Date.now()) });
|
|
88
|
+
if (result.exceptionDetails) {
|
|
89
|
+
throw new Error(result.exceptionDetails.exception?.description ?? result.exceptionDetails.text ?? 'Perps fill observation failed.');
|
|
90
|
+
}
|
|
91
|
+
startTime = result.result?.value;
|
|
92
|
+
if (startTime !== null) {
|
|
93
|
+
if (!Number.isSafeInteger(startTime) || startTime <= 0) throw new Error('Accepted fill observation returned an invalid timestamp.');
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
await new Promise((resolve) => setTimeout(resolve, Math.min(250, Math.max(1, deadline - Date.now()))));
|
|
97
|
+
}
|
|
98
|
+
if (startTime === null) throw new Error('Accepted order did not reach fill history before the cleanup observation deadline.');
|
|
99
|
+
const data = await page.evaluate(`(async () => {
|
|
100
|
+
const request = globalThis.stateHooks?.submitRequestToBackground;
|
|
101
|
+
if (typeof request !== 'function') throw new Error('Read-only Perps fill API is unavailable.');
|
|
102
|
+
const startTime = ${JSON.stringify(startTime)};
|
|
103
|
+
const [fills, orders] = await Promise.all([
|
|
104
|
+
request('perpsGetOrderFills', [{ aggregateByTime: false, startTime, endTime: ${JSON.stringify(Date.now())} }]),
|
|
105
|
+
request('perpsGetOpenOrders', []),
|
|
106
|
+
]);
|
|
107
|
+
if (!Array.isArray(fills) || !Array.isArray(orders) || fills.length >= 2000) {
|
|
108
|
+
throw new Error('Read-only Perps fill coverage is incomplete.');
|
|
109
|
+
}
|
|
110
|
+
const market = ${JSON.stringify(market)};
|
|
111
|
+
return {
|
|
112
|
+
openOrderCount: orders.filter(order => String(order.symbol ?? order.coin).toUpperCase() === market).length,
|
|
113
|
+
fills: fills.filter(fill => String(fill.symbol).toUpperCase() === market).map(fill => ({
|
|
114
|
+
orderId: String(fill.orderId), side: fill.side, size: fill.size,
|
|
115
|
+
startPosition: fill.startPosition, timestamp: fill.timestamp,
|
|
116
|
+
})),
|
|
117
|
+
};
|
|
118
|
+
})()`);
|
|
119
|
+
if (data.openOrderCount !== 0) throw new Error('Cancel the exact accepted order remainder before closing its filled position.');
|
|
120
|
+
const decimal = (value) => {
|
|
121
|
+
const match = /^(-?)(0|[1-9][0-9]{0,19})(?:\.([0-9]{1,8}))?$/u.exec(String(value));
|
|
122
|
+
if (!match) throw new Error('Perps fill size identity is invalid.');
|
|
123
|
+
return BigInt(`${match[1]}${match[2]}${String(match[3] ?? '').padEnd(8, '0')}`);
|
|
124
|
+
};
|
|
125
|
+
const owned = data.fills.filter((fill) => fill.orderId === accepted.venueOrderId);
|
|
126
|
+
if (!owned.length || data.fills.some((fill) => !Number.isSafeInteger(fill.timestamp) || fill.timestamp <= 0)) {
|
|
127
|
+
throw new Error('Accepted venue order has no independently observed complete fill identity.');
|
|
128
|
+
}
|
|
129
|
+
const firstTimestamp = Math.min(...owned.map((fill) => fill.timestamp));
|
|
130
|
+
if (data.fills.some((fill) => fill.timestamp >= firstTimestamp && fill.orderId !== accepted.venueOrderId)) {
|
|
131
|
+
throw new Error('Another order changed this market after the accepted fill; refusing shared-position cleanup.');
|
|
132
|
+
}
|
|
133
|
+
const sign = side === 'long' ? 1n : -1n;
|
|
134
|
+
owned.sort((left, right) => {
|
|
135
|
+
const difference = sign * (decimal(left.startPosition) - decimal(right.startPosition));
|
|
136
|
+
return difference < 0n ? -1 : difference > 0n ? 1 : 0;
|
|
137
|
+
});
|
|
138
|
+
let position = 0n;
|
|
139
|
+
for (const fill of owned) {
|
|
140
|
+
const size = decimal(fill.size);
|
|
141
|
+
if (fill.side !== (side === 'long' ? 'buy' : 'sell') || size <= 0n || decimal(fill.startPosition) !== position) {
|
|
142
|
+
throw new Error('Accepted order fills do not prove an exclusively owned position starting from zero.');
|
|
143
|
+
}
|
|
144
|
+
position += sign * size;
|
|
145
|
+
}
|
|
146
|
+
const sizeMatch = /^([0-9.,]+) ([A-Z0-9:_-]{2,20})$/u.exec(visibleSize);
|
|
147
|
+
if (!sizeMatch || sizeMatch[2] !== market || decimal(sizeMatch[1].replace(/,/gu, '')) !== sign * position
|
|
148
|
+
|| sign * position > decimal(accepted.size)) {
|
|
149
|
+
throw new Error('Visible position size does not equal the exact accepted-order fills.');
|
|
150
|
+
}
|
|
151
|
+
return { venueOrderId: accepted.venueOrderId, fillCount: owned.length, size: visibleSize,
|
|
152
|
+
firstFillTimestamp: firstTimestamp, lastFillTimestamp: Math.max(...owned.map((fill) => fill.timestamp)) };
|
|
153
|
+
}
|
|
154
|
+
|
|
61
155
|
async function prepareCloseModal(page, partialAmount) {
|
|
62
156
|
let confirmation;
|
|
63
157
|
for (let attempt = 0; attempt < 3 && !confirmation; attempt += 1) {
|
|
@@ -75,6 +169,7 @@ async function prepareCloseModal(page, partialAmount) {
|
|
|
75
169
|
return {
|
|
76
170
|
amount: String(document.querySelector('[data-testid="close-amount-value"] input')?.value || read('close-amount-value')),
|
|
77
171
|
fullSizeSelected: Boolean(document.querySelector('[data-testid="close-amount-slider-pct-100"]')),
|
|
172
|
+
marketOrder: !modal.querySelector('[data-testid="limit-price-input"]'),
|
|
78
173
|
margin: read('perps-close-summary-margin-value'),
|
|
79
174
|
fees: read('perps-close-summary-fees-value'),
|
|
80
175
|
receive: read('perps-close-summary-receive-value'),
|
|
@@ -129,6 +224,20 @@ async function proveGlobalCleanup(page, balanceBeforeBaseUnits, toleranceBaseUni
|
|
|
129
224
|
};
|
|
130
225
|
}
|
|
131
226
|
|
|
227
|
+
function requireCloseAcceptance(receipt, maximumBaseUnits) {
|
|
228
|
+
const accepted = receipt?.acceptance;
|
|
229
|
+
const size = receipt?.facts?.closeSizing?.size;
|
|
230
|
+
if (!accepted?.venueOrderId || accepted.status !== 'filled' || Number(accepted.filledSize) !== Number(size)
|
|
231
|
+
|| accepted.venueOrderId === receipt?.facts?.acceptedFill?.venueOrderId) {
|
|
232
|
+
throw new Error('Full close has no distinct accepted full-fill identity.');
|
|
233
|
+
}
|
|
234
|
+
const notional = Math.ceil(Number(accepted.filledSize) * Number(accepted.averagePrice) * 1_000_000);
|
|
235
|
+
if (!Number.isSafeInteger(notional) || notional <= 0 || BigInt(notional) > BigInt(maximumBaseUnits)) {
|
|
236
|
+
throw new Error('Accepted close fill exceeds the trusted operation cap.');
|
|
237
|
+
}
|
|
238
|
+
return { venueOrderId: accepted.venueOrderId, filledSize: accepted.filledSize, averagePrice: accepted.averagePrice };
|
|
239
|
+
}
|
|
240
|
+
|
|
132
241
|
export async function closeVisiblePosition(input) {
|
|
133
242
|
return withExtensionPage(input, async (page) => {
|
|
134
243
|
const market = marketSymbol(input.node?.market);
|
|
@@ -176,6 +285,7 @@ export async function closeVisiblePosition(input) {
|
|
|
176
285
|
roundingToleranceBaseUnits,
|
|
177
286
|
...(cleanupOnly ? { positionSize: fundingPositionSize, cleanupOnly: true } : {}),
|
|
178
287
|
});
|
|
288
|
+
const closeBudget = trustedExternalMutationBudget(input, 'perps-close-position');
|
|
179
289
|
const existingReceipt = await readExternalMutationReceipt(input, identity);
|
|
180
290
|
if (existingReceipt) {
|
|
181
291
|
return withExternalMutationRecovery(existingReceipt, async () => {
|
|
@@ -187,7 +297,8 @@ export async function closeVisiblePosition(input) {
|
|
|
187
297
|
dispatch: async () => {
|
|
188
298
|
throw new Error('resumed close must not dispatch');
|
|
189
299
|
},
|
|
190
|
-
observe: async () => {
|
|
300
|
+
observe: async ({ receipt }) => {
|
|
301
|
+
const acceptedClose = partialAmount == null ? requireCloseAcceptance(receipt, closeBudget.maxBaseUnits) : null;
|
|
191
302
|
const deadline = Date.now() + 45_000;
|
|
192
303
|
let finalState;
|
|
193
304
|
let absentSince = null;
|
|
@@ -218,6 +329,7 @@ export async function closeVisiblePosition(input) {
|
|
|
218
329
|
);
|
|
219
330
|
return {
|
|
220
331
|
success: true,
|
|
332
|
+
...acceptedClose,
|
|
221
333
|
finalState,
|
|
222
334
|
cleanup,
|
|
223
335
|
};
|
|
@@ -238,6 +350,8 @@ export async function closeVisiblePosition(input) {
|
|
|
238
350
|
receipt: mutation.receipt,
|
|
239
351
|
resumed: true,
|
|
240
352
|
recovery: mutation.recovery,
|
|
353
|
+
...(partialAmount == null ? { venueOrderId: mutation.observation.venueOrderId,
|
|
354
|
+
filledSize: mutation.observation.filledSize, averagePrice: mutation.observation.averagePrice } : {}),
|
|
241
355
|
finalState: mutation.observation.finalState,
|
|
242
356
|
cleanup: mutation.observation.cleanup ?? null,
|
|
243
357
|
proofPath: 'trusted-pointer-visible-ui',
|
|
@@ -275,20 +389,30 @@ export async function closeVisiblePosition(input) {
|
|
|
275
389
|
const observedNotionalBaseUnits = sizeMatch && sizeMatch[2] === market && priceBaseUnits != null
|
|
276
390
|
? `${Math.round(Number(sizeMatch[1]) * Number(priceBaseUnits))}`
|
|
277
391
|
: null;
|
|
278
|
-
const closeBudget = trustedExternalMutationBudget(input, 'perps-close-position');
|
|
279
392
|
if (!/^[1-9][0-9]{0,29}$/u.test(String(observedNotionalBaseUnits ?? ''))
|
|
280
393
|
|| BigInt(observedNotionalBaseUnits) > BigInt(closeBudget.maxBaseUnits)) {
|
|
281
394
|
throw new Error('Visible Perps position notional is unavailable or exceeds the authorized close cap.');
|
|
282
395
|
}
|
|
283
|
-
|
|
284
|
-
await readPriorMutationReceipt(input, identity, 'metamask.perps.place_order');
|
|
396
|
+
const placement = cleanupOnly ? null
|
|
397
|
+
: await readPriorMutationReceipt(input, identity, 'metamask.perps.place_order', placementReceiptSelection(input));
|
|
398
|
+
let acceptedFill = null;
|
|
399
|
+
if (['limit', 'market'].includes(placement?.record?.identity?.orderType)) {
|
|
400
|
+
if (partialAmount != null) throw new Error('Accepted-fill cleanup requires a full owned-size close.');
|
|
401
|
+
acceptedFill = await readAcceptedFill(page, placement, market, fundingSide, exactBefore.size);
|
|
285
402
|
}
|
|
286
|
-
const balanceBeforeBaseUnits = fundingBalance.observedBaseUnits;
|
|
403
|
+
const balanceBeforeBaseUnits = String(placement?.record?.facts?.balanceBeforeBaseUnits ?? fundingBalance.observedBaseUnits);
|
|
287
404
|
if (!/^(?:0|[1-9][0-9]{0,29})$/u.test(balanceBeforeBaseUnits)) {
|
|
288
405
|
throw new Error('Prior order receipt has no valid visible pre-order balance.');
|
|
289
406
|
}
|
|
290
407
|
const confirmation = await prepareCloseModal(page, partialAmount);
|
|
291
|
-
|
|
408
|
+
if (partialAmount == null && (!confirmation.fullSizeSelected || !confirmation.marketOrder)) {
|
|
409
|
+
throw new Error('Receipt-bound full close requires the visible 100% market-close form.');
|
|
410
|
+
}
|
|
411
|
+
const closeSizing = partialAmount == null ? { size: sizeMatch[1], side: fundingSide === 'long' ? 'short' : 'long' } : null;
|
|
412
|
+
let submissionObserver = null;
|
|
413
|
+
let mutation;
|
|
414
|
+
try {
|
|
415
|
+
mutation = await runGuardedExternalMutation({
|
|
292
416
|
input,
|
|
293
417
|
identity,
|
|
294
418
|
publicationFacts: {
|
|
@@ -298,12 +422,13 @@ export async function closeVisiblePosition(input) {
|
|
|
298
422
|
cleanupOnly,
|
|
299
423
|
fundingBalance,
|
|
300
424
|
tradingContext,
|
|
425
|
+
...(acceptedFill ? { acceptedFill } : {}),
|
|
426
|
+
...(closeSizing ? { closeSizing } : {}),
|
|
301
427
|
},
|
|
302
428
|
dispatch: async () => {
|
|
303
429
|
let dispatchTarget;
|
|
304
430
|
try {
|
|
305
431
|
await requireCurrentVisibleFundingBalance(input, page, timeoutMs, fundingBalance.observedBaseUnits);
|
|
306
|
-
await requirePerpsTradingContext(input, page);
|
|
307
432
|
assertTrustedExternalMutationTarget(input, page, identity);
|
|
308
433
|
const current = await readVisibleClosePosition(page, market);
|
|
309
434
|
const currentPosition = parsePositionDetail({ pair: `${market}-USD`, ...current });
|
|
@@ -313,7 +438,20 @@ export async function closeVisiblePosition(input) {
|
|
|
313
438
|
}) || currentPosition.size !== exactBefore.size) {
|
|
314
439
|
throw new Error('Visible Perps position changed before close dispatch.');
|
|
315
440
|
}
|
|
316
|
-
await
|
|
441
|
+
if (acceptedFill && JSON.stringify(await readAcceptedFill(page, placement, market, fundingSide, currentPosition.size))
|
|
442
|
+
!== JSON.stringify(acceptedFill)) {
|
|
443
|
+
throw new Error('Accepted-order fill identity changed before close dispatch.');
|
|
444
|
+
}
|
|
445
|
+
const currentConfirmation = await prepareCloseModal(page, partialAmount);
|
|
446
|
+
if (closeSizing) {
|
|
447
|
+
if (!currentConfirmation.fullSizeSelected || !currentConfirmation.marketOrder) {
|
|
448
|
+
throw new Error('Full market-close selection changed before dispatch.');
|
|
449
|
+
}
|
|
450
|
+
submissionObserver = await observeHyperliquidSubmission(page, { ...tradingContext, market,
|
|
451
|
+
side: closeSizing.side, size: closeSizing.size, orderType: 'market', reduceOnly: true,
|
|
452
|
+
leverage, maximumNotionalBaseUnits: closeBudget.maxBaseUnits });
|
|
453
|
+
await submissionObserver.checkSizing();
|
|
454
|
+
}
|
|
317
455
|
dispatchTarget = await page.evaluate(`(() => {
|
|
318
456
|
const element = document.querySelector('[data-testid="perps-close-position-modal-submit"]');
|
|
319
457
|
if (!element || element.disabled || element.getAttribute('aria-disabled') === 'true') {
|
|
@@ -322,10 +460,12 @@ export async function closeVisiblePosition(input) {
|
|
|
322
460
|
const rect = element.getBoundingClientRect();
|
|
323
461
|
return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
|
|
324
462
|
})()`);
|
|
463
|
+
await requirePerpsTradingContext(input, page);
|
|
325
464
|
assertTrustedExternalMutationTarget(input, page, identity);
|
|
326
465
|
} catch (error) {
|
|
327
466
|
throw externalMutationNotDispatched(error instanceof Error ? error.message : String(error));
|
|
328
467
|
}
|
|
468
|
+
submissionObserver?.arm();
|
|
329
469
|
await page.session.call('Input.dispatchMouseEvent', {
|
|
330
470
|
type: 'mousePressed', x: dispatchTarget.x, y: dispatchTarget.y,
|
|
331
471
|
button: 'left', buttons: 1, clickCount: 1,
|
|
@@ -334,8 +474,10 @@ export async function closeVisiblePosition(input) {
|
|
|
334
474
|
type: 'mouseReleased', x: dispatchTarget.x, y: dispatchTarget.y,
|
|
335
475
|
button: 'left', buttons: 0, clickCount: 1,
|
|
336
476
|
});
|
|
477
|
+
return submissionObserver ? submissionObserver.accepted(timeoutMs) : undefined;
|
|
337
478
|
},
|
|
338
|
-
observe: async () => {
|
|
479
|
+
observe: async ({ receipt }) => {
|
|
480
|
+
const acceptedClose = closeSizing ? requireCloseAcceptance(receipt, closeBudget.maxBaseUnits) : null;
|
|
339
481
|
const closeDeadline = Date.now() + 45_000;
|
|
340
482
|
let finalState;
|
|
341
483
|
while (Date.now() <= closeDeadline) {
|
|
@@ -364,6 +506,7 @@ export async function closeVisiblePosition(input) {
|
|
|
364
506
|
);
|
|
365
507
|
return {
|
|
366
508
|
success: true,
|
|
509
|
+
...acceptedClose,
|
|
367
510
|
finalState,
|
|
368
511
|
cleanup,
|
|
369
512
|
};
|
|
@@ -373,7 +516,10 @@ export async function closeVisiblePosition(input) {
|
|
|
373
516
|
}
|
|
374
517
|
throw new Error('matching closed-position postcondition was not visible before timeout');
|
|
375
518
|
},
|
|
376
|
-
|
|
519
|
+
});
|
|
520
|
+
} finally {
|
|
521
|
+
submissionObserver?.close();
|
|
522
|
+
}
|
|
377
523
|
return {
|
|
378
524
|
action: input.action,
|
|
379
525
|
market,
|
|
@@ -387,6 +533,9 @@ export async function closeVisiblePosition(input) {
|
|
|
387
533
|
receipt: mutation.receipt,
|
|
388
534
|
resumed: mutation.resumed,
|
|
389
535
|
recovery: mutation.recovery,
|
|
536
|
+
...(closeSizing ? { venueOrderId: mutation.observation.venueOrderId,
|
|
537
|
+
filledSize: mutation.observation.filledSize, averagePrice: mutation.observation.averagePrice,
|
|
538
|
+
closeSizing: submissionObserver?.sizing } : {}),
|
|
390
539
|
proofPath: 'trusted-pointer-visible-ui',
|
|
391
540
|
};
|
|
392
541
|
});
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Fetch production Hyperliquid data and compare it with the opened market's displayed values.
|
|
2
|
+
// Also check the funding countdown and nonblank chart canvases.
|
|
1
3
|
import { dataTestId, marketSymbol, runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
4
|
|
|
3
5
|
function numeric(value) {
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Submit live margin changes through visible controls with mutation receipts and position identity checks.
|
|
2
|
+
// Observe the resulting margin and balance; removal requires a prior successful add receipt.
|
|
1
3
|
import { dataTestId, runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
4
|
import {
|
|
3
5
|
assertTrustedExternalMutationTarget,
|
|
@@ -266,7 +268,6 @@ export async function editMargin(input) {
|
|
|
266
268
|
let dispatchTarget;
|
|
267
269
|
try {
|
|
268
270
|
await requireCurrentVisibleFundingBalance(input, page, timeoutMs, fundingBalance.observedBaseUnits);
|
|
269
|
-
await requirePerpsTradingContext(input, page);
|
|
270
271
|
assertTrustedExternalMutationTarget(input, page, identity);
|
|
271
272
|
const current = await readPosition(page, market);
|
|
272
273
|
if (current.marginBaseUnits !== before.marginBaseUnits || current.size !== before.size
|
|
@@ -284,6 +285,7 @@ export async function editMargin(input) {
|
|
|
284
285
|
const rect = element.getBoundingClientRect();
|
|
285
286
|
return { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
|
|
286
287
|
})()`);
|
|
288
|
+
await requirePerpsTradingContext(input, page);
|
|
287
289
|
assertTrustedExternalMutationTarget(input, page, identity);
|
|
288
290
|
} catch (error) {
|
|
289
291
|
throw externalMutationNotDispatched(error instanceof Error ? error.message : String(error));
|