@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,470 @@
|
|
|
1
|
+
import { pathToFileURL } from 'node:url';
|
|
2
|
+
|
|
3
|
+
import { normalizeMarketSymbol, runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
4
|
+
|
|
5
|
+
const ALL_SECTIONS = ['account', 'markets', 'positions', 'orders', 'history', 'watchlist'];
|
|
6
|
+
const DEFAULT_HISTORY_WINDOW_MS = 30 * 24 * 60 * 60 * 1000;
|
|
7
|
+
const MAX_HISTORY_WINDOW_MS = 365 * 24 * 60 * 60 * 1000;
|
|
8
|
+
|
|
9
|
+
function boundedInteger(value, fallback, minimum, maximum, name) {
|
|
10
|
+
const resolved = value == null ? fallback : Number(value);
|
|
11
|
+
if (!Number.isInteger(resolved) || resolved < minimum || resolved > maximum) {
|
|
12
|
+
throw new Error(`metamask.perps.read_snapshot ${name} must be an integer from ${minimum} to ${maximum}.`);
|
|
13
|
+
}
|
|
14
|
+
return resolved;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function requestedMarkets(node) {
|
|
18
|
+
if (node.market != null && node.markets != null) {
|
|
19
|
+
throw new Error('metamask.perps.read_snapshot accepts market or markets, not both.');
|
|
20
|
+
}
|
|
21
|
+
const values = node.markets ?? (node.market == null ? [] : [node.market]);
|
|
22
|
+
if (!Array.isArray(values)) {
|
|
23
|
+
throw new Error('metamask.perps.read_snapshot markets must be an array of market symbols.');
|
|
24
|
+
}
|
|
25
|
+
const markets = Array.from(new Set(
|
|
26
|
+
values.map((value) => typeof value === 'string' ? normalizeMarketSymbol(value) : '').filter(Boolean),
|
|
27
|
+
));
|
|
28
|
+
if (markets.length > 100) {
|
|
29
|
+
throw new Error('metamask.perps.read_snapshot accepts at most 100 market symbols.');
|
|
30
|
+
}
|
|
31
|
+
return markets;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function resolveSnapshotOptions(input, now = Date.now()) {
|
|
35
|
+
const node = input.node ?? {};
|
|
36
|
+
const include = node.include ?? ALL_SECTIONS;
|
|
37
|
+
if (!Array.isArray(include) || include.length === 0) {
|
|
38
|
+
throw new Error('metamask.perps.read_snapshot include must be a non-empty array.');
|
|
39
|
+
}
|
|
40
|
+
const sections = Array.from(new Set(include.map((section) => String(section).toLowerCase())));
|
|
41
|
+
const unsupported = sections.filter((section) => !ALL_SECTIONS.includes(section));
|
|
42
|
+
if (unsupported.length > 0) {
|
|
43
|
+
throw new Error(`metamask.perps.read_snapshot received unsupported sections: ${unsupported.join(', ')}.`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const history = node.history && typeof node.history === 'object' && !Array.isArray(node.history)
|
|
47
|
+
? node.history
|
|
48
|
+
: {};
|
|
49
|
+
const endTime = boundedInteger(
|
|
50
|
+
history.end_time_ms,
|
|
51
|
+
now,
|
|
52
|
+
0,
|
|
53
|
+
Number.MAX_SAFE_INTEGER,
|
|
54
|
+
'history.end_time_ms',
|
|
55
|
+
);
|
|
56
|
+
const startTime = boundedInteger(
|
|
57
|
+
history.start_time_ms,
|
|
58
|
+
Math.max(0, endTime - DEFAULT_HISTORY_WINDOW_MS),
|
|
59
|
+
0,
|
|
60
|
+
Number.MAX_SAFE_INTEGER,
|
|
61
|
+
'history.start_time_ms',
|
|
62
|
+
);
|
|
63
|
+
if (startTime > endTime) {
|
|
64
|
+
throw new Error('metamask.perps.read_snapshot history.start_time_ms must not exceed history.end_time_ms.');
|
|
65
|
+
}
|
|
66
|
+
if (endTime - startTime > MAX_HISTORY_WINDOW_MS) {
|
|
67
|
+
throw new Error('metamask.perps.read_snapshot history range must not exceed 365 days.');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
include: sections,
|
|
72
|
+
markets: requestedMarkets(node),
|
|
73
|
+
timeoutMs: boundedInteger(node.timeout_ms, 20000, 1000, 60000, 'timeout_ms'),
|
|
74
|
+
maxItems: boundedInteger(node.max_items_per_section, 100, 1, 500, 'max_items_per_section'),
|
|
75
|
+
history: {
|
|
76
|
+
startTime,
|
|
77
|
+
endTime,
|
|
78
|
+
accountId: history.account_id == null || history.account_id === ''
|
|
79
|
+
? undefined
|
|
80
|
+
: String(history.account_id),
|
|
81
|
+
limit: boundedInteger(history.limit, 100, 1, 500, 'history.limit'),
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function snapshotRequestExpression(options) {
|
|
87
|
+
const methods = {
|
|
88
|
+
account: ['perpsGetAccountState', []],
|
|
89
|
+
markets: ['perpsGetMarketDataWithPrices', []],
|
|
90
|
+
positions: ['perpsGetPositions', []],
|
|
91
|
+
orders: ['perpsGetOpenOrders', []],
|
|
92
|
+
history: ['perpsGetUserHistory', null],
|
|
93
|
+
watchlist: ['perpsGetWatchlistMarkets', []],
|
|
94
|
+
};
|
|
95
|
+
return `(async () => {
|
|
96
|
+
const request = globalThis.stateHooks?.submitRequestToBackground;
|
|
97
|
+
if (typeof request !== 'function') {
|
|
98
|
+
throw new Error('stateHooks.submitRequestToBackground is unavailable; cannot read live Perps snapshot.');
|
|
99
|
+
}
|
|
100
|
+
const timeoutMs = ${JSON.stringify(options.timeoutMs)};
|
|
101
|
+
const historyParams = {
|
|
102
|
+
startTime: ${JSON.stringify(options.history.startTime)},
|
|
103
|
+
endTime: ${JSON.stringify(options.history.endTime)},
|
|
104
|
+
accountId: ${options.history.accountId === undefined ? 'undefined' : JSON.stringify(options.history.accountId)},
|
|
105
|
+
};
|
|
106
|
+
const methods = ${JSON.stringify(methods)};
|
|
107
|
+
const markets = ${JSON.stringify(options.markets)};
|
|
108
|
+
const maxItems = ${JSON.stringify(options.maxItems)};
|
|
109
|
+
const historyLimit = ${JSON.stringify(Math.min(options.history.limit, options.maxItems))};
|
|
110
|
+
function field(record, names) {
|
|
111
|
+
for (const name of names) {
|
|
112
|
+
const value = record?.[name];
|
|
113
|
+
if (value === null || ['string', 'number', 'boolean'].includes(typeof value)) return value;
|
|
114
|
+
}
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
function marketFor(item) {
|
|
118
|
+
const value = field(item, ['symbol', 'coin', 'market', 'asset']);
|
|
119
|
+
return typeof value === 'string' ? value.replace(/-PERP$/iu, '').toUpperCase() : '';
|
|
120
|
+
}
|
|
121
|
+
function sideFor(item) {
|
|
122
|
+
const side = String(field(item, ['side', 'direction']) ?? '').toLowerCase();
|
|
123
|
+
if (['buy', 'b', 'long'].includes(side)) return 'long';
|
|
124
|
+
if (['sell', 'a', 's', 'short'].includes(side)) return 'short';
|
|
125
|
+
return side || null;
|
|
126
|
+
}
|
|
127
|
+
function leverageFor(item) {
|
|
128
|
+
const leverage = item?.leverage;
|
|
129
|
+
if (leverage === null || ['string', 'number'].includes(typeof leverage)) return leverage;
|
|
130
|
+
if (!leverage || typeof leverage !== 'object' || Array.isArray(leverage)) return null;
|
|
131
|
+
return { type: field(leverage, ['type']), value: field(leverage, ['value']) };
|
|
132
|
+
}
|
|
133
|
+
function normalize(section, item) {
|
|
134
|
+
if (section === 'markets') return {
|
|
135
|
+
market: marketFor(item) || null,
|
|
136
|
+
name: field(item, ['name']),
|
|
137
|
+
price: field(item, ['price', 'currentPrice']),
|
|
138
|
+
markPrice: field(item, ['markPrice', 'markPx']),
|
|
139
|
+
oraclePrice: field(item, ['oraclePrice', 'oraclePx']),
|
|
140
|
+
change24hPercent: field(item, ['change24hPercent', 'priceChange24h']),
|
|
141
|
+
volume24h: field(item, ['volume24h', 'dayNtlVlm']),
|
|
142
|
+
openInterest: field(item, ['openInterest']),
|
|
143
|
+
fundingRate: field(item, ['fundingRate', 'funding']),
|
|
144
|
+
maxLeverage: field(item, ['maxLeverage']),
|
|
145
|
+
};
|
|
146
|
+
if (section === 'positions') return {
|
|
147
|
+
market: marketFor(item) || null,
|
|
148
|
+
side: sideFor(item),
|
|
149
|
+
size: field(item, ['size', 'szi']),
|
|
150
|
+
entryPrice: field(item, ['entryPrice', 'entryPx']),
|
|
151
|
+
positionValue: field(item, ['positionValue']),
|
|
152
|
+
leverage: leverageFor(item),
|
|
153
|
+
liquidationPrice: field(item, ['liquidationPrice', 'liquidationPx']),
|
|
154
|
+
marginUsed: field(item, ['marginUsed']),
|
|
155
|
+
unrealizedPnl: field(item, ['unrealizedPnl']),
|
|
156
|
+
returnOnEquity: field(item, ['returnOnEquity']),
|
|
157
|
+
takeProfitPrice: field(item, ['takeProfitPrice']),
|
|
158
|
+
stopLossPrice: field(item, ['stopLossPrice']),
|
|
159
|
+
};
|
|
160
|
+
if (section === 'orders') return {
|
|
161
|
+
orderId: field(item, ['orderId', 'id', 'oid']),
|
|
162
|
+
market: marketFor(item) || null,
|
|
163
|
+
side: sideFor(item),
|
|
164
|
+
size: field(item, ['size', 'sz', 'szi']),
|
|
165
|
+
price: field(item, ['price', 'limitPx', 'px']),
|
|
166
|
+
type: field(item, ['orderType', 'type']),
|
|
167
|
+
status: field(item, ['status']),
|
|
168
|
+
timestamp: field(item, ['timestamp', 'time']),
|
|
169
|
+
reduceOnly: field(item, ['reduceOnly']),
|
|
170
|
+
isTrigger: field(item, ['isTrigger']),
|
|
171
|
+
triggerPrice: field(item, ['triggerPrice', 'triggerPx']),
|
|
172
|
+
triggerOrderType: field(item, ['triggerOrderType']),
|
|
173
|
+
takeProfitPrice: field(item, ['takeProfitPrice']),
|
|
174
|
+
stopLossPrice: field(item, ['stopLossPrice']),
|
|
175
|
+
};
|
|
176
|
+
return {
|
|
177
|
+
type: field(item, ['type', 'activityType']),
|
|
178
|
+
status: field(item, ['status']),
|
|
179
|
+
asset: field(item, ['asset', 'token', 'currency']),
|
|
180
|
+
market: marketFor(item) || null,
|
|
181
|
+
amount: field(item, ['amount', 'value']),
|
|
182
|
+
fee: field(item, ['fee']),
|
|
183
|
+
timestamp: field(item, ['timestamp', 'time']),
|
|
184
|
+
transactionHash: field(item, ['transactionHash', 'txHash', 'hash']),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
function transport(section, value) {
|
|
188
|
+
if (section === 'account') {
|
|
189
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) return { value };
|
|
190
|
+
const allowed = ${JSON.stringify([
|
|
191
|
+
'accountValue', 'totalBalance', 'balance', 'spendableBalance',
|
|
192
|
+
'withdrawableBalance', 'availableBalance', 'availableToTradeBalance',
|
|
193
|
+
'marginUsed', 'maintenanceMargin', 'unrealizedPnl', 'returnOnEquity',
|
|
194
|
+
])};
|
|
195
|
+
return { value: Object.fromEntries(allowed.map((name) => [name, field(value, [name])]).filter(([, item]) => item !== null)) };
|
|
196
|
+
}
|
|
197
|
+
if (!Array.isArray(value)) return { value };
|
|
198
|
+
if (section === 'watchlist') {
|
|
199
|
+
const items = value.map((item) => typeof item === 'string' ? item : marketFor(item)).filter(Boolean);
|
|
200
|
+
const selected = markets.length === 0 ? items : items.filter((item) => markets.includes(String(item).replace(/-PERP$/iu, '').toUpperCase()));
|
|
201
|
+
const bounded = selected.slice(0, maxItems).map((item) => String(item).replace(/-PERP$/iu, '').toUpperCase());
|
|
202
|
+
return { value: bounded, totalCount: value.length, matchingCount: selected.length, truncated: bounded.length < selected.length, transportBound: true };
|
|
203
|
+
}
|
|
204
|
+
const selected = markets.length === 0 ? value : value.filter((item) => markets.includes(marketFor(item)));
|
|
205
|
+
const limit = section === 'history' ? historyLimit : maxItems;
|
|
206
|
+
const bounded = selected.slice(0, limit).map((item) => normalize(section, item));
|
|
207
|
+
return { value: bounded, totalCount: value.length, matchingCount: selected.length, truncated: bounded.length < selected.length, transportBound: true };
|
|
208
|
+
}
|
|
209
|
+
async function read(section) {
|
|
210
|
+
const [method, configuredArgs] = methods[section];
|
|
211
|
+
const args = section === 'history' ? [historyParams] : configuredArgs;
|
|
212
|
+
let timeoutId;
|
|
213
|
+
try {
|
|
214
|
+
const value = await Promise.race([
|
|
215
|
+
request(method, args),
|
|
216
|
+
new Promise((_, reject) => {
|
|
217
|
+
timeoutId = setTimeout(
|
|
218
|
+
() => reject(new Error(method + ' timed out after ' + timeoutMs + 'ms')),
|
|
219
|
+
timeoutMs,
|
|
220
|
+
);
|
|
221
|
+
}),
|
|
222
|
+
]);
|
|
223
|
+
return [section, { ok: true, method, ...transport(section, value) }];
|
|
224
|
+
} catch (error) {
|
|
225
|
+
return [section, {
|
|
226
|
+
ok: false,
|
|
227
|
+
method,
|
|
228
|
+
error: error instanceof Error ? error.message : String(error),
|
|
229
|
+
}];
|
|
230
|
+
} finally {
|
|
231
|
+
clearTimeout(timeoutId);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return Object.fromEntries(await Promise.all(${JSON.stringify(options.include)}.map(read)));
|
|
235
|
+
})()`;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function field(record, names) {
|
|
239
|
+
for (const name of names) {
|
|
240
|
+
const value = record?.[name];
|
|
241
|
+
if (value === null || ['string', 'number', 'boolean'].includes(typeof value)) return value;
|
|
242
|
+
}
|
|
243
|
+
return null;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function normalizedSide(item) {
|
|
247
|
+
const side = String(field(item, ['side', 'direction']) ?? '').toLowerCase();
|
|
248
|
+
if (side === 'buy' || side === 'b' || side === 'long') return 'long';
|
|
249
|
+
if (side === 'sell' || side === 'a' || side === 's' || side === 'short') return 'short';
|
|
250
|
+
return side || null;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function normalizedLeverage(item) {
|
|
254
|
+
const leverage = item?.leverage;
|
|
255
|
+
if (leverage === null || ['string', 'number'].includes(typeof leverage)) return leverage;
|
|
256
|
+
if (!leverage || typeof leverage !== 'object' || Array.isArray(leverage)) return null;
|
|
257
|
+
return {
|
|
258
|
+
type: field(leverage, ['type']),
|
|
259
|
+
value: field(leverage, ['value']),
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function marketForItem(item) {
|
|
264
|
+
const value = field(item, ['symbol', 'coin', 'market', 'asset']);
|
|
265
|
+
return typeof value === 'string' && value.length > 0 ? normalizeMarketSymbol(value) : '';
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function normalizeAccount(account) {
|
|
269
|
+
const allowed = [
|
|
270
|
+
'accountValue',
|
|
271
|
+
'totalBalance',
|
|
272
|
+
'balance',
|
|
273
|
+
'spendableBalance',
|
|
274
|
+
'withdrawableBalance',
|
|
275
|
+
'availableBalance',
|
|
276
|
+
'availableToTradeBalance',
|
|
277
|
+
'marginUsed',
|
|
278
|
+
'maintenanceMargin',
|
|
279
|
+
'unrealizedPnl',
|
|
280
|
+
'returnOnEquity',
|
|
281
|
+
];
|
|
282
|
+
return Object.fromEntries(
|
|
283
|
+
allowed
|
|
284
|
+
.map((name) => [name, field(account, [name])])
|
|
285
|
+
.filter(([, value]) => value !== null),
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function normalizeMarket(item) {
|
|
290
|
+
return {
|
|
291
|
+
market: marketForItem(item) || null,
|
|
292
|
+
name: field(item, ['name']),
|
|
293
|
+
price: field(item, ['price', 'currentPrice']),
|
|
294
|
+
markPrice: field(item, ['markPrice', 'markPx']),
|
|
295
|
+
oraclePrice: field(item, ['oraclePrice', 'oraclePx']),
|
|
296
|
+
change24hPercent: field(item, ['change24hPercent', 'priceChange24h']),
|
|
297
|
+
volume24h: field(item, ['volume24h', 'dayNtlVlm']),
|
|
298
|
+
openInterest: field(item, ['openInterest']),
|
|
299
|
+
fundingRate: field(item, ['fundingRate', 'funding']),
|
|
300
|
+
maxLeverage: field(item, ['maxLeverage']),
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function normalizePosition(item) {
|
|
305
|
+
return {
|
|
306
|
+
market: marketForItem(item) || null,
|
|
307
|
+
side: normalizedSide(item),
|
|
308
|
+
size: field(item, ['size', 'szi']),
|
|
309
|
+
entryPrice: field(item, ['entryPrice', 'entryPx']),
|
|
310
|
+
positionValue: field(item, ['positionValue']),
|
|
311
|
+
leverage: normalizedLeverage(item),
|
|
312
|
+
liquidationPrice: field(item, ['liquidationPrice', 'liquidationPx']),
|
|
313
|
+
marginUsed: field(item, ['marginUsed']),
|
|
314
|
+
unrealizedPnl: field(item, ['unrealizedPnl']),
|
|
315
|
+
returnOnEquity: field(item, ['returnOnEquity']),
|
|
316
|
+
takeProfitPrice: field(item, ['takeProfitPrice']),
|
|
317
|
+
stopLossPrice: field(item, ['stopLossPrice']),
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function normalizeOrder(item) {
|
|
322
|
+
return {
|
|
323
|
+
orderId: field(item, ['orderId', 'id', 'oid']),
|
|
324
|
+
market: marketForItem(item) || null,
|
|
325
|
+
side: normalizedSide(item),
|
|
326
|
+
size: field(item, ['size', 'sz', 'szi']),
|
|
327
|
+
price: field(item, ['price', 'limitPx', 'px']),
|
|
328
|
+
type: field(item, ['orderType', 'type']),
|
|
329
|
+
status: field(item, ['status']),
|
|
330
|
+
timestamp: field(item, ['timestamp', 'time']),
|
|
331
|
+
reduceOnly: field(item, ['reduceOnly']),
|
|
332
|
+
isTrigger: field(item, ['isTrigger']),
|
|
333
|
+
triggerPrice: field(item, ['triggerPrice', 'triggerPx']),
|
|
334
|
+
triggerOrderType: field(item, ['triggerOrderType']),
|
|
335
|
+
takeProfitPrice: field(item, ['takeProfitPrice']),
|
|
336
|
+
stopLossPrice: field(item, ['stopLossPrice']),
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function normalizeHistoryItem(item) {
|
|
341
|
+
return {
|
|
342
|
+
type: field(item, ['type', 'activityType']),
|
|
343
|
+
status: field(item, ['status']),
|
|
344
|
+
asset: field(item, ['asset', 'token', 'currency']),
|
|
345
|
+
market: marketForItem(item) || null,
|
|
346
|
+
amount: field(item, ['amount', 'value']),
|
|
347
|
+
fee: field(item, ['fee']),
|
|
348
|
+
timestamp: field(item, ['timestamp', 'time']),
|
|
349
|
+
transactionHash: field(item, ['transactionHash', 'txHash', 'hash']),
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function selectAndBound(items, options, normalize, limit = options.maxItems, result = {}) {
|
|
354
|
+
const selected = result.transportBound === true || options.markets.length === 0
|
|
355
|
+
? items
|
|
356
|
+
: items.filter((item) => options.markets.includes(marketForItem(item)));
|
|
357
|
+
const bounded = selected.slice(0, Math.min(limit, options.maxItems));
|
|
358
|
+
return {
|
|
359
|
+
totalCount: result.transportBound === true ? result.totalCount : items.length,
|
|
360
|
+
matchingCount: result.transportBound === true ? result.matchingCount : selected.length,
|
|
361
|
+
returnedCount: bounded.length,
|
|
362
|
+
truncated: result.transportBound === true ? result.truncated === true : bounded.length < selected.length,
|
|
363
|
+
items: bounded.map(normalize),
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function requireObjectSection(section, result) {
|
|
368
|
+
if (!result?.ok) {
|
|
369
|
+
throw new Error(`Requested Perps snapshot section ${section} is unavailable: ${result?.error ?? 'unknown error'}.`);
|
|
370
|
+
}
|
|
371
|
+
if (!result.value || typeof result.value !== 'object' || Array.isArray(result.value)) {
|
|
372
|
+
throw new Error(`Requested Perps snapshot section ${section} is unavailable: ${result.method} returned a non-object result.`);
|
|
373
|
+
}
|
|
374
|
+
return result.value;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function requireArraySection(section, result) {
|
|
378
|
+
if (!result?.ok) {
|
|
379
|
+
throw new Error(`Requested Perps snapshot section ${section} is unavailable: ${result?.error ?? 'unknown error'}.`);
|
|
380
|
+
}
|
|
381
|
+
if (!Array.isArray(result.value)) {
|
|
382
|
+
throw new Error(`Requested Perps snapshot section ${section} is unavailable: ${result.method} returned a non-array result.`);
|
|
383
|
+
}
|
|
384
|
+
return result.value;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
export function normalizeSnapshotResult(raw, options) {
|
|
388
|
+
const sections = {};
|
|
389
|
+
for (const section of options.include) {
|
|
390
|
+
const result = raw?.[section];
|
|
391
|
+
if (section === 'account') {
|
|
392
|
+
sections.account = {
|
|
393
|
+
source: result?.method ?? 'perpsGetAccountState',
|
|
394
|
+
state: normalizeAccount(requireObjectSection(section, result)),
|
|
395
|
+
};
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
if (section === 'watchlist') {
|
|
399
|
+
const values = requireArraySection(section, result);
|
|
400
|
+
const items = values
|
|
401
|
+
.map((item) => typeof item === 'string' ? item : marketForItem(item))
|
|
402
|
+
.filter((item) => typeof item === 'string' && item.length > 0);
|
|
403
|
+
const selected = result.transportBound === true || options.markets.length === 0
|
|
404
|
+
? items
|
|
405
|
+
: items.filter((item) => options.markets.includes(normalizeMarketSymbol(item)));
|
|
406
|
+
const bounded = selected
|
|
407
|
+
.slice(0, options.maxItems)
|
|
408
|
+
.map((item) => typeof item === 'string' && item.length > 0 ? normalizeMarketSymbol(item) : '')
|
|
409
|
+
.filter(Boolean);
|
|
410
|
+
sections.watchlist = {
|
|
411
|
+
source: result.method,
|
|
412
|
+
totalCount: result.transportBound === true ? result.totalCount : values.length,
|
|
413
|
+
matchingCount: result.transportBound === true ? result.matchingCount : selected.length,
|
|
414
|
+
returnedCount: bounded.length,
|
|
415
|
+
truncated: result.transportBound === true ? result.truncated === true : bounded.length < selected.length,
|
|
416
|
+
markets: bounded,
|
|
417
|
+
};
|
|
418
|
+
continue;
|
|
419
|
+
}
|
|
420
|
+
const values = requireArraySection(section, result);
|
|
421
|
+
const normalizer = {
|
|
422
|
+
markets: normalizeMarket,
|
|
423
|
+
positions: normalizePosition,
|
|
424
|
+
orders: normalizeOrder,
|
|
425
|
+
history: normalizeHistoryItem,
|
|
426
|
+
}[section];
|
|
427
|
+
sections[section] = {
|
|
428
|
+
source: result.method,
|
|
429
|
+
...selectAndBound(
|
|
430
|
+
values,
|
|
431
|
+
options,
|
|
432
|
+
normalizer,
|
|
433
|
+
section === 'history' ? options.history.limit : options.maxItems,
|
|
434
|
+
result,
|
|
435
|
+
),
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
return sections;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
export async function readSnapshot(input) {
|
|
442
|
+
const options = resolveSnapshotOptions(input);
|
|
443
|
+
return withExtensionPage(input, async (page) => {
|
|
444
|
+
const raw = await page.evaluate(snapshotRequestExpression(options));
|
|
445
|
+
return {
|
|
446
|
+
action: input.action,
|
|
447
|
+
source: 'extension-stateHooks-background',
|
|
448
|
+
include: options.include,
|
|
449
|
+
markets: options.markets,
|
|
450
|
+
historyQuery: options.include.includes('history')
|
|
451
|
+
? {
|
|
452
|
+
startTime: options.history.startTime,
|
|
453
|
+
endTime: options.history.endTime,
|
|
454
|
+
accountScoped: options.history.accountId !== undefined,
|
|
455
|
+
}
|
|
456
|
+
: null,
|
|
457
|
+
sections: normalizeSnapshotResult(raw, options),
|
|
458
|
+
redacted: true,
|
|
459
|
+
proofPath: 'background-perps-snapshot',
|
|
460
|
+
};
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function isDirectRun() {
|
|
465
|
+
return process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
if (isDirectRun()) {
|
|
469
|
+
runAdapter(readSnapshot);
|
|
470
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
|
+
import {
|
|
3
|
+
normalizeVisiblePerpsState,
|
|
4
|
+
resolveVisibleStateOptions,
|
|
5
|
+
} from '../../shared/perps/visible-state.mjs';
|
|
6
|
+
|
|
7
|
+
runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
8
|
+
const options = resolveVisibleStateOptions(input.node);
|
|
9
|
+
const raw = await page.evaluate(`(() => {
|
|
10
|
+
const includeOffscreen = ${JSON.stringify(options.includeOffscreen)};
|
|
11
|
+
const maximum = ${JSON.stringify(options.maxItems)};
|
|
12
|
+
const relevant = /^(?:perps-|explore-markets-|market-list-view|market-row-|filter-select-button|sort-dropdown-button|search-input|position-card-|order-card-|transaction-card-|bottom-nav-perps|direction-|order-type-|amount-|balance-percent-|leverage-|auto-close|submit-order-button|limit-price-|tp-|sl-)/u;
|
|
13
|
+
const items = [];
|
|
14
|
+
const offscreenItems = [];
|
|
15
|
+
const clippedTestIds = [];
|
|
16
|
+
let matched = 0;
|
|
17
|
+
for (const element of document.querySelectorAll('[data-testid]')) {
|
|
18
|
+
const testId = element.getAttribute('data-testid');
|
|
19
|
+
if (!testId || !relevant.test(testId)) continue;
|
|
20
|
+
const style = getComputedStyle(element);
|
|
21
|
+
const rect = element.getBoundingClientRect();
|
|
22
|
+
const rendered = style.display !== 'none' && style.visibility !== 'hidden' && rect.width > 0 && rect.height > 0;
|
|
23
|
+
if (!rendered) continue;
|
|
24
|
+
matched += 1;
|
|
25
|
+
const visible = rect.bottom > 0 && rect.right > 0 && rect.top < innerHeight && rect.left < innerWidth;
|
|
26
|
+
if (rect.bottom > 0 && rect.top < innerHeight && (rect.left < -1 || rect.right > innerWidth + 1)) {
|
|
27
|
+
clippedTestIds.push(testId);
|
|
28
|
+
}
|
|
29
|
+
if (!visible && !includeOffscreen) continue;
|
|
30
|
+
if (items.length + offscreenItems.length >= maximum) break;
|
|
31
|
+
const label = String(element.getAttribute('aria-label') || element.innerText || element.getAttribute('value') || '')
|
|
32
|
+
.replace(/\\s+/gu, ' ')
|
|
33
|
+
.trim()
|
|
34
|
+
.slice(0, 500);
|
|
35
|
+
const item = {
|
|
36
|
+
testId,
|
|
37
|
+
label,
|
|
38
|
+
role: element.getAttribute('role') || element.tagName.toLowerCase(),
|
|
39
|
+
enabled: !element.disabled && element.getAttribute('aria-disabled') !== 'true',
|
|
40
|
+
visible,
|
|
41
|
+
};
|
|
42
|
+
(visible ? items : offscreenItems).push(item);
|
|
43
|
+
}
|
|
44
|
+
const field = (testId) => {
|
|
45
|
+
const element = document.querySelector('[data-testid="' + testId + '"]');
|
|
46
|
+
const input = element?.matches('input') ? element : element?.querySelector('input');
|
|
47
|
+
if (!element) return null;
|
|
48
|
+
return {
|
|
49
|
+
testId,
|
|
50
|
+
label: String(element.innerText || element.getAttribute('aria-label') || '').replace(/\\s+/gu, ' ').trim().slice(0, 500),
|
|
51
|
+
value: input && 'value' in input ? String(input.value) : null,
|
|
52
|
+
checked: input?.checked === true,
|
|
53
|
+
enabled: !element.disabled && element.getAttribute('aria-disabled') !== 'true',
|
|
54
|
+
pressed: element.getAttribute('aria-pressed'),
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
const submit = field('submit-order-button');
|
|
58
|
+
const direction = submit?.label.match(/\\b(long|short)\\b/iu)?.[1]?.toLowerCase() ||
|
|
59
|
+
String(location.hash).match(/[?&]direction=(long|short)(?:&|$)/u)?.[1] ||
|
|
60
|
+
null;
|
|
61
|
+
return {
|
|
62
|
+
route: location.hash,
|
|
63
|
+
title: document.title,
|
|
64
|
+
items,
|
|
65
|
+
offscreenItems,
|
|
66
|
+
truncated: matched > items.length + offscreenItems.length,
|
|
67
|
+
orderEntry: document.querySelector('[data-testid="perps-order-entry-page"]') ? {
|
|
68
|
+
symbol: field('perps-order-entry-asset-symbol')?.label || null,
|
|
69
|
+
direction,
|
|
70
|
+
orderType: field('order-type-limit')?.pressed === 'true' ? 'limit' : 'market',
|
|
71
|
+
amount: field('amount-input-field')?.value,
|
|
72
|
+
percent: field('balance-percent-input')?.value,
|
|
73
|
+
leverage: field('leverage-input')?.value,
|
|
74
|
+
autoClose: field('auto-close-toggle')?.checked === true,
|
|
75
|
+
takeProfitPrice: field('tp-price-input')?.value,
|
|
76
|
+
stopLossPrice: field('sl-price-input')?.value,
|
|
77
|
+
submit,
|
|
78
|
+
limitPrice: field('limit-price-input')?.value,
|
|
79
|
+
validation: [...document.querySelectorAll('[data-testid$="validation-error"], [role="alert"]')]
|
|
80
|
+
.map((element) => String(element.innerText || '').replace(/\\s+/gu, ' ').trim())
|
|
81
|
+
.filter(Boolean)
|
|
82
|
+
.slice(0, 20),
|
|
83
|
+
} : null,
|
|
84
|
+
layout: {
|
|
85
|
+
viewportWidth: innerWidth,
|
|
86
|
+
viewportHeight: innerHeight,
|
|
87
|
+
contentWidth: Math.max(document.documentElement?.scrollWidth || 0, document.body?.scrollWidth || 0),
|
|
88
|
+
horizontalOverflow: Math.max(document.documentElement?.scrollWidth || 0, document.body?.scrollWidth || 0) > innerWidth + 1,
|
|
89
|
+
clippedTestIds: [...new Set(clippedTestIds)].slice(0, 100),
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
})()`);
|
|
93
|
+
return {
|
|
94
|
+
action: input.action,
|
|
95
|
+
...normalizeVisiblePerpsState(raw, options, 'extension'),
|
|
96
|
+
proofPath: 'visible-dom-accessibility',
|
|
97
|
+
};
|
|
98
|
+
}));
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { dataTestId, runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
|
+
|
|
3
|
+
const FILTERS = new Map([
|
|
4
|
+
['trades', 'trade'],
|
|
5
|
+
['orders', 'order'],
|
|
6
|
+
['funding', 'funding'],
|
|
7
|
+
['deposits', 'deposit'],
|
|
8
|
+
]);
|
|
9
|
+
|
|
10
|
+
runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
11
|
+
const filter = String(input.node?.filter ?? '').trim().toLowerCase();
|
|
12
|
+
const option = FILTERS.get(filter);
|
|
13
|
+
if (!option) {
|
|
14
|
+
throw new Error(`metamask.perps.select_activity_filter received unsupported filter ${JSON.stringify(filter)}.`);
|
|
15
|
+
}
|
|
16
|
+
await page.click(dataTestId('perps-activity-filter-button'));
|
|
17
|
+
await page.click(dataTestId(`perps-activity-filter-option-${option}`));
|
|
18
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
19
|
+
const observed = await page.evaluate(`(() => {
|
|
20
|
+
const button = document.querySelector('[data-testid="perps-activity-filter-button"]');
|
|
21
|
+
const entries = [...document.querySelectorAll('[data-testid^="transaction-card-"]')]
|
|
22
|
+
.slice(0, 200)
|
|
23
|
+
.map((element) => String(element.innerText || '').replace(/\\s+/gu, ' ').trim().slice(0, 500));
|
|
24
|
+
return {
|
|
25
|
+
route: String(location.hash),
|
|
26
|
+
selected: String(button?.innerText || '').trim(),
|
|
27
|
+
entries,
|
|
28
|
+
empty: String(document.body?.innerText || '').replace(/\\s+/gu, ' ').trim().slice(0, 2_000),
|
|
29
|
+
};
|
|
30
|
+
})()`);
|
|
31
|
+
if (observed.route !== '#/perps/activity') throw new Error('Perps activity filter left the activity route.');
|
|
32
|
+
if (observed.selected.toLowerCase() !== filter) {
|
|
33
|
+
throw new Error(`Expected Perps activity filter ${filter}, but observed ${observed.selected || 'none'}.`);
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
action: input.action,
|
|
37
|
+
filter,
|
|
38
|
+
selected: observed.selected,
|
|
39
|
+
count: observed.entries.length,
|
|
40
|
+
entries: observed.entries,
|
|
41
|
+
empty: observed.entries.length === 0 ? observed.empty : undefined,
|
|
42
|
+
proofPath: 'trusted-pointer-visible-ui',
|
|
43
|
+
};
|
|
44
|
+
}));
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { dataTestId, runAdapter, withExtensionPage } from '../platform/cdp.mjs';
|
|
2
|
+
import {
|
|
3
|
+
normalizeVisiblePerpsState,
|
|
4
|
+
resolveVisibleStateOptions,
|
|
5
|
+
} from '../../shared/perps/visible-state.mjs';
|
|
6
|
+
|
|
7
|
+
const FILTERS = new Map([
|
|
8
|
+
['all', 'all'],
|
|
9
|
+
['crypto', 'crypto'],
|
|
10
|
+
['stocks', 'stock'],
|
|
11
|
+
['pre-ipo', 'pre-ipo'],
|
|
12
|
+
['index', 'index'],
|
|
13
|
+
['etf', 'etf'],
|
|
14
|
+
['commodities', 'commodity'],
|
|
15
|
+
['forex', 'forex'],
|
|
16
|
+
['new', 'new'],
|
|
17
|
+
]);
|
|
18
|
+
|
|
19
|
+
runAdapter(async (input) => withExtensionPage(input, async (page) => {
|
|
20
|
+
const filter = String(input.node?.filter ?? '').trim().toLowerCase();
|
|
21
|
+
const option = FILTERS.get(filter);
|
|
22
|
+
if (!option) {
|
|
23
|
+
throw new Error(`metamask.perps.select_market_filter received unsupported filter ${JSON.stringify(filter)}.`);
|
|
24
|
+
}
|
|
25
|
+
const options = resolveVisibleStateOptions({
|
|
26
|
+
surface: 'market-list',
|
|
27
|
+
include_offscreen: true,
|
|
28
|
+
max_items: input.node?.max_items ?? 100,
|
|
29
|
+
minimum_market_count: input.node?.minimum_market_count ?? 1,
|
|
30
|
+
maximum_market_count: input.node?.maximum_market_count ?? 200,
|
|
31
|
+
});
|
|
32
|
+
await page.click(dataTestId('filter-select-button'));
|
|
33
|
+
const optionSelector = dataTestId(`filter-select-option-${option}`);
|
|
34
|
+
const found = await page.evaluate(`(() => Boolean(document.querySelector(${JSON.stringify(optionSelector)})))()`);
|
|
35
|
+
if (!found) {
|
|
36
|
+
throw new Error(`Perps market filter ${JSON.stringify(filter)} is not available in this release.`);
|
|
37
|
+
}
|
|
38
|
+
await page.click(optionSelector);
|
|
39
|
+
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
40
|
+
const raw = await page.evaluate(`(() => {
|
|
41
|
+
const maximum = ${JSON.stringify(options.maxItems)};
|
|
42
|
+
const items = [];
|
|
43
|
+
let matched = 0;
|
|
44
|
+
for (const element of document.querySelectorAll('[data-testid]')) {
|
|
45
|
+
const testId = element.getAttribute('data-testid');
|
|
46
|
+
if (!testId || !/^(?:market-list-view|market-row-|perps-token-logo-|filter-select-button)/u.test(testId)) continue;
|
|
47
|
+
const style = getComputedStyle(element);
|
|
48
|
+
const rect = element.getBoundingClientRect();
|
|
49
|
+
if (style.display === 'none' || style.visibility === 'hidden' || rect.width <= 0 || rect.height <= 0) continue;
|
|
50
|
+
matched += 1;
|
|
51
|
+
if (items.length >= maximum) break;
|
|
52
|
+
items.push({
|
|
53
|
+
testId,
|
|
54
|
+
label: String(element.getAttribute('aria-label') || element.innerText || element.getAttribute('value') || '')
|
|
55
|
+
.replace(/\\s+/gu, ' ')
|
|
56
|
+
.trim()
|
|
57
|
+
.slice(0, 500),
|
|
58
|
+
role: element.getAttribute('role') || element.tagName.toLowerCase(),
|
|
59
|
+
enabled: !element.disabled && element.getAttribute('aria-disabled') !== 'true',
|
|
60
|
+
visible: rect.bottom > 0 && rect.right > 0 && rect.top < innerHeight && rect.left < innerWidth,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return { route: location.hash, title: document.title, items, offscreenItems: [], truncated: matched > items.length };
|
|
64
|
+
})()`);
|
|
65
|
+
return {
|
|
66
|
+
action: input.action,
|
|
67
|
+
filter,
|
|
68
|
+
...normalizeVisiblePerpsState(raw, options, 'extension'),
|
|
69
|
+
proofPath: 'trusted-pointer-visible-ui',
|
|
70
|
+
};
|
|
71
|
+
}));
|