@deeeed/metamask-harness 0.36.0 → 0.38.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 +51 -0
- package/adapters/extension/artifact-runtime-state.cjs +138 -0
- package/adapters/extension/inject.mjs +2 -0
- package/adapters/extension/launch-browser.cjs +24 -0
- package/adapters/extension/live.sh +82 -12
- package/adapters/extension/readiness.mjs +77 -36
- package/adapters/extension/snapshot-dist.sh +88 -3
- package/adapters/extension/verify.sh +6 -2
- package/adapters/manifest.json +17 -1
- package/adapters/shared/log-tui.mjs +1 -1
- package/dist/adapters/extension/artifact-integrity.js +38 -0
- package/dist/adapters/extension/extension-id.js +23 -4
- package/dist/adapters/extension/release-artifact.js +386 -0
- package/dist/adapters/extension/runtime-decision.js +164 -20
- package/dist/adapters/extension/runtime.js +169 -3
- package/dist/adapters/mobile/provision.js +38 -3
- package/dist/adapters/mobile/release-artifact-state.js +124 -0
- package/dist/adapters/mobile/release-artifact.js +295 -0
- package/dist/adapters.js +78 -6
- package/dist/app-lifecycle.js +4 -3
- package/dist/command-contract.js +16 -0
- package/dist/commands/call.js +6 -2
- package/dist/commands/launch/extension.js +74 -9
- package/dist/commands/launch/index.js +3 -0
- package/dist/commands/launch/mobile.js +2 -0
- package/dist/commands/provision.js +2 -0
- package/dist/commands/run-engine.js +109 -14
- package/dist/commands/run.js +4 -2
- package/dist/commands/runtime-launch.js +178 -10
- package/dist/heal-bounds.js +1 -1
- package/dist/live-adapter-contract.js +3 -1
- package/dist/metamask-action-validation.js +47 -1
- package/dist/mm-harness-cli.js +31 -1
- package/dist/recipe-security.js +40 -0
- package/dist/run-diagnostics.js +1 -1
- package/dist/runner.js +3 -0
- package/docs/RELEASE-QA-CAPABILITY-MAP.md +125 -0
- package/library/actions/extension/assets/finish_send.mjs +128 -0
- package/library/actions/extension/assets/import_custom_token.mjs +227 -0
- package/library/actions/extension/assets/prepare_send.mjs +53 -0
- package/library/actions/extension/assets/read_details.mjs +57 -0
- package/library/actions/extension/assets/read_visible_state.mjs +50 -0
- package/library/actions/extension/assets/review_send.mjs +23 -0
- package/library/actions/extension/assets/select_network_scope.mjs +30 -0
- package/library/actions/extension/assets/send.mjs +104 -0
- package/library/actions/extension/assets/set_custom_gas.mjs +56 -0
- package/library/actions/extension/assets/set_token_visibility.mjs +129 -0
- package/library/actions/extension/assets/verify_sorting.mjs +89 -0
- package/library/actions/extension/networks/add_chainlist.mjs +94 -0
- package/library/actions/extension/networks/add_custom.mjs +123 -0
- package/library/actions/extension/networks/custom_network.mjs +103 -0
- package/library/actions/extension/networks/read_visible_state.mjs +50 -0
- package/library/actions/extension/networks/remove_custom.mjs +68 -0
- package/library/actions/extension/perps/close_orders.mjs +1 -0
- package/library/actions/extension/perps/close_visible_position.mjs +133 -0
- package/library/actions/extension/perps/compare_provider_market.mjs +109 -0
- package/library/actions/extension/perps/edit_margin.mjs +82 -0
- package/library/actions/extension/perps/open_balance_action.mjs +58 -0
- package/library/actions/extension/perps/open_position_action.mjs +80 -0
- package/library/actions/extension/perps/perps.mjs +196 -2
- package/library/actions/extension/perps/place_order.mjs +5 -2
- package/library/actions/extension/perps/read_eligibility.mjs +34 -0
- package/library/actions/extension/perps/read_funds_confirmation.mjs +69 -0
- package/library/actions/extension/perps/read_snapshot.mjs +470 -0
- package/library/actions/extension/perps/read_visible_state.mjs +98 -0
- package/library/actions/extension/perps/select_activity_filter.mjs +44 -0
- package/library/actions/extension/perps/select_market_filter.mjs +71 -0
- package/library/actions/extension/perps/set_market_favorite.mjs +47 -0
- package/library/actions/extension/perps/update_position_tpsl.mjs +106 -0
- package/library/actions/extension/platform/cdp.mjs +109 -3
- package/library/actions/extension/settings/set_basic_functionality.mjs +58 -0
- package/library/actions/extension/swap_bridge/read_visible_state.mjs +71 -0
- package/library/actions/extension/swap_bridge/select_assets.mjs +103 -0
- package/library/actions/extension/swap_bridge/set_amount.mjs +36 -0
- package/library/actions/extension/swap_bridge/set_max_amount.mjs +58 -0
- package/library/actions/extension/swap_bridge/set_slippage.mjs +121 -0
- package/library/actions/extension/swap_bridge/submit_transaction.mjs +363 -0
- package/library/actions/extension/ui/activate_and_observe.mjs +96 -0
- package/library/actions/extension/ui/navigate.mjs +264 -8
- package/library/actions/extension/wallet/ensure_unlocked.mjs +2 -6
- package/library/actions/extension/wallet/import.mjs +13 -46
- package/library/actions/extension/wallet/lock.mjs +35 -0
- package/library/actions/extension/wallet/secret-input.mjs +98 -0
- package/library/actions/extension/wallet/select_account.mjs +71 -7
- package/library/actions/mobile/assets/read_visible_state.mjs +37 -0
- package/library/actions/mobile/perps/read_visible_state.mjs +31 -0
- package/library/actions/mobile/platform/native-session-name.mjs +5 -0
- package/library/actions/mobile/platform/observe-ui.mjs +86 -2
- package/library/actions/mobile/swap_bridge/native-session.mjs +45 -0
- package/library/actions/mobile/swap_bridge/read_visible_state.mjs +31 -0
- package/library/actions/mobile/swap_bridge/select_assets.mjs +94 -0
- package/library/actions/mobile/swap_bridge/set_amount.mjs +73 -0
- package/library/actions/mobile/ui/native-navigation.mjs +265 -0
- package/library/actions/mobile/ui/navigate.mjs +10 -1
- package/library/actions/mobile/wallet/ensure_unlocked.mjs +17 -0
- package/library/actions/mobile/wallet/import.mjs +71 -2
- package/library/actions/mobile/wallet/native-ui.mjs +512 -0
- package/library/actions/mobile/wallet/read_state.mjs +16 -0
- package/library/actions/mobile/wallet/reset.mjs +17 -4
- package/library/actions/shared/assets/visible-state.mjs +176 -0
- package/library/actions/shared/networks/visible-state.mjs +70 -0
- package/library/actions/shared/perps/visible-state.mjs +354 -0
- package/library/actions/shared/swap-bridge/transaction.mjs +54 -0
- package/library/actions/shared/swap-bridge/visible-state.mjs +97 -0
- package/library/actions/shared/ui/locators.mjs +7 -0
- package/library/manifests/extension.action-manifest.json +1366 -184
- package/library/manifests/mobile.action-manifest.json +235 -1
- package/library/recipes/assets/release-custom-gas-send.recipe.json +88 -0
- package/library/recipes/assets/release-custom-network-token-persistence.recipe.json +106 -0
- package/library/recipes/assets/release-native-max-cancel.recipe.json +70 -0
- package/library/recipes/assets/release-native-max-send.recipe.json +78 -0
- package/library/recipes/assets/release-own-account-send.recipe.json +80 -0
- package/library/recipes/assets/release-token-address-import.recipe.json +47 -0
- package/library/recipes/assets/release-token-details.recipe.json +48 -0
- package/library/recipes/assets/release-token-hide-readd.recipe.json +55 -0
- package/library/recipes/assets/release-token-list.recipe.json +75 -0
- package/library/recipes/assets/release-token-persistence.recipe.json +73 -0
- package/library/recipes/assets/release-token-search-import.recipe.json +40 -0
- package/library/recipes/assets/release-token-sorting.recipe.json +29 -0
- package/library/recipes/extension/networks/release-balance-stability.recipe.json +51 -0
- package/library/recipes/extension/perps/release-live-limit-order.recipe.json +104 -0
- package/library/recipes/extension/perps/release-order-entry.recipe.json +155 -0
- package/library/recipes/extension/perps/release-order-validation.recipe.json +220 -0
- package/library/recipes/extension/runner/action-validation.recipe.json +12 -1
- package/library/recipes/perps/release-activity-filters.recipe.json +51 -0
- package/library/recipes/perps/release-funds-flows.recipe.json +76 -0
- package/library/recipes/perps/release-live-market-order.recipe.json +122 -0
- package/library/recipes/perps/release-market-details.recipe.json +83 -0
- package/library/recipes/perps/release-market-search.recipe.json +79 -0
- package/library/recipes/perps/release-watchlist.recipe.json +99 -0
- package/library/recipes/swap-bridge/release-custom-slippage.recipe.json +90 -0
- package/library/recipes/swap-bridge/release-live-max-swap.recipe.json +99 -0
- package/library/recipes/swap-bridge/release-live-swap.recipe.json +101 -0
- package/library/recipes/swap-bridge/release-max-quote.recipe.json +72 -0
- package/library/recipes/swap-bridge/release-quote.recipe.json +72 -0
- package/library/recipes/swap-bridge/release-surface.recipe.json +43 -0
- package/library/recipes/wallet/import.recipe.json +20 -1
- package/library/recipes/wallet/reset-import.recipe.json +20 -1
- package/package.json +6 -5
- package/scripts/validate-release-report.mjs +319 -0
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { dataTestId } from '../platform/cdp.mjs';
|
|
2
|
+
|
|
3
|
+
export async function visible(page, selector) {
|
|
4
|
+
return page.evaluate(`(() => {
|
|
5
|
+
const element = document.querySelector(${JSON.stringify(selector)});
|
|
6
|
+
if (!element) return false;
|
|
7
|
+
const style = getComputedStyle(element);
|
|
8
|
+
const rect = element.getBoundingClientRect();
|
|
9
|
+
return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0;
|
|
10
|
+
})()`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function waitFor(page, predicate, message, timeoutMs) {
|
|
14
|
+
const deadline = Date.now() + timeoutMs;
|
|
15
|
+
while (Date.now() < deadline) {
|
|
16
|
+
const value = await predicate();
|
|
17
|
+
if (value) return value;
|
|
18
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
19
|
+
}
|
|
20
|
+
throw new Error(message);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function tokenRow(page, symbol) {
|
|
24
|
+
const marker = `mm-harness-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
25
|
+
return page.evaluate(`(() => {
|
|
26
|
+
const symbol = ${JSON.stringify(symbol.toUpperCase())};
|
|
27
|
+
const marker = ${JSON.stringify(marker)};
|
|
28
|
+
for (const row of document.querySelectorAll('[data-testid="multichain-token-list-button"]')) {
|
|
29
|
+
const amount = String(row.querySelector('[data-testid="multichain-token-list-item-value"]')?.innerText || '').replace(/\\s+/gu, ' ').trim();
|
|
30
|
+
const observed = amount.match(/\\b([A-Za-z][A-Za-z0-9]{1,11})$/u)?.[1]?.toUpperCase();
|
|
31
|
+
if (observed !== symbol) continue;
|
|
32
|
+
const style = getComputedStyle(row);
|
|
33
|
+
const rect = row.getBoundingClientRect();
|
|
34
|
+
if (style.display === 'none' || style.visibility === 'hidden' || rect.width <= 0 || rect.height <= 0) continue;
|
|
35
|
+
row.setAttribute('data-mm-harness-target', marker);
|
|
36
|
+
return {
|
|
37
|
+
selector: '[data-mm-harness-target="' + marker + '"]',
|
|
38
|
+
name: String(row.querySelector('[data-testid="multichain-token-list-item-token-name"]')?.innerText || '').trim(),
|
|
39
|
+
amount,
|
|
40
|
+
fiat: String(row.querySelector('[data-testid="multichain-token-list-item-secondary-value"]')?.innerText || '').trim(),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
return null;
|
|
44
|
+
})()`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export async function bodyState(page) {
|
|
48
|
+
return page.evaluate(`(() => ({
|
|
49
|
+
route: location.hash,
|
|
50
|
+
text: String(document.body?.innerText || '').replace(/\\s+/gu, ' ').trim().slice(0, 20_000),
|
|
51
|
+
}))()`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function transactionState(page) {
|
|
55
|
+
return page.evaluate(`(async () => {
|
|
56
|
+
const hooks = globalThis.stateHooks || {};
|
|
57
|
+
const root = (await hooks.getCleanAppState?.()) || hooks.store?.getState?.() || null;
|
|
58
|
+
const transactions = root?.metamask?.transactions;
|
|
59
|
+
if (!Array.isArray(transactions)) return { available: false, transactions: [] };
|
|
60
|
+
return {
|
|
61
|
+
available: true,
|
|
62
|
+
transactions: transactions.slice(0, 500).map((transaction) => ({
|
|
63
|
+
id: transaction?.id == null ? null : String(transaction.id),
|
|
64
|
+
hash: transaction?.hash == null ? null : String(transaction.hash),
|
|
65
|
+
status: transaction?.status == null ? null : String(transaction.status),
|
|
66
|
+
chainId: transaction?.chainId == null ? null : String(transaction.chainId),
|
|
67
|
+
recipient: transaction?.txParams?.to == null ? null : String(transaction.txParams.to),
|
|
68
|
+
time: Number(transaction?.time ?? 0),
|
|
69
|
+
})).filter((transaction) => transaction.id),
|
|
70
|
+
};
|
|
71
|
+
})()`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export async function activityItem(page, symbol, amount) {
|
|
75
|
+
const marker = `mm-harness-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
76
|
+
return page.evaluate(`(() => {
|
|
77
|
+
const symbol = ${JSON.stringify(symbol.toUpperCase())};
|
|
78
|
+
const amount = ${JSON.stringify(String(amount))};
|
|
79
|
+
const marker = ${JSON.stringify(marker)};
|
|
80
|
+
for (const row of document.querySelectorAll('[data-testid="activity-list-item"]')) {
|
|
81
|
+
const label = String(row.innerText || row.textContent || '').replace(/\\s+/gu, ' ').trim();
|
|
82
|
+
if (!label.toUpperCase().includes(symbol) || (amount.toLowerCase() !== 'max' && !label.includes(amount))) continue;
|
|
83
|
+
const style = getComputedStyle(row);
|
|
84
|
+
const rect = row.getBoundingClientRect();
|
|
85
|
+
if (style.display === 'none' || style.visibility === 'hidden' || rect.width <= 0 || rect.height <= 0) continue;
|
|
86
|
+
row.setAttribute('data-mm-harness-target', marker);
|
|
87
|
+
return { selector: '[data-mm-harness-target="' + marker + '"]', label: label.slice(0, 1000) };
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
})()`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const selectors = {
|
|
94
|
+
send: dataTestId('eth-overview-send'),
|
|
95
|
+
nativeSend: dataTestId('coin-overview-send'),
|
|
96
|
+
recipient: dataTestId('recipient-address-input'),
|
|
97
|
+
amount: dataTestId('send-amount-input'),
|
|
98
|
+
continue: dataTestId('send-continue-button'),
|
|
99
|
+
alert: dataTestId('send-alert-modal-acknowledge-button'),
|
|
100
|
+
confirm: dataTestId('confirm-footer-button'),
|
|
101
|
+
cancel: dataTestId('confirm-footer-cancel-button'),
|
|
102
|
+
activity: dataTestId('bottom-nav-activity'),
|
|
103
|
+
home: dataTestId('bottom-nav-home'),
|
|
104
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { dataTestId, runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
|
+
import { bodyState, visible, waitFor } from './send.mjs';
|
|
3
|
+
|
|
4
|
+
runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
5
|
+
const gasLimit = String(input.node?.gas_limit || '').trim();
|
|
6
|
+
const timeoutMs = Number(input.node?.timeout_ms ?? 30_000);
|
|
7
|
+
if (!/^\d+$/u.test(gasLimit) || Number(gasLimit) <= 0) {
|
|
8
|
+
throw new Error('metamask.assets.set_custom_gas requires a positive integer gas_limit.');
|
|
9
|
+
}
|
|
10
|
+
const edit = dataTestId('edit-gas-fee-icon');
|
|
11
|
+
const advanced = dataTestId('gas-option-advanced');
|
|
12
|
+
const modal = dataTestId('gas-fee-advanced-eip1559-modal');
|
|
13
|
+
const gasInput = '#gas-input';
|
|
14
|
+
const maxFeeInput = '#max-base-fee-input';
|
|
15
|
+
const priorityFeeInput = '#priority-fee-input';
|
|
16
|
+
const save = dataTestId('gas-fee-modal-save-button');
|
|
17
|
+
if (!(await visible(page, modal))) {
|
|
18
|
+
await waitFor(page, async () => {
|
|
19
|
+
if (await visible(page, modal)) return true;
|
|
20
|
+
const control = (await visible(page, advanced)) ? advanced : (await visible(page, edit)) ? edit : null;
|
|
21
|
+
if (control) {
|
|
22
|
+
try {
|
|
23
|
+
await page.click(control);
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return null;
|
|
29
|
+
}, 'Advanced gas editor did not become visible.', timeoutMs);
|
|
30
|
+
}
|
|
31
|
+
await waitFor(page, () => visible(page, gasInput), 'Gas limit input did not become visible.', timeoutMs);
|
|
32
|
+
const fees = await page.evaluate(`(() => ({
|
|
33
|
+
maxFee: document.querySelector(${JSON.stringify(maxFeeInput)})?.value || '',
|
|
34
|
+
priorityFee: document.querySelector(${JSON.stringify(priorityFeeInput)})?.value || '',
|
|
35
|
+
}))()`);
|
|
36
|
+
if (!(Number(fees.priorityFee) > 0)) {
|
|
37
|
+
const maxFee = Number(fees.maxFee);
|
|
38
|
+
if (!(maxFee > 0)) throw new Error('Advanced gas editor did not expose a positive max base fee.');
|
|
39
|
+
await page.setInput(priorityFeeInput, String(Math.min(maxFee / 2, 0.01)));
|
|
40
|
+
}
|
|
41
|
+
await page.setInput(gasInput, gasLimit);
|
|
42
|
+
await waitFor(page, () => page.evaluate(`document.querySelector(${JSON.stringify(save)})?.disabled === false`), 'Custom gas values did not enable Save.', timeoutMs);
|
|
43
|
+
await page.click(save);
|
|
44
|
+
const state = await waitFor(page, async () => {
|
|
45
|
+
const current = await bodyState(page);
|
|
46
|
+
return !(await visible(page, modal)) && /Speed Advanced/u.test(current.text) ? current : null;
|
|
47
|
+
}, 'Custom gas was not applied to the confirmation.', timeoutMs);
|
|
48
|
+
return {
|
|
49
|
+
action: input.action,
|
|
50
|
+
gasLimit,
|
|
51
|
+
maxFeeGwei: fees.maxFee,
|
|
52
|
+
priorityFeeGwei: Number(fees.priorityFee) > 0 ? fees.priorityFee : String(Math.min(Number(fees.maxFee) / 2, 0.01)),
|
|
53
|
+
...state,
|
|
54
|
+
proofPath: 'trusted-pointer-visible-ui',
|
|
55
|
+
};
|
|
56
|
+
}));
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { dataTestId, runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
|
+
|
|
3
|
+
const OPTIONS = dataTestId('asset-list-control-bar-action-button');
|
|
4
|
+
const MANAGE = dataTestId('manageTokens__button');
|
|
5
|
+
const PAGE = dataTestId('token-management-page');
|
|
6
|
+
const SEARCH = dataTestId('token-management-search-input');
|
|
7
|
+
const BACK = dataTestId('token-management-header-back-button');
|
|
8
|
+
const LOW_VALUE = dataTestId('low-value-assets-toggle');
|
|
9
|
+
|
|
10
|
+
async function visible(page, selector) {
|
|
11
|
+
return page.evaluate(`(() => {
|
|
12
|
+
const element = document.querySelector(${JSON.stringify(selector)});
|
|
13
|
+
if (!element) return false;
|
|
14
|
+
const style = getComputedStyle(element);
|
|
15
|
+
const rect = element.getBoundingClientRect();
|
|
16
|
+
return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0;
|
|
17
|
+
})()`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async function waitFor(page, predicate, message, timeoutMs) {
|
|
21
|
+
const deadline = Date.now() + timeoutMs;
|
|
22
|
+
while (Date.now() < deadline) {
|
|
23
|
+
const value = await predicate();
|
|
24
|
+
if (value) return value;
|
|
25
|
+
await page.evaluate('new Promise((resolve) => setTimeout(resolve, 150))');
|
|
26
|
+
}
|
|
27
|
+
throw new Error(message);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async function matchingToggle(page, query) {
|
|
31
|
+
return page.evaluate(`(() => {
|
|
32
|
+
const query = ${JSON.stringify(query.toLowerCase())};
|
|
33
|
+
const candidates = [...document.querySelectorAll('[data-testid^="token-management-cell-search-"][data-testid$="-toggle-control"]')];
|
|
34
|
+
for (const element of candidates) {
|
|
35
|
+
const testId = element.getAttribute('data-testid');
|
|
36
|
+
const row = document.querySelector('[data-testid="' + testId.slice(0, -'-toggle-control'.length) + '"]');
|
|
37
|
+
const label = String(row?.innerText || element.innerText || '').replace(/\\s+/gu, ' ').trim().slice(0, 500);
|
|
38
|
+
const words = label.toLowerCase().split(/[^a-z0-9]+/u).filter(Boolean);
|
|
39
|
+
if (!words.includes(query) && !label.toLowerCase().includes(query)) continue;
|
|
40
|
+
const style = getComputedStyle(element);
|
|
41
|
+
const rect = element.getBoundingClientRect();
|
|
42
|
+
if (style.display === 'none' || style.visibility === 'hidden' || rect.width <= 0 || rect.height <= 0) continue;
|
|
43
|
+
return {
|
|
44
|
+
testId,
|
|
45
|
+
label,
|
|
46
|
+
enabled: Boolean(element.querySelector('.toggle-button--on')),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
})()`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function homeToken(page, query) {
|
|
54
|
+
return page.evaluate(`(() => {
|
|
55
|
+
const query = ${JSON.stringify(query.toLowerCase())};
|
|
56
|
+
for (const row of document.querySelectorAll('[data-testid="multichain-token-list-button"]')) {
|
|
57
|
+
const name = String(row.querySelector('[data-testid="multichain-token-list-item-token-name"]')?.innerText || '').replace(/\\s+/gu, ' ').trim();
|
|
58
|
+
const amount = String(row.querySelector('[data-testid="multichain-token-list-item-value"]')?.innerText || '').replace(/\\s+/gu, ' ').trim();
|
|
59
|
+
const symbol = amount.match(/\\b([A-Za-z][A-Za-z0-9]{1,11})$/u)?.[1] || '';
|
|
60
|
+
if (symbol.toLowerCase() === query || name.toLowerCase().includes(query)) {
|
|
61
|
+
return { name, symbol: symbol.toUpperCase(), amount };
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return null;
|
|
65
|
+
})()`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
69
|
+
const query = String(input.node?.query || '').trim();
|
|
70
|
+
if (!query || query.length > 50) {
|
|
71
|
+
throw new Error('metamask.assets.set_token_visibility requires query with 1-50 characters.');
|
|
72
|
+
}
|
|
73
|
+
if (typeof input.node?.visible !== 'boolean') {
|
|
74
|
+
throw new Error('metamask.assets.set_token_visibility requires visible=true or visible=false.');
|
|
75
|
+
}
|
|
76
|
+
const requested = input.node.visible;
|
|
77
|
+
const requireUnchanged = input.node?.require_unchanged === true;
|
|
78
|
+
const timeoutMs = Number(input.node?.timeout_ms ?? 45_000);
|
|
79
|
+
|
|
80
|
+
if (!(await visible(page, MANAGE))) await page.click(OPTIONS);
|
|
81
|
+
await waitFor(page, () => visible(page, MANAGE), 'Manage tokens did not become visible.', timeoutMs);
|
|
82
|
+
await page.click(MANAGE);
|
|
83
|
+
await waitFor(page, () => visible(page, PAGE), 'Token management did not open.', timeoutMs);
|
|
84
|
+
await page.setInput(SEARCH, query);
|
|
85
|
+
const before = await waitFor(
|
|
86
|
+
page,
|
|
87
|
+
() => matchingToggle(page, query),
|
|
88
|
+
`Token management returned no visible result for ${query}.`,
|
|
89
|
+
timeoutMs,
|
|
90
|
+
);
|
|
91
|
+
if (requireUnchanged && before.enabled !== requested) {
|
|
92
|
+
throw new Error(`${query} token visibility did not persist as ${requested ? 'enabled' : 'disabled'}.`);
|
|
93
|
+
}
|
|
94
|
+
if (before.enabled !== requested) {
|
|
95
|
+
await page.click(dataTestId(before.testId));
|
|
96
|
+
}
|
|
97
|
+
const after = await waitFor(
|
|
98
|
+
page,
|
|
99
|
+
async () => {
|
|
100
|
+
const state = await matchingToggle(page, query);
|
|
101
|
+
return state?.enabled === requested ? state : null;
|
|
102
|
+
},
|
|
103
|
+
`${query} token visibility did not become ${requested ? 'enabled' : 'disabled'}.`,
|
|
104
|
+
timeoutMs,
|
|
105
|
+
);
|
|
106
|
+
await page.click(BACK);
|
|
107
|
+
await waitFor(page, () => visible(page, OPTIONS), 'Token list did not reopen.', timeoutMs);
|
|
108
|
+
|
|
109
|
+
if (requested && (await visible(page, LOW_VALUE))) {
|
|
110
|
+
const expanded = await page.evaluate(`document.querySelector(${JSON.stringify(LOW_VALUE)})?.getAttribute('aria-expanded') === 'true'`);
|
|
111
|
+
if (!expanded) await page.click(LOW_VALUE);
|
|
112
|
+
}
|
|
113
|
+
const token = requested
|
|
114
|
+
? await waitFor(page, () => homeToken(page, query), `${query} did not appear in the token list.`, timeoutMs)
|
|
115
|
+
: await homeToken(page, query);
|
|
116
|
+
if (!requested && token) throw new Error(`${query} remained visible after disabling it.`);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
action: input.action,
|
|
120
|
+
query,
|
|
121
|
+
requested,
|
|
122
|
+
requireUnchanged,
|
|
123
|
+
changed: before.enabled !== after.enabled,
|
|
124
|
+
searchResult: after.label,
|
|
125
|
+
toggleTestId: after.testId,
|
|
126
|
+
token,
|
|
127
|
+
proofPath: 'trusted-pointer-visible-ui',
|
|
128
|
+
};
|
|
129
|
+
}));
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { dataTestId, runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
|
+
|
|
3
|
+
const TOGGLE = dataTestId('sort-by-popover-toggle');
|
|
4
|
+
const ALPHABETICAL = dataTestId('sortByAlphabetically__button');
|
|
5
|
+
const DECLINING = dataTestId('sortByDecliningBalance__button');
|
|
6
|
+
|
|
7
|
+
async function readRows(page) {
|
|
8
|
+
return page.evaluate(`(() => [...document.querySelectorAll('[data-testid="multichain-token-list-button"]')]
|
|
9
|
+
.filter((element) => {
|
|
10
|
+
const style = getComputedStyle(element);
|
|
11
|
+
const rect = element.getBoundingClientRect();
|
|
12
|
+
return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0;
|
|
13
|
+
})
|
|
14
|
+
.map((element) => ({
|
|
15
|
+
name: String(element.querySelector('[data-testid="multichain-token-list-item-token-name"]')?.innerText || '').replace(/\\s+/gu, ' ').trim(),
|
|
16
|
+
fiat: String(element.querySelector('[data-testid="multichain-token-list-item-secondary-value"]')?.innerText || '').replace(/\\s+/gu, ' ').trim(),
|
|
17
|
+
})))()`);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function fiatNumber(value) {
|
|
21
|
+
const sanitized = String(value).replace(/[^0-9.-]/gu, '');
|
|
22
|
+
if (!/\d/u.test(sanitized)) return null;
|
|
23
|
+
const parsed = Number(sanitized);
|
|
24
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function assertAlphabetical(rows) {
|
|
28
|
+
const names = rows.map((row) => row.name).filter(Boolean);
|
|
29
|
+
if (names.length < 2) {
|
|
30
|
+
throw new Error(`Asset A-Z sorting requires at least two named rows, but observed ${names.length}.`);
|
|
31
|
+
}
|
|
32
|
+
const expected = [...names].sort((left, right) => left.localeCompare(right, 'en', { sensitivity: 'base' }));
|
|
33
|
+
if (names.some((name, index) => name !== expected[index])) {
|
|
34
|
+
throw new Error(`Asset names are not sorted A-Z: ${names.join(', ')}.`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function assertDeclining(rows) {
|
|
39
|
+
const values = rows.map((row) => fiatNumber(row.fiat)).filter((value) => value !== null);
|
|
40
|
+
if (values.length < 2) {
|
|
41
|
+
throw new Error(`Asset balance sorting requires at least two comparable fiat rows, but observed ${values.length}.`);
|
|
42
|
+
}
|
|
43
|
+
if (values.some((value, index) => index > 0 && values[index - 1] < value)) {
|
|
44
|
+
throw new Error(`Asset fiat values are not sorted high-low: ${values.join(', ')}.`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function choose(page, selector) {
|
|
49
|
+
const isVisible = async () => page.evaluate(`(() => {
|
|
50
|
+
const element = document.querySelector(${JSON.stringify(selector)});
|
|
51
|
+
if (!element) return false;
|
|
52
|
+
const style = getComputedStyle(element);
|
|
53
|
+
const rect = element.getBoundingClientRect();
|
|
54
|
+
return style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0;
|
|
55
|
+
})()`);
|
|
56
|
+
if (!(await isVisible())) await page.click(TOGGLE);
|
|
57
|
+
const deadline = Date.now() + 5_000;
|
|
58
|
+
while (!(await isVisible())) {
|
|
59
|
+
if (Date.now() >= deadline) throw new Error('Asset sort menu did not become visible.');
|
|
60
|
+
await page.evaluate('new Promise((resolve) => setTimeout(resolve, 100))');
|
|
61
|
+
}
|
|
62
|
+
await page.click(selector);
|
|
63
|
+
await page.evaluate('new Promise((resolve) => setTimeout(resolve, 500))');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
67
|
+
if (!String(await page.evaluate('location.hash')).startsWith('#/')) {
|
|
68
|
+
throw new Error('metamask.assets.verify_sorting requires the wallet home token list.');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
await choose(page, ALPHABETICAL);
|
|
72
|
+
const alphabetical = await readRows(page);
|
|
73
|
+
assertAlphabetical(alphabetical);
|
|
74
|
+
|
|
75
|
+
await choose(page, DECLINING);
|
|
76
|
+
const declining = await readRows(page);
|
|
77
|
+
assertDeclining(declining);
|
|
78
|
+
|
|
79
|
+
return {
|
|
80
|
+
action: input.action,
|
|
81
|
+
source: 'visible-ui',
|
|
82
|
+
platform: 'extension',
|
|
83
|
+
surface: 'token-list',
|
|
84
|
+
alphabetical: alphabetical.map((row) => row.name),
|
|
85
|
+
decliningFiat: declining.map((row) => row.fiat),
|
|
86
|
+
restoredSort: 'declining-balance',
|
|
87
|
+
proofPath: 'trusted-pointer-visible-ui',
|
|
88
|
+
};
|
|
89
|
+
}));
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { dataTestId, runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
|
+
import {
|
|
3
|
+
findNetworkItem,
|
|
4
|
+
openNetworkManager,
|
|
5
|
+
selectHomeNetwork,
|
|
6
|
+
waitFor,
|
|
7
|
+
} from './custom_network.mjs';
|
|
8
|
+
|
|
9
|
+
const ADD_CUSTOM = dataTestId('networks-page-add-custom-network-button');
|
|
10
|
+
const ADD_CHAINLIST = dataTestId('network-form-add-from-chainlist');
|
|
11
|
+
const CHAINLIST_SEARCH = dataTestId('networks-page-chainlist-search');
|
|
12
|
+
const CHAINLIST_ROW = '[data-testid="networks-page-chainlist-network"]';
|
|
13
|
+
const SAVE = dataTestId('page-container-footer-next');
|
|
14
|
+
|
|
15
|
+
runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
16
|
+
const network = String(input.node?.network || '').trim();
|
|
17
|
+
const query = String(input.node?.query || network).trim();
|
|
18
|
+
const chainId = Number(input.node?.chain_id);
|
|
19
|
+
const symbol = String(input.node?.symbol || '').trim().toUpperCase();
|
|
20
|
+
const timeoutMs = Number(input.node?.timeout_ms ?? 60_000);
|
|
21
|
+
if (!network || !query || !Number.isInteger(chainId) || chainId <= 0 || !symbol) {
|
|
22
|
+
throw new Error('metamask.networks.add_chainlist requires network, query, positive chain_id, and symbol.');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const chainlistOpen = await page.evaluate(`Boolean(document.querySelector(${JSON.stringify(CHAINLIST_SEARCH)}))`);
|
|
26
|
+
if (!chainlistOpen) await openNetworkManager(page, timeoutMs);
|
|
27
|
+
let item = chainlistOpen ? null : await findNetworkItem(page, network, chainId);
|
|
28
|
+
let added = false;
|
|
29
|
+
if (!item) {
|
|
30
|
+
if (!chainlistOpen) {
|
|
31
|
+
await page.click(ADD_CUSTOM);
|
|
32
|
+
await waitFor(page, () => page.evaluate(`Boolean(document.querySelector(${JSON.stringify(ADD_CHAINLIST)}))`), 'Add from ChainList did not become visible.', timeoutMs);
|
|
33
|
+
}
|
|
34
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
35
|
+
if (await page.evaluate(`Boolean(document.querySelector(${JSON.stringify(CHAINLIST_SEARCH)}))`)) break;
|
|
36
|
+
if (await page.evaluate(`Boolean(document.querySelector(${JSON.stringify(ADD_CHAINLIST)}))`)) {
|
|
37
|
+
await page.click(ADD_CHAINLIST);
|
|
38
|
+
}
|
|
39
|
+
await page.evaluate('new Promise((resolve) => setTimeout(resolve, 500))');
|
|
40
|
+
}
|
|
41
|
+
await waitFor(page, () => page.evaluate(`Boolean(document.querySelector(${JSON.stringify(CHAINLIST_SEARCH)}))`), 'ChainList network search did not become visible.', timeoutMs);
|
|
42
|
+
await page.setInput(`${CHAINLIST_SEARCH} input`, query);
|
|
43
|
+
const row = await waitFor(page, () => page.evaluate(`(() => {
|
|
44
|
+
const expectedName = ${JSON.stringify(network.toLowerCase())};
|
|
45
|
+
const expectedSymbol = ${JSON.stringify(symbol.toLowerCase())};
|
|
46
|
+
const expectedId = ${JSON.stringify(String(chainId))};
|
|
47
|
+
for (const element of document.querySelectorAll(${JSON.stringify(CHAINLIST_ROW)})) {
|
|
48
|
+
const text = String(element.innerText || element.textContent || '').replace(/\\s+/gu, ' ').trim();
|
|
49
|
+
const normalized = text.toLowerCase();
|
|
50
|
+
if (!normalized.includes(expectedName) || !normalized.includes('symbol: ' + expectedSymbol) || !normalized.includes('id: ' + expectedId)) continue;
|
|
51
|
+
element.setAttribute('data-mm-harness-chainlist-row', 'true');
|
|
52
|
+
return { selector: '[data-mm-harness-chainlist-row="true"]', text: text.slice(0, 500) };
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
})()`), `${network} (ID ${chainId}) did not appear in ChainList results.`, timeoutMs);
|
|
56
|
+
await page.click(row.selector);
|
|
57
|
+
let enabledSince = 0;
|
|
58
|
+
await waitFor(page, async () => {
|
|
59
|
+
const enabled = await page.evaluate(`(() => {
|
|
60
|
+
const button = document.querySelector(${JSON.stringify(SAVE)});
|
|
61
|
+
return Boolean(button && !button.disabled && button.getAttribute('aria-disabled') !== 'true');
|
|
62
|
+
})()`);
|
|
63
|
+
if (!enabled) {
|
|
64
|
+
enabledSince = 0;
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
if (enabledSince === 0) enabledSince = Date.now();
|
|
68
|
+
return Date.now() - enabledSince >= 1500;
|
|
69
|
+
}, 'ChainList network Save did not remain enabled after prefilling.', timeoutMs);
|
|
70
|
+
await waitFor(page, async () => {
|
|
71
|
+
const stillOpen = await page.evaluate(`Boolean(document.querySelector(${JSON.stringify(SAVE)}))`);
|
|
72
|
+
if (!stillOpen) return true;
|
|
73
|
+
await page.click(SAVE);
|
|
74
|
+
await page.evaluate('new Promise((resolve) => setTimeout(resolve, 500))');
|
|
75
|
+
return false;
|
|
76
|
+
}, 'ChainList network Save did not close the prefilled form.', timeoutMs);
|
|
77
|
+
item = await waitFor(page, () => findNetworkItem(page, network, chainId), `${network} did not appear after the ChainList save.`, timeoutMs);
|
|
78
|
+
added = true;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
await page.click(dataTestId('page-header-back-button'));
|
|
82
|
+
await waitFor(page, () => page.evaluate(`Boolean(document.querySelector(${JSON.stringify(dataTestId('sort-by-networks'))}))`), 'Wallet home did not reopen after ChainList network management.', timeoutMs);
|
|
83
|
+
await selectHomeNetwork(page, network, chainId, timeoutMs);
|
|
84
|
+
return {
|
|
85
|
+
action: input.action,
|
|
86
|
+
network,
|
|
87
|
+
chainId,
|
|
88
|
+
symbol,
|
|
89
|
+
query,
|
|
90
|
+
added,
|
|
91
|
+
item,
|
|
92
|
+
proofPath: 'trusted-pointer-chainlist-ui',
|
|
93
|
+
};
|
|
94
|
+
}));
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { dataTestId, runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
|
+
import {
|
|
3
|
+
findNetworkItem,
|
|
4
|
+
openNetworkManager,
|
|
5
|
+
selectHomeNetwork,
|
|
6
|
+
semanticButton,
|
|
7
|
+
semanticButtonOrNull,
|
|
8
|
+
waitFor,
|
|
9
|
+
} from './custom_network.mjs';
|
|
10
|
+
|
|
11
|
+
runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
12
|
+
const network = String(input.node?.network || '').trim();
|
|
13
|
+
const rpcUrl = String(input.node?.rpc_url || '').trim();
|
|
14
|
+
const chainId = Number(input.node?.chain_id);
|
|
15
|
+
const symbol = String(input.node?.symbol || '').trim();
|
|
16
|
+
const explorerUrl = String(input.node?.explorer_url || '').trim();
|
|
17
|
+
const timeoutMs = Number(input.node?.timeout_ms ?? 60_000);
|
|
18
|
+
if (!network || !/^https?:\/\//u.test(rpcUrl) || !Number.isInteger(chainId) || chainId <= 0 || !symbol) {
|
|
19
|
+
throw new Error('metamask.networks.add_custom requires network, rpc_url, positive chain_id, and symbol.');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
await openNetworkManager(page, timeoutMs);
|
|
23
|
+
let item = await findNetworkItem(page, network, chainId);
|
|
24
|
+
let added = false;
|
|
25
|
+
if (!item) {
|
|
26
|
+
await page.click(dataTestId('networks-page-add-custom-network-button'));
|
|
27
|
+
await waitFor(
|
|
28
|
+
page,
|
|
29
|
+
() => page.evaluate(`Boolean(document.querySelector(${JSON.stringify(dataTestId('network-form-network-name'))}))`),
|
|
30
|
+
'Custom network form did not open.',
|
|
31
|
+
timeoutMs,
|
|
32
|
+
);
|
|
33
|
+
await page.setInput(dataTestId('network-form-network-name'), network);
|
|
34
|
+
await page.setInput(dataTestId('network-form-chain-id'), chainId);
|
|
35
|
+
await page.setInput(dataTestId('network-form-ticker-input'), symbol);
|
|
36
|
+
|
|
37
|
+
await page.evaluate('new Promise((resolve) => setTimeout(resolve, 1000))');
|
|
38
|
+
let addRpc;
|
|
39
|
+
for (let attempt = 0; attempt < 3 && !addRpc; attempt += 1) {
|
|
40
|
+
await page.click(dataTestId('test-add-rpc-drop-down'));
|
|
41
|
+
await page.evaluate('new Promise((resolve) => setTimeout(resolve, 250))');
|
|
42
|
+
addRpc = await semanticButtonOrNull(page, 'Add RPC URL');
|
|
43
|
+
}
|
|
44
|
+
if (!addRpc) throw new Error('Visible button "Add RPC URL" was not found after opening the RPC menu.');
|
|
45
|
+
await page.click(addRpc);
|
|
46
|
+
await page.setInput(dataTestId('rpc-url-input-test'), rpcUrl);
|
|
47
|
+
await page.click(dataTestId('page-container-footer-next'));
|
|
48
|
+
await waitFor(
|
|
49
|
+
page,
|
|
50
|
+
() => page.evaluate(`!document.querySelector(${JSON.stringify(dataTestId('rpc-url-input-test'))})`),
|
|
51
|
+
'RPC URL form did not close after adding the URL.',
|
|
52
|
+
timeoutMs,
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
if (explorerUrl) {
|
|
56
|
+
await page.evaluate('new Promise((resolve) => setTimeout(resolve, 1000))');
|
|
57
|
+
let addExplorer;
|
|
58
|
+
for (let attempt = 0; attempt < 3 && !addExplorer; attempt += 1) {
|
|
59
|
+
await page.click(dataTestId('test-explorer-drop-down'));
|
|
60
|
+
await page.evaluate('new Promise((resolve) => setTimeout(resolve, 250))');
|
|
61
|
+
addExplorer = await semanticButtonOrNull(page, 'Add a block explorer URL');
|
|
62
|
+
}
|
|
63
|
+
if (!addExplorer) throw new Error('Visible button "Add a block explorer URL" was not found after opening the explorer menu.');
|
|
64
|
+
await page.click(addExplorer);
|
|
65
|
+
await page.setInput(dataTestId('explorer-url-input'), explorerUrl);
|
|
66
|
+
await page.click(await semanticButton(page, 'Add URL'));
|
|
67
|
+
await waitFor(
|
|
68
|
+
page,
|
|
69
|
+
() => page.evaluate(`!document.querySelector(${JSON.stringify(dataTestId('explorer-url-input'))})`),
|
|
70
|
+
'Block explorer form did not close after adding the URL.',
|
|
71
|
+
timeoutMs,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
let enabledSince = 0;
|
|
76
|
+
await waitFor(page, async () => {
|
|
77
|
+
const enabled = await page.evaluate(`(() => {
|
|
78
|
+
for (const element of document.querySelectorAll('button')) {
|
|
79
|
+
const text = String(element.innerText || element.textContent || '').replace(/\\s+/gu, ' ').trim();
|
|
80
|
+
const style = getComputedStyle(element);
|
|
81
|
+
const rect = element.getBoundingClientRect();
|
|
82
|
+
if (text === 'Save' && !element.disabled && element.getAttribute('aria-disabled') !== 'true' && style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0) return true;
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
85
|
+
})()`);
|
|
86
|
+
if (!enabled) {
|
|
87
|
+
enabledSince = 0;
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
if (enabledSince === 0) enabledSince = Date.now();
|
|
91
|
+
return Date.now() - enabledSince >= 1500;
|
|
92
|
+
}, 'Custom network save did not remain enabled after RPC validation.', timeoutMs);
|
|
93
|
+
const save = await semanticButton(page, 'Save');
|
|
94
|
+
await page.click(save);
|
|
95
|
+
item = await waitFor(
|
|
96
|
+
page,
|
|
97
|
+
() => findNetworkItem(page, network, chainId),
|
|
98
|
+
`${network} did not appear in the network manager after save.`,
|
|
99
|
+
timeoutMs,
|
|
100
|
+
);
|
|
101
|
+
added = true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
await page.click(dataTestId('page-header-back-button'));
|
|
105
|
+
await waitFor(
|
|
106
|
+
page,
|
|
107
|
+
() => page.evaluate(`Boolean(document.querySelector(${JSON.stringify(dataTestId('sort-by-networks'))}))`),
|
|
108
|
+
'Wallet home did not reopen after network management.',
|
|
109
|
+
timeoutMs,
|
|
110
|
+
);
|
|
111
|
+
await selectHomeNetwork(page, network, chainId, timeoutMs);
|
|
112
|
+
return {
|
|
113
|
+
action: input.action,
|
|
114
|
+
network,
|
|
115
|
+
chainId,
|
|
116
|
+
symbol,
|
|
117
|
+
rpcUrl,
|
|
118
|
+
explorerUrl: explorerUrl || null,
|
|
119
|
+
added,
|
|
120
|
+
item,
|
|
121
|
+
proofPath: 'trusted-pointer-visible-ui',
|
|
122
|
+
};
|
|
123
|
+
}));
|