@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,12 +1,11 @@
|
|
|
1
|
+
// Converge selected testnet orders to open or absent state, then assert the result.
|
|
2
|
+
// Reuse an existing matching order instead of placing another.
|
|
3
|
+
|
|
1
4
|
import { getCoreController, isDirectRun, requireExplicitSelection, runAdapter, selectedItems } from './_controller.mjs';
|
|
2
5
|
import { assertOrders } from './assert_orders.mjs';
|
|
3
6
|
import { closeOrders } from './close_orders.mjs';
|
|
4
7
|
import { placeOrder } from './place_order.mjs';
|
|
5
8
|
|
|
6
|
-
// core Higher-level wrapper that reads selected open orders, converges them to the
|
|
7
|
-
// requested `state` (open/none) by canceling or placing, then asserts the final
|
|
8
|
-
// order state. Mirrors ensure_positions.mjs.
|
|
9
|
-
|
|
10
9
|
export async function ensureOrders(input) {
|
|
11
10
|
if (input.node?.state == null) throw new Error('metamask.perps.ensure_orders requires state=open or state=none.');
|
|
12
11
|
requireExplicitSelection(input);
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
+
// Converge selected testnet positions to open or absent state, then assert the result.
|
|
2
|
+
// Open a position only when the selected state requires one.
|
|
3
|
+
|
|
1
4
|
import { getCoreController, isDirectRun, requireExplicitSelection, runAdapter, selectedItems } from './_controller.mjs';
|
|
2
5
|
import { assertPositions } from './assert_positions.mjs';
|
|
3
6
|
import { closePositions } from './close_positions.mjs';
|
|
4
7
|
import { placeOrder } from './place_order.mjs';
|
|
5
8
|
|
|
6
|
-
// core Higher-level wrapper that reads selected positions, converges them to the
|
|
7
|
-
// requested `state` (open/none) by placing or closing, then asserts the final
|
|
8
|
-
// state. Mirrors the extension ensure_positions semantics.
|
|
9
|
-
|
|
10
9
|
export async function ensurePositions(input) {
|
|
11
10
|
if (input.node?.state == null) throw new Error('metamask.perps.ensure_positions requires state=open or state=none.');
|
|
12
11
|
requireExplicitSelection(input);
|
|
@@ -26,7 +25,7 @@ export async function ensurePositions(input) {
|
|
|
26
25
|
userAddress: accountAddress,
|
|
27
26
|
});
|
|
28
27
|
let order = null;
|
|
29
|
-
if (selectedItems(input, positions).length === 0) {
|
|
28
|
+
if (selectedItems(input, positions, 'position').length === 0) {
|
|
30
29
|
order = await placeOrder(input);
|
|
31
30
|
}
|
|
32
31
|
const assertion = await assertPositions(input, true);
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// Submit and verify testnet market, limit or trigger orders through the headless controller.
|
|
2
|
+
// Supports declared rejection cases and attached protection parameters.
|
|
3
|
+
|
|
1
4
|
import {
|
|
2
5
|
configuredSymbols,
|
|
3
6
|
controllerRejection,
|
|
@@ -12,24 +15,14 @@ import {
|
|
|
12
15
|
selectedItems,
|
|
13
16
|
} from './_controller.mjs';
|
|
14
17
|
|
|
15
|
-
// core Place a real Perps order on HyperLiquid testnet by driving the headless
|
|
16
|
-
// PerpsController.placeOrder() through the full signing/provider path (Slice 2).
|
|
17
|
-
// Mirrors the extension adapter's input vocabulary and notional→size math:
|
|
18
|
-
// size = (usdAmount * leverage) / currentPrice
|
|
19
|
-
// The HyperLiquidProvider recalculates size from usdAmount internally; we still
|
|
20
|
-
// pass a derived size for the controller's own validation, plus currentPrice/
|
|
21
|
-
// priceAtCalculation so the provider's slippage guard has a fresh anchor.
|
|
22
|
-
//
|
|
23
18
|
// LIMIT orders (orderType=limit) place a RESTING order on the book instead of
|
|
24
19
|
// filling: the provider sends tif=Gtc and uses the provided `price` verbatim
|
|
25
20
|
// (calculateOrderPriceAndSize / buildOrdersArray in core's orderCalculations.ts).
|
|
26
21
|
// The limit price is derived from the live mid by `offset_pct` (e.g. -30 places a
|
|
27
|
-
// BUY 30% below mid
|
|
22
|
+
// BUY 30% below mid), or supplied absolutely via `limit_price`.
|
|
28
23
|
// For a limit order we size from the LIMIT price (not mid) and omit usdAmount so
|
|
29
|
-
// the controller
|
|
30
|
-
//
|
|
31
|
-
// though the limit sits far from mid. After placing we verify the resting open
|
|
32
|
-
// order exists (not a filled position).
|
|
24
|
+
// the controller receives an explicit size; venue quantity normalization still
|
|
25
|
+
// applies. After placing we verify the resting open order exists, not a filled position.
|
|
33
26
|
//
|
|
34
27
|
// TRIGGER placements (order_type=stop_market | stop_limit | take_profit_market |
|
|
35
28
|
// take_profit_limit) rest off-book until `trigger_price` is reached, then execute
|
|
@@ -567,7 +560,7 @@ export async function placeOrder(input) {
|
|
|
567
560
|
userAddress: accountAddress,
|
|
568
561
|
});
|
|
569
562
|
}
|
|
570
|
-
const matchingPositions = selectedItems(input, positions);
|
|
563
|
+
const matchingPositions = selectedItems(input, positions, 'position');
|
|
571
564
|
|
|
572
565
|
if (restsOnBook && matchingOrders.length === 0) {
|
|
573
566
|
throw new Error(
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Read standalone Perps positions and return redacted selected data with total/matching counts.
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
getCoreController,
|
|
3
5
|
isDirectRun,
|
|
@@ -12,7 +14,7 @@ export async function readPositions(input) {
|
|
|
12
14
|
standalone: true,
|
|
13
15
|
userAddress: accountAddress,
|
|
14
16
|
});
|
|
15
|
-
const matching = selectedItems(input, positions);
|
|
17
|
+
const matching = selectedItems(input, positions, 'position');
|
|
16
18
|
return {
|
|
17
19
|
action: input.action,
|
|
18
20
|
source: 'perps-controller-standalone',
|
|
@@ -1,21 +1,10 @@
|
|
|
1
|
+
// Prepare selected testnet baselines before proof, handling positions before orders.
|
|
2
|
+
// Omitted domains are skipped; this headless action performs no UI navigation.
|
|
3
|
+
|
|
1
4
|
import { isDirectRun, resolveNetwork, runAdapter } from './_controller.mjs';
|
|
2
5
|
import { ensureOrders } from './ensure_orders.mjs';
|
|
3
6
|
import { ensurePositions } from './ensure_positions.mjs';
|
|
4
7
|
|
|
5
|
-
// core Headless composite that converges the POSITION and ORDER baselines for the
|
|
6
|
-
// selected market(s) on HyperLiquid testnet before the proof window. Mirrors the
|
|
7
|
-
// extension perps-domain-dispatcher start_state semantics but SKIPS every
|
|
8
|
-
// navigation/page/HUD step — headless core has no UI surface.
|
|
9
|
-
//
|
|
10
|
-
// Params (extension schema, testnet-default): profile, market(s)/symbol(s),
|
|
11
|
-
// positions{state,...}, orders{state,...}, network. The UI-only `page`/`hud`
|
|
12
|
-
// fields are accepted by the schema but intentionally ignored here.
|
|
13
|
-
//
|
|
14
|
-
// A baseline block converges to its `state` by composing the ensure_* helpers; a
|
|
15
|
-
// block set to `false` (or omitted) skips that domain. Position convergence runs
|
|
16
|
-
// first (closing a position also clears its TP/SL orders), then order
|
|
17
|
-
// convergence, so the final order baseline is authoritative.
|
|
18
|
-
|
|
19
8
|
const BASELINE_KEYS = [
|
|
20
9
|
'market',
|
|
21
10
|
'symbol',
|
|
@@ -1,21 +1,10 @@
|
|
|
1
|
+
// Restore selected testnet baselines, canceling orders before closing positions.
|
|
2
|
+
// Omitted domains default to empty; explicit false skips a domain.
|
|
3
|
+
|
|
1
4
|
import { isDirectRun, resolveNetwork, runAdapter } from './_controller.mjs';
|
|
2
5
|
import { ensureOrders } from './ensure_orders.mjs';
|
|
3
6
|
import { ensurePositions } from './ensure_positions.mjs';
|
|
4
7
|
|
|
5
|
-
// core Headless composite that restores the POSITION and ORDER baselines for the
|
|
6
|
-
// selected market(s) on HyperLiquid testnet after the proof window. Mirrors the
|
|
7
|
-
// extension perps-domain-dispatcher teardown_state semantics but SKIPS every
|
|
8
|
-
// navigation/page/HUD step — headless core has no UI surface.
|
|
9
|
-
//
|
|
10
|
-
// Params (extension schema, testnet-default): profile, market(s)/symbol(s),
|
|
11
|
-
// positions{state,...}, orders{state,...}, network. The UI-only `page`/`hud`
|
|
12
|
-
// fields are accepted by the schema but intentionally ignored here.
|
|
13
|
-
//
|
|
14
|
-
// Teardown defaults BOTH domains to state=none (leave the shared dev account
|
|
15
|
-
// FLAT) when a baseline block is omitted; pass a block explicitly to override, or
|
|
16
|
-
// `false` to skip a domain. Orders are canceled FIRST, then positions are closed,
|
|
17
|
-
// so resting orders never re-arm against a position mid-teardown.
|
|
18
|
-
|
|
19
8
|
const BASELINE_KEYS = [
|
|
20
9
|
'market',
|
|
21
10
|
'symbol',
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// Manage full or partial TP/SL for a selected testnet position and verify the resulting triggers.
|
|
2
|
+
// Fractional sizes use live venue quantity precision.
|
|
3
|
+
|
|
1
4
|
import {
|
|
2
5
|
configuredSymbols,
|
|
3
6
|
controllerRejection,
|
|
@@ -10,14 +13,6 @@ import {
|
|
|
10
13
|
selectedItems,
|
|
11
14
|
} from './_controller.mjs';
|
|
12
15
|
|
|
13
|
-
// core Set or replace the TP/SL attached to an existing Perps POSITION by driving
|
|
14
|
-
// the headless PerpsController.updatePositionTPSL() through the full
|
|
15
|
-
// signing/provider path. This is the position-bound half of the advanced-order
|
|
16
|
-
// contract (TAT-3511): it covers both whole-position TP/SL and PARTIAL
|
|
17
|
-
// (quantity-scoped) TP/SL, which the provider must express as standalone
|
|
18
|
-
// reduce-only trigger orders because a position-bound TP/SL always closes the
|
|
19
|
-
// whole position.
|
|
20
|
-
//
|
|
21
16
|
// Params use the controller's own provider-agnostic names: take_profit_price /
|
|
22
17
|
// take_profit_size / stop_loss_price / stop_loss_size (camelCase aliases
|
|
23
18
|
// accepted). Omitting a price removes that side; omitting a size covers the
|
|
@@ -129,9 +124,6 @@ export async function updatePositionTpsl(input) {
|
|
|
129
124
|
// Prices and sizes are resolved from live mid and the actual position, so a
|
|
130
125
|
// recipe never has to name a number that only holds for one market at one
|
|
131
126
|
// moment. An absolute value still wins when the caller gives one.
|
|
132
|
-
// Both derivations below need the market, and each used to fetch it
|
|
133
|
-
// separately. getMarketDataWithPrices reads every market, so doing it twice in
|
|
134
|
-
// one adapter is what pushed this past the node timeout.
|
|
135
127
|
let market;
|
|
136
128
|
const readMarket = async () => {
|
|
137
129
|
if (market === undefined) {
|
|
@@ -181,11 +173,16 @@ export async function updatePositionTpsl(input) {
|
|
|
181
173
|
|
|
182
174
|
if (takeProfitFraction !== undefined || stopLossFraction !== undefined) {
|
|
183
175
|
const absolutePositionSize = Math.abs(Number(position.size ?? position.szi ?? 0));
|
|
184
|
-
//
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
176
|
+
// MarketInfo carries quantity precision; formatted market prices do not.
|
|
177
|
+
const markets = await controller.getMarkets({ symbols: [symbol] });
|
|
178
|
+
const szDecimals = markets.find(
|
|
179
|
+
(item) => item.name.toUpperCase() === symbol.toUpperCase(),
|
|
180
|
+
)?.szDecimals;
|
|
181
|
+
if (!Number.isInteger(szDecimals) || szDecimals < 0) {
|
|
182
|
+
throw new Error(
|
|
183
|
+
`core updatePositionTPSL found no valid size precision for ${symbol}.\nNext: run mm-harness verify --adapter core`,
|
|
184
|
+
);
|
|
185
|
+
}
|
|
189
186
|
const fromFraction = (raw, name) => {
|
|
190
187
|
const fraction = Number(raw);
|
|
191
188
|
if (!Number.isFinite(fraction) || fraction <= 0 || fraction > 1) {
|
|
@@ -194,9 +191,6 @@ export async function updatePositionTpsl(input) {
|
|
|
194
191
|
);
|
|
195
192
|
}
|
|
196
193
|
const exact = absolutePositionSize * fraction;
|
|
197
|
-
if (!Number.isFinite(szDecimals)) {
|
|
198
|
-
return String(exact);
|
|
199
|
-
}
|
|
200
194
|
const rounded = Number(exact.toFixed(szDecimals));
|
|
201
195
|
if (rounded <= 0) {
|
|
202
196
|
throw new Error(
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// Derive public account identities from the checkout's wallet fixture and apply scope selection.
|
|
2
|
+
// Credential values are not included in the returned account list.
|
|
3
|
+
|
|
1
4
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
2
5
|
import { pathToFileURL } from 'node:url';
|
|
3
6
|
import { mnemonicToAccount, privateKeyToAccount } from 'viem/accounts';
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
// Sets MetaMetrics and marketing consent through background controllers and checks settled state.
|
|
2
|
+
// Reloads with a temporary Chrome bridge when the debug request bridge is unavailable.
|
|
1
3
|
import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
4
|
import { consentParams } from '../../shared/analytics/consent.mjs';
|
|
3
5
|
import {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Opens a Home token to verify its ERC-20 identity from the details route, then returns Home.
|
|
2
|
+
import { openHome } from '../ui/navigate.mjs';
|
|
3
|
+
|
|
4
|
+
export async function verifyHomeIdentity(page, token, assetId, timeoutMs) {
|
|
5
|
+
try {
|
|
6
|
+
await page.clickPoint(token.point.x, token.point.y);
|
|
7
|
+
const deadline = Date.now() + timeoutMs;
|
|
8
|
+
let route;
|
|
9
|
+
while (Date.now() < deadline) {
|
|
10
|
+
const value = String(await page.evaluate('location.hash'));
|
|
11
|
+
if (value.startsWith('#/asset/')) {
|
|
12
|
+
route = value;
|
|
13
|
+
break;
|
|
14
|
+
}
|
|
15
|
+
await page.evaluate('new Promise((resolve) => setTimeout(resolve, 150))');
|
|
16
|
+
}
|
|
17
|
+
if (!route) throw new Error('The selected Home token did not expose an asset-details route.');
|
|
18
|
+
const [, , chainId, address] = route.split('/');
|
|
19
|
+
if (!/^0x[0-9a-f]+$/iu.test(chainId ?? '') || !/^0x[0-9a-f]{40}$/iu.test(address ?? '')) {
|
|
20
|
+
throw new Error('The selected Home route does not expose an ERC-20 identity.');
|
|
21
|
+
}
|
|
22
|
+
const observed = `eip155:${BigInt(chainId).toString()}/erc20:${address.toLowerCase()}`;
|
|
23
|
+
if (observed !== assetId) {
|
|
24
|
+
throw new Error(`Home asset identity ${observed} does not match ${assetId}.\nNext: select a network filter with an unambiguous matching token before continuing.`);
|
|
25
|
+
}
|
|
26
|
+
return { assetId, route, verified: true };
|
|
27
|
+
} finally {
|
|
28
|
+
await openHome(page, timeoutMs);
|
|
29
|
+
delete token.point;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -1,10 +1,15 @@
|
|
|
1
|
+
// Imports an ERC-20 through token management and checks its Home row.
|
|
2
|
+
// Reusing a visible token requires a matching details-route identity and an unchanged account.
|
|
1
3
|
import { dataTestId, runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
4
|
+
import { readWalletStateExpression } from '../wallet/state.mjs';
|
|
5
|
+
import { verifyHomeIdentity } from './home-token-identity.mjs';
|
|
2
6
|
|
|
3
7
|
const OPTIONS = dataTestId('asset-list-control-bar-action-button');
|
|
4
8
|
const MANAGE = dataTestId('manageTokens__button');
|
|
5
9
|
const ADD = dataTestId('token-management-add-custom-token-button');
|
|
6
10
|
const PAGE = dataTestId('custom-token-import-page');
|
|
7
11
|
const NETWORK = dataTestId('network-selector');
|
|
12
|
+
const NETWORK_PICKER = dataTestId('custom-token-import-network-selector');
|
|
8
13
|
const ADDRESS = dataTestId('custom-token-import-address-input');
|
|
9
14
|
const SYMBOL = dataTestId('custom-token-import-symbol-input');
|
|
10
15
|
const DECIMALS = dataTestId('custom-token-import-decimal-input');
|
|
@@ -33,96 +38,69 @@ async function waitFor(page, predicate, message, timeoutMs) {
|
|
|
33
38
|
throw new Error(message);
|
|
34
39
|
}
|
|
35
40
|
|
|
36
|
-
async function
|
|
37
|
-
const marker = `mm-harness-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
38
|
-
const found = await page.evaluate(`(() => {
|
|
39
|
-
const label = ${JSON.stringify(label)};
|
|
40
|
-
const marker = ${JSON.stringify(marker)};
|
|
41
|
-
for (const element of document.querySelectorAll('button')) {
|
|
42
|
-
const text = String(element.innerText || element.textContent || '').replace(/\\s+/gu, ' ').trim();
|
|
43
|
-
const style = getComputedStyle(element);
|
|
44
|
-
const rect = element.getBoundingClientRect();
|
|
45
|
-
if (text === label && style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0) {
|
|
46
|
-
element.setAttribute('data-mm-harness-target', marker);
|
|
47
|
-
return true;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
return false;
|
|
51
|
-
})()`);
|
|
52
|
-
if (!found) throw new Error(`Visible button ${JSON.stringify(label)} was not found.`);
|
|
53
|
-
return `[data-mm-harness-target="${marker}"]`;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
async function networkOption(page, network, chainId) {
|
|
41
|
+
async function networkOption(page, chainId) {
|
|
57
42
|
return page.evaluate(`(() => {
|
|
58
|
-
const
|
|
43
|
+
const picker = document.querySelector(${JSON.stringify(NETWORK_PICKER)});
|
|
44
|
+
if (!picker) return null;
|
|
59
45
|
const chainIds = new Set(${JSON.stringify([
|
|
60
46
|
String(chainId),
|
|
61
47
|
`0x${chainId.toString(16)}`,
|
|
62
48
|
`eip155:${chainId}`,
|
|
63
49
|
].map((value) => value.toLowerCase()))});
|
|
64
|
-
for (const element of
|
|
50
|
+
for (const element of picker.querySelectorAll('[data-testid^="network-list-item-"]')) {
|
|
65
51
|
const testId = String(element.getAttribute('data-testid') || '');
|
|
66
52
|
const suffix = testId.slice('network-list-item-'.length).toLowerCase();
|
|
53
|
+
if (!chainIds.has(suffix)) continue;
|
|
54
|
+
element.scrollIntoView({ block: 'nearest', inline: 'nearest' });
|
|
55
|
+
const rect = element.getBoundingClientRect();
|
|
56
|
+
const hit = document.elementFromPoint(rect.left + rect.width / 2, rect.top + rect.height / 2);
|
|
57
|
+
if (!element.checkVisibility({ checkOpacity: true, checkVisibilityCSS: true })
|
|
58
|
+
|| rect.width <= 0 || rect.height <= 0 || !hit || !element.contains(hit)) continue;
|
|
67
59
|
const label = String(element.innerText || '').replace(/\\s+/gu, ' ').trim().slice(0, 500);
|
|
68
|
-
|
|
60
|
+
return { testId, label };
|
|
69
61
|
}
|
|
70
62
|
return null;
|
|
71
63
|
})()`);
|
|
72
64
|
}
|
|
73
65
|
|
|
74
|
-
async function homeToken(page, symbol) {
|
|
66
|
+
async function homeToken(page, symbol, verifyIdentity = false) {
|
|
75
67
|
return page.evaluate(`(() => {
|
|
76
68
|
const symbol = ${JSON.stringify(symbol.toLowerCase())};
|
|
69
|
+
const verifyIdentity = ${JSON.stringify(verifyIdentity)};
|
|
70
|
+
const matches = [];
|
|
77
71
|
for (const row of document.querySelectorAll('[data-testid="multichain-token-list-button"]')) {
|
|
78
72
|
const name = String(row.querySelector('[data-testid="multichain-token-list-item-token-name"]')?.innerText || '').replace(/\\s+/gu, ' ').trim();
|
|
79
73
|
const amount = String(row.querySelector('[data-testid="multichain-token-list-item-value"]')?.innerText || '').replace(/\\s+/gu, ' ').trim();
|
|
80
74
|
const observed = amount.match(/\\b([A-Za-z][A-Za-z0-9]{1,11})$/u)?.[1] || '';
|
|
81
|
-
|
|
75
|
+
const style = getComputedStyle(row);
|
|
76
|
+
const rect = row.getBoundingClientRect();
|
|
77
|
+
if (observed.toLowerCase() !== symbol || style.display === 'none' || style.visibility === 'hidden'
|
|
78
|
+
|| rect.width <= 0 || rect.height <= 0) continue;
|
|
79
|
+
matches.push({ row, name, symbol: observed.toUpperCase(), amount });
|
|
82
80
|
}
|
|
83
|
-
|
|
81
|
+
if (matches.length > 1) throw new Error('Visible token symbol matches multiple rows; identity is ambiguous.\\nNext: narrow the product network filter before importing.');
|
|
82
|
+
if (!matches.length) return null;
|
|
83
|
+
const { row, ...token } = matches[0];
|
|
84
|
+
if (verifyIdentity) {
|
|
85
|
+
row.scrollIntoView({ block: 'center', inline: 'nearest' });
|
|
86
|
+
const rect = row.getBoundingClientRect();
|
|
87
|
+
const x = rect.left + rect.width / 2;
|
|
88
|
+
const y = rect.top + rect.height / 2;
|
|
89
|
+
const hit = document.elementFromPoint(x, y);
|
|
90
|
+
if (!hit || !row.contains(hit)) throw new Error('The matching Home token is covered; identity cannot be verified.\\nNext: uncover the token row before importing.');
|
|
91
|
+
token.point = { x, y };
|
|
92
|
+
}
|
|
93
|
+
return token;
|
|
84
94
|
})()`);
|
|
85
95
|
}
|
|
86
96
|
|
|
87
|
-
async function
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if (!allTokens || typeof allTokens !== 'object' || !selectedAddress) {
|
|
95
|
-
return { available: false, token: null };
|
|
96
|
-
}
|
|
97
|
-
const expectedChainId = ${JSON.stringify(chainId)};
|
|
98
|
-
const chainEntry = Object.entries(allTokens).find(([key]) => {
|
|
99
|
-
const normalized = String(key).toLowerCase();
|
|
100
|
-
const value = normalized.startsWith('eip155:')
|
|
101
|
-
? Number(normalized.slice('eip155:'.length))
|
|
102
|
-
: normalized.startsWith('0x')
|
|
103
|
-
? Number.parseInt(normalized.slice(2), 16)
|
|
104
|
-
: Number(normalized);
|
|
105
|
-
return value === expectedChainId;
|
|
106
|
-
});
|
|
107
|
-
const accountEntry = chainEntry && Object.entries(chainEntry[1] || {})
|
|
108
|
-
.find(([account]) => String(account).toLowerCase() === selectedAddress);
|
|
109
|
-
const tokens = Array.isArray(accountEntry?.[1]) ? accountEntry[1] : [];
|
|
110
|
-
const expectedAddress = ${JSON.stringify(address.toLowerCase())};
|
|
111
|
-
const expectedSymbol = ${JSON.stringify(expectedSymbol)};
|
|
112
|
-
const token = tokens.find((entry) => {
|
|
113
|
-
const tokenAddress = String(entry?.address || entry?.contractAddress || '').toLowerCase();
|
|
114
|
-
return tokenAddress === expectedAddress && String(entry?.symbol || '').toUpperCase() === expectedSymbol;
|
|
115
|
-
});
|
|
116
|
-
return {
|
|
117
|
-
available: true,
|
|
118
|
-
token: token ? {
|
|
119
|
-
address: String(token.address || token.contractAddress),
|
|
120
|
-
symbol: String(token.symbol),
|
|
121
|
-
decimals: token.decimals == null ? null : String(token.decimals),
|
|
122
|
-
chainId: chainEntry?.[0] || null,
|
|
123
|
-
} : null,
|
|
124
|
-
};
|
|
125
|
-
})()`, { awaitPromise: true });
|
|
97
|
+
async function selectedEvmAccount(page) {
|
|
98
|
+
const { selectedAccount } = await page.evaluate(readWalletStateExpression(), { awaitPromise: true });
|
|
99
|
+
if (!selectedAccount?.id || !['eip155:eoa', 'eip155:erc4337'].includes(selectedAccount.type)
|
|
100
|
+
|| !/^0x[0-9a-f]{40}$/iu.test(selectedAccount.address ?? '')) {
|
|
101
|
+
throw new Error('The selected EVM account is unavailable; existing token identity cannot be verified.\nNext: select the intended EVM account before importing.');
|
|
102
|
+
}
|
|
103
|
+
return { id: selectedAccount.id, address: selectedAccount.address.toLowerCase(), type: selectedAccount.type };
|
|
126
104
|
}
|
|
127
105
|
|
|
128
106
|
runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
@@ -136,39 +114,44 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
136
114
|
throw new Error('metamask.assets.import_custom_token requires network, positive chain_id, ERC-20 address, and expected_symbol.');
|
|
137
115
|
}
|
|
138
116
|
|
|
139
|
-
const
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
117
|
+
const account = await selectedEvmAccount(page);
|
|
118
|
+
const existingToken = await homeToken(page, expectedSymbol, true);
|
|
119
|
+
if (existingToken) {
|
|
120
|
+
const existingIdentity = await verifyHomeIdentity(
|
|
121
|
+
page, existingToken, `eip155:${chainId}/erc20:${address.toLowerCase()}`, timeoutMs,
|
|
122
|
+
);
|
|
123
|
+
const afterAccount = await selectedEvmAccount(page);
|
|
124
|
+
if (JSON.stringify(afterAccount) !== JSON.stringify(account)) {
|
|
125
|
+
throw new Error('The selected EVM account changed while checking the existing token.\nNext: keep the intended account selected for the full import check.');
|
|
126
|
+
}
|
|
144
127
|
return {
|
|
145
128
|
action: input.action,
|
|
146
129
|
network,
|
|
147
130
|
chainId,
|
|
148
131
|
address,
|
|
149
132
|
alreadyImported: true,
|
|
133
|
+
changed: false,
|
|
150
134
|
token: existingToken,
|
|
151
|
-
identity: existingIdentity.
|
|
135
|
+
identity: { ...existingIdentity, account, address, symbol: expectedSymbol, decimals: null, chainId: `0x${chainId.toString(16)}` },
|
|
152
136
|
proofPath: 'trusted-pointer-visible-ui',
|
|
153
137
|
};
|
|
154
138
|
}
|
|
155
139
|
|
|
156
140
|
if (!(await visible(page, MANAGE))) await page.click(OPTIONS);
|
|
157
141
|
await waitFor(page, () => visible(page, MANAGE), 'Manage tokens did not become visible.', timeoutMs);
|
|
158
|
-
await page.click(
|
|
142
|
+
await page.click(MANAGE);
|
|
159
143
|
await waitFor(page, () => visible(page, ADD), 'Add a custom token did not become visible.', timeoutMs);
|
|
160
|
-
await page.click(
|
|
144
|
+
await page.click(ADD);
|
|
161
145
|
await waitFor(page, () => visible(page, PAGE), 'Custom token import did not open.', timeoutMs);
|
|
162
146
|
await page.click(NETWORK);
|
|
163
147
|
const selectedNetwork = await waitFor(
|
|
164
148
|
page,
|
|
165
|
-
() => networkOption(page,
|
|
149
|
+
() => networkOption(page, chainId),
|
|
166
150
|
`${network} (${chainId}) was not available in the custom token network selector.`,
|
|
167
151
|
timeoutMs,
|
|
168
152
|
);
|
|
169
|
-
await page.click(dataTestId(selectedNetwork.testId));
|
|
170
|
-
await waitFor(page, () => visible(page, ADDRESS), 'Custom token
|
|
171
|
-
await page.evaluate('new Promise((resolve) => setTimeout(resolve, 2000))');
|
|
153
|
+
await page.click(`${NETWORK_PICKER} ${dataTestId(selectedNetwork.testId)}`);
|
|
154
|
+
await waitFor(page, async () => !(await visible(page, NETWORK_PICKER)) && await visible(page, ADDRESS), 'Custom token network selection did not return to the address form.', timeoutMs);
|
|
172
155
|
await page.setInput(ADDRESS, address);
|
|
173
156
|
|
|
174
157
|
let metadataSource = 'network';
|
|
@@ -183,7 +166,7 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
183
166
|
return state.symbol && state.enabled ? state : null;
|
|
184
167
|
}, 'Custom token metadata did not resolve to an enabled import.', Math.min(timeoutMs, 15_000));
|
|
185
168
|
} catch (error) {
|
|
186
|
-
if (expectedDecimals === null) throw error;
|
|
169
|
+
if (expectedDecimals === null || !(await visible(page, SYMBOL)) || !(await visible(page, DECIMALS))) throw error;
|
|
187
170
|
metadataSource = 'manual';
|
|
188
171
|
await page.setInput(SYMBOL, expectedSymbol);
|
|
189
172
|
await page.setInput(DECIMALS, expectedDecimals);
|
|
@@ -216,6 +199,7 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
216
199
|
|
|
217
200
|
return {
|
|
218
201
|
action: input.action,
|
|
202
|
+
changed: true,
|
|
219
203
|
network: selectedNetwork.label || network,
|
|
220
204
|
chainId,
|
|
221
205
|
address,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
// Opens asset details from a uniquely visible token symbol and waits for the asset route.
|
|
1
2
|
import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
3
|
|
|
3
4
|
runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
@@ -6,19 +7,30 @@ runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
|
6
7
|
if (!/^[A-Z0-9._-]{2,20}$/u.test(symbol)) {
|
|
7
8
|
throw new Error('metamask.assets.open_details requires a valid token symbol.');
|
|
8
9
|
}
|
|
9
|
-
const
|
|
10
|
+
const match = await page.evaluate(`(() => {
|
|
10
11
|
const symbol = ${JSON.stringify(symbol)};
|
|
11
|
-
|
|
12
|
+
const selectors = [];
|
|
13
|
+
for (const row of document.querySelectorAll('[data-testid="multichain-token-list-button"]')) {
|
|
14
|
+
const style = getComputedStyle(row);
|
|
15
|
+
const rect = row.getBoundingClientRect();
|
|
16
|
+
if (style.display === 'none' || style.visibility === 'hidden' || rect.width <= 0 || rect.height <= 0
|
|
17
|
+
|| rect.bottom <= 0 || rect.right <= 0 || rect.top >= innerHeight || rect.left >= innerWidth) continue;
|
|
12
18
|
const amount = String(row.querySelector('[data-testid="multichain-token-list-item-value"]')?.innerText || '').trim();
|
|
13
19
|
const observed = amount.match(/\\b([A-Za-z][A-Za-z0-9._-]{1,19})$/u)?.[1]?.toUpperCase();
|
|
14
20
|
if (observed !== symbol) continue;
|
|
15
|
-
|
|
16
|
-
|
|
21
|
+
const parts = [];
|
|
22
|
+
for (let element = row; element; element = element.parentElement) {
|
|
23
|
+
const index = element.parentElement
|
|
24
|
+
? [...element.parentElement.children].indexOf(element) + 1 : 1;
|
|
25
|
+
parts.unshift(element.localName + ':nth-child(' + index + ')');
|
|
26
|
+
}
|
|
27
|
+
selectors.push(parts.join(' > '));
|
|
17
28
|
}
|
|
18
|
-
return null;
|
|
29
|
+
return { count: selectors.length, selector: selectors.length === 1 ? selectors[0] : null };
|
|
19
30
|
})()`);
|
|
20
|
-
if (!
|
|
21
|
-
|
|
31
|
+
if (!match.count) throw new Error(`Visible asset ${symbol} was not found.`);
|
|
32
|
+
if (match.count !== 1) throw new Error(`Visible asset ${symbol} matches ${match.count} rows; selection is ambiguous.\nNext: use a uniquely visible token symbol or narrow the product network filter before retrying.`);
|
|
33
|
+
await page.click(match.selector);
|
|
22
34
|
const deadline = Date.now() + timeoutMs;
|
|
23
35
|
while (Date.now() <= deadline) {
|
|
24
36
|
if (String(await page.evaluate('location.hash')).startsWith('#/asset/')) {
|