@deeeed/metamask-harness 0.94.2 → 0.94.3

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 CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.94.3 - 2026-10-25
6
+
7
+ ### Fixed
8
+
9
+ - Extension testnet `close_visible_position` with `cleanup_only` binds one position by market and exact visible size. Fresh and resumed cleanup prove the accepted full close and zero target-market positions/orders without reconciling unrelated account-balance changes; the result names the cleaned market. Same-market orders still block, and mainnet keeps its global cleanup and balance guards.
10
+
5
11
  ## 0.94.2 - 2026-10-24
6
12
 
7
13
  ### Fixed
@@ -208,9 +208,10 @@ async function prepareCloseModal(page, partialAmount) {
208
208
  return confirmation;
209
209
  }
210
210
 
211
- async function proveGlobalCleanup(page, balanceBeforeBaseUnits, releaseBaseUnits, toleranceBaseUnits, timeoutMs = 20_000) {
211
+ async function proveCleanup(page, balanceBeforeBaseUnits, releaseBaseUnits, toleranceBaseUnits, market = null, timeoutMs = 20_000) {
212
212
  await page.navigateHash('#/perps-home');
213
- const state = await waitForStableGlobalPerpsHome(page, { timeoutMs, requireZero: true });
213
+ const state = await waitForStableGlobalPerpsHome(page, { timeoutMs, requireZero: true, market });
214
+ if (market) return { market, positionCount: 0, orderCount: 0 };
214
215
  const expectedBalanceAfterBaseUnits = `${BigInt(balanceBeforeBaseUnits) + BigInt(releaseBaseUnits ?? '0')}`;
215
216
  const balanceDeltaBaseUnits = absoluteDelta(state.balanceBaseUnits, expectedBalanceAfterBaseUnits);
216
217
  if (BigInt(balanceDeltaBaseUnits) > BigInt(toleranceBaseUnits)) {
@@ -293,6 +294,7 @@ export async function closeVisiblePosition(input) {
293
294
  roundingToleranceBaseUnits,
294
295
  ...(cleanupOnly ? { positionSize: fundingPositionSize, cleanupOnly: true } : {}),
295
296
  });
297
+ const cleanupMarket = cleanupOnly && identity.network === 'testnet' ? market : null;
296
298
  const closeBudget = trustedExternalMutationBudget(input, 'perps-close-position');
297
299
  const existingReceipt = await readExternalMutationReceipt(input, identity);
298
300
  if (existingReceipt) {
@@ -334,11 +336,12 @@ export async function closeVisiblePosition(input) {
334
336
  if (releaseBaseUnits != null && !/^-?(?:0|[1-9][0-9]{0,29})$/u.test(String(releaseBaseUnits))) {
335
337
  throw new Error('Resumed close receipt has an invalid close release amount.');
336
338
  }
337
- const cleanup = await proveGlobalCleanup(
339
+ const cleanup = await proveCleanup(
338
340
  page,
339
341
  balanceBeforeBaseUnits,
340
342
  releaseBaseUnits,
341
343
  balanceToleranceBaseUnits,
344
+ cleanupMarket,
342
345
  );
343
346
  return {
344
347
  success: true,
@@ -374,8 +377,9 @@ export async function closeVisiblePosition(input) {
374
377
  let cleanupHomeBefore = null;
375
378
  if (cleanupOnly) {
376
379
  await page.navigateHash('#/perps-home');
377
- cleanupHomeBefore = await waitForStableGlobalPerpsHome(page, { timeoutMs: 20_000, requireZero: false });
380
+ cleanupHomeBefore = await waitForStableGlobalPerpsHome(page, { timeoutMs: 20_000, requireZero: false, market: cleanupMarket });
378
381
  if (cleanupHomeBefore.positionCount !== 1 || cleanupHomeBefore.orderCount !== 0) {
382
+ if (cleanupMarket) throw new Error('Cleanup-only close requires exactly one position and zero orders in the target market.');
379
383
  throw new Error('Cleanup-only close requires exactly one primary position and globally zero orders.');
380
384
  }
381
385
  }
@@ -451,7 +455,7 @@ export async function closeVisiblePosition(input) {
451
455
  dispatch: async () => {
452
456
  let dispatchTarget;
453
457
  try {
454
- await requireCurrentVisibleFundingBalance(input, page, timeoutMs, fundingBalance.observedBaseUnits);
458
+ await requireCurrentVisibleFundingBalance(input, page, timeoutMs, cleanupMarket ? undefined : fundingBalance.observedBaseUnits);
455
459
  assertTrustedExternalMutationTarget(input, page, identity);
456
460
  const current = await readVisibleClosePosition(page, market);
457
461
  const currentPosition = parsePositionDetail({ pair: `${market}-USD`, ...current });
@@ -522,11 +526,12 @@ export async function closeVisiblePosition(input) {
522
526
  `Boolean(document.querySelector('[data-testid="perps-position-size-value"]'))`,
523
527
  );
524
528
  if (exactAbsent) {
525
- const cleanup = await proveGlobalCleanup(
529
+ const cleanup = await proveCleanup(
526
530
  page,
527
531
  balanceBeforeBaseUnits,
528
532
  closeReleaseBaseUnits,
529
533
  balanceToleranceBaseUnits,
534
+ cleanupMarket,
530
535
  );
531
536
  return {
532
537
  success: true,
@@ -1,5 +1,5 @@
1
1
  // Parse and match visible Perps position and order identities using USD base-unit tolerances.
2
- // Also wait for a stable, loaded Perps home view, optionally requiring zero positions and orders.
2
+ // Also wait for a stable Perps home view, with optional global or market-scoped cleanup checks.
3
3
  const INTEGER = /^(0|[1-9][0-9]{0,29})$/u;
4
4
 
5
5
  export function parseUsdBaseUnits(value) {
@@ -119,18 +119,21 @@ export async function waitForStableGlobalPerpsHome(page, options = {}) {
119
119
  const timeoutMs = Number(options.timeoutMs ?? 20_000);
120
120
  const stableMs = Number(options.stableMs ?? 1_000);
121
121
  const requireZero = options.requireZero !== false;
122
+ const market = options.market ?? null;
122
123
  const deadline = Date.now() + timeoutMs;
123
124
  let stableSince = null;
124
125
  let last = null;
125
126
  while (Date.now() <= deadline) {
126
127
  last = await page.evaluate(`(() => {
128
+ const market = ${JSON.stringify(market)};
127
129
  const visible = (element) => {
128
130
  if (!element) return false;
129
131
  const rect = element.getBoundingClientRect();
130
132
  const style = getComputedStyle(element);
131
133
  return rect.width > 0 && rect.height > 0 && style.display !== 'none' && style.visibility !== 'hidden';
132
134
  };
133
- const visibleCount = (selector) => [...document.querySelectorAll(selector)].filter(visible).length;
135
+ const visibleCount = (selector, childSelector = null) => [...document.querySelectorAll(selector)]
136
+ .filter(element => visible(element) && (!childSelector || element.querySelector(childSelector))).length;
134
137
  const balance = document.querySelector('[data-testid="perps-balance-actions-available-value"]')
135
138
  ?? document.querySelector('[data-testid="perps-balance-dropdown-balance"]');
136
139
  return {
@@ -139,8 +142,10 @@ export async function waitForStableGlobalPerpsHome(page, options = {}) {
139
142
  ?? document.querySelector('[data-testid="perps-view"]')),
140
143
  loading: visibleCount('[data-testid$="-skeleton"], [data-testid*="loading"]') > 0,
141
144
  marketCount: visibleCount('[data-testid="perps-explore-markets-row"]'),
142
- positionCount: visibleCount('[data-testid^="position-card-"]:not([data-testid^="position-card-roe-"])'),
143
- orderCount: visibleCount('[data-testid^="order-card-"]'),
145
+ positionCount: visibleCount(market ? '[data-testid="position-card-' + market + '" i]'
146
+ : '[data-testid^="position-card-"]:not([data-testid^="position-card-roe-"])'),
147
+ orderCount: visibleCount('[data-testid^="order-card-"]', market
148
+ ? '[data-testid="perps-token-logo-' + market.replace(/:/gu, '-') + '" i]' : null),
144
149
  balance: visible(balance) ? String(balance.innerText || '').replace(/\\s+/gu, ' ').trim().slice(0, 200) : null,
145
150
  };
146
151
  })()`);
@@ -162,6 +167,7 @@ export async function waitForStableGlobalPerpsHome(page, options = {}) {
162
167
  await new Promise((resolve) => setTimeout(resolve, 250));
163
168
  }
164
169
  if (requireZero && last && (last.positionCount !== 0 || last.orderCount !== 0)) {
170
+ if (market) throw new Error(`Loaded Perps Home must have zero positions and orders in ${market} before cleanup.`);
165
171
  throw new Error('Loaded Perps Home must have globally zero positions and orders before external mutation or cleanup.');
166
172
  }
167
173
  throw new Error('Loaded skeleton-free Perps Home did not become stable before timeout.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deeeed/metamask-harness",
3
- "version": "0.94.2",
3
+ "version": "0.94.3",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mm-harness": "bin/mm-harness"