w3wallets 0.10.2 → 1.0.0-beta.10

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/dist/index.js CHANGED
@@ -30,126 +30,697 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ Metamask: () => Metamask,
34
+ PolkadotJS: () => PolkadotJS,
35
+ config: () => config,
36
+ createWallet: () => createWallet,
37
+ debug: () => debug,
38
+ isCachedConfig: () => isCachedConfig,
39
+ metamask: () => metamask,
40
+ polkadotJS: () => polkadotJS,
41
+ prepareWallet: () => prepareWallet,
33
42
  withWallets: () => withWallets
34
43
  });
35
44
  module.exports = __toCommonJS(index_exports);
36
45
 
37
46
  // src/withWallets.ts
47
+ var import_path3 = __toESM(require("path"));
48
+ var import_fs3 = __toESM(require("fs"));
49
+ var import_test2 = require("@playwright/test");
50
+
51
+ // src/cache/types.ts
52
+ function isCachedConfig(config2) {
53
+ return "__cached" in config2 && config2.__cached === true;
54
+ }
55
+
56
+ // src/cache/buildCache.ts
57
+ var import_path2 = __toESM(require("path"));
58
+ var import_fs2 = __toESM(require("fs"));
59
+ var import_test = require("@playwright/test");
60
+
61
+ // src/cache/constants.ts
62
+ var CACHE_DIR = ".w3wallets/cache";
63
+
64
+ // src/core/utils.ts
38
65
  var import_path = __toESM(require("path"));
39
66
  var import_fs = __toESM(require("fs"));
67
+ var import_crypto = __toESM(require("crypto"));
68
+ function sleep(ms) {
69
+ return new Promise((resolve) => setTimeout(resolve, ms));
70
+ }
71
+ function getExtensionId(extensionPath) {
72
+ const absolutePath = import_path.default.resolve(extensionPath);
73
+ const manifestPath = import_path.default.join(absolutePath, "manifest.json");
74
+ const manifest = JSON.parse(import_fs.default.readFileSync(manifestPath, "utf-8"));
75
+ let dataToHash;
76
+ if (manifest.key) {
77
+ dataToHash = Buffer.from(manifest.key, "base64");
78
+ } else {
79
+ dataToHash = Buffer.from(absolutePath);
80
+ }
81
+ const hash = import_crypto.default.createHash("sha256").update(dataToHash).digest();
82
+ const ALPHABET = "abcdefghijklmnop";
83
+ let extensionId = "";
84
+ for (let i = 0; i < 16; i++) {
85
+ const byte = hash[i];
86
+ extensionId += ALPHABET[byte >> 4 & 15];
87
+ extensionId += ALPHABET[byte & 15];
88
+ }
89
+ return extensionId;
90
+ }
40
91
 
41
- // tests/utils/sleep.ts
42
- var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
92
+ // src/timeouts.ts
93
+ var SERVICE_WORKER_TIMEOUT = 3e4;
94
+ var SERVICE_WORKER_POLL_INTERVAL = 500;
95
+ var POPUP_VISIBILITY_TIMEOUT = 2e3;
96
+ var SHIELD_CLOSE_TIMEOUT = 1e3;
97
+ var ARIA_CLOSE_TIMEOUT = 500;
98
+ var POPUP_HIDDEN_TIMEOUT = 3e3;
99
+ var POST_UNLOCK_TIMEOUT = 3e4;
100
+ var NOTIFICATION_CHECK_TIMEOUT = 5e3;
101
+ var POST_CLICK_TIMEOUT = 1e4;
102
+ var BUTTON_OR_POPUP_TIMEOUT = 3e4;
103
+ var LAST_RESORT_CLICK_TIMEOUT = 1e4;
104
+ var LOCK_SCREEN_TIMEOUT = 3e4;
105
+ var MENU_BUTTON_TIMEOUT = 3e4;
106
+ var ONBOARD_VISIBLE_TIMEOUT = 3e4;
107
+ var ROUTE_RETRY_TIMEOUT = 5e3;
108
+ var MAX_ROUTE_ATTEMPTS = 5;
109
+ var MNEMONIC_KEY_DELAY = 5;
110
+ var MNEMONIC_WORD_DELAY = 100;
111
+
112
+ // src/debug.ts
113
+ var isDebug = () => process.env.W3WALLETS_DEBUG === "true";
114
+ function debug(message) {
115
+ if (!isDebug()) return;
116
+ const ts = (/* @__PURE__ */ new Date()).toISOString();
117
+ console.log(`[w3wallets ${ts}] ${message}`);
118
+ }
119
+
120
+ // src/cache/buildCache.ts
121
+ function findCacheDir(walletName) {
122
+ const cacheRoot = import_path2.default.join(process.cwd(), CACHE_DIR);
123
+ if (!import_fs2.default.existsSync(cacheRoot)) return null;
124
+ const entries = import_fs2.default.readdirSync(cacheRoot, { withFileTypes: true });
125
+ for (const entry of entries) {
126
+ if (!entry.isDirectory() || entry.name.startsWith(".")) continue;
127
+ const metaPath = import_path2.default.join(cacheRoot, entry.name, ".meta.json");
128
+ if (!import_fs2.default.existsSync(metaPath)) continue;
129
+ try {
130
+ const meta = JSON.parse(import_fs2.default.readFileSync(metaPath, "utf-8"));
131
+ if (meta.name === walletName) {
132
+ return import_path2.default.join(cacheRoot, entry.name);
133
+ }
134
+ } catch {
135
+ continue;
136
+ }
137
+ }
138
+ return null;
139
+ }
43
140
 
44
141
  // src/withWallets.ts
45
- var import_test4 = require("@playwright/test");
142
+ var W3WALLETS_DIR = ".w3wallets";
143
+ var MIN_PLAYWRIGHT_VERSION = "1.57.0";
144
+ function checkPlaywrightVersion() {
145
+ try {
146
+ const pkgPath = require.resolve("@playwright/test/package.json");
147
+ const { version } = require(pkgPath);
148
+ const [minMajor, minMinor, minPatch] = MIN_PLAYWRIGHT_VERSION.split(".").map(Number);
149
+ const [curMajor, curMinor, curPatch] = version.split(".").map(Number);
150
+ const isBelow = curMajor < minMajor || curMajor === minMajor && curMinor < minMinor || curMajor === minMajor && curMinor === minMinor && curPatch < minPatch;
151
+ if (isBelow) {
152
+ throw new Error(
153
+ `w3wallets requires @playwright/test >= ${MIN_PLAYWRIGHT_VERSION}, but found ${version}.
154
+ Upgrade: npm install -D @playwright/test@latest`
155
+ );
156
+ }
157
+ } catch (err) {
158
+ if (err instanceof Error && err.message.startsWith("w3wallets requires")) {
159
+ throw err;
160
+ }
161
+ }
162
+ }
163
+ function withWallets(test, ...wallets) {
164
+ checkPlaywrightVersion();
165
+ const cachedCount = wallets.filter((w) => isCachedConfig(w)).length;
166
+ if (cachedCount > 0 && cachedCount < wallets.length) {
167
+ throw new Error(
168
+ "Mixing cached and non-cached wallet configs is not supported. All wallets must be either cached (via prepareWallet) or non-cached."
169
+ );
170
+ }
171
+ const useCachedContext = cachedCount > 0;
172
+ debug(`withWallets: ${wallets.length} wallet(s), cached=${useCachedContext}`);
173
+ const extensionInfo = wallets.map((w) => {
174
+ const extPath = import_path3.default.join(process.cwd(), W3WALLETS_DIR, w.extensionDir);
175
+ ensureWalletExtensionExists(extPath, w.name);
176
+ const extensionId = w.extensionId ?? getExtensionId(extPath);
177
+ return { path: extPath, id: extensionId, name: w.name };
178
+ });
179
+ const extensionPaths = extensionInfo.map((e) => e.path);
180
+ const fixtures = {
181
+ context: async ({}, use, testInfo) => {
182
+ const userDataDir = import_path3.default.join(
183
+ process.cwd(),
184
+ W3WALLETS_DIR,
185
+ ".context",
186
+ testInfo.testId
187
+ );
188
+ cleanUserDataDir(userDataDir);
189
+ if (useCachedContext) {
190
+ const wallet = wallets[0];
191
+ const cacheDir = findCacheDir(wallet.name);
192
+ if (!cacheDir) {
193
+ throw new Error(
194
+ `Cache not found for wallet "${wallet.name}".
195
+ Searched: ${import_path3.default.join(process.cwd(), CACHE_DIR)}/
196
+ Rebuild: npx w3wallets cache --force <your-cache-dir>
197
+ Ensure your *.cache.ts setup file exports prepareWallet(...).`
198
+ );
199
+ }
200
+ import_fs3.default.cpSync(cacheDir, userDataDir, { recursive: true });
201
+ }
202
+ debug(`Launching persistent context: ${userDataDir}`);
203
+ const context = await import_test2.chromium.launchPersistentContext(userDataDir, {
204
+ headless: testInfo.project.use.headless ?? true,
205
+ channel: "chromium",
206
+ args: [
207
+ `--disable-extensions-except=${extensionPaths.join(",")}`,
208
+ `--load-extension=${extensionPaths.join(",")}`
209
+ ]
210
+ });
211
+ debug(`Waiting for ${extensionPaths.length} service worker(s)...`);
212
+ const swDeadline = Date.now() + SERVICE_WORKER_TIMEOUT;
213
+ while (context.serviceWorkers().length < extensionPaths.length) {
214
+ if (Date.now() > swDeadline) {
215
+ const found = context.serviceWorkers().length;
216
+ throw new Error(
217
+ `Service worker initialization timed out after ${SERVICE_WORKER_TIMEOUT / 1e3}s.
218
+ Expected: ${extensionPaths.length} extension(s), found: ${found} service worker(s).
219
+ Extension paths: ${extensionPaths.map((p) => import_path3.default.relative(process.cwd(), p)).join(", ")}
220
+ Suggestions:
221
+ - Check extension path exists and contains manifest.json
222
+ - Try headed mode to see what's happening: headless: false
223
+ - Ensure extension is compatible with the installed Chromium version`
224
+ );
225
+ }
226
+ await Promise.race([
227
+ context.waitForEvent("serviceworker", {
228
+ timeout: SERVICE_WORKER_TIMEOUT
229
+ }),
230
+ sleep(SERVICE_WORKER_POLL_INTERVAL)
231
+ ]);
232
+ }
233
+ debug(`All ${extensionPaths.length} service worker(s) detected`);
234
+ await use(context);
235
+ await context.close();
236
+ }
237
+ };
238
+ for (let i = 0; i < wallets.length; i++) {
239
+ const wallet = wallets[i];
240
+ const info = extensionInfo[i];
241
+ fixtures[wallet.name] = async ({ context }, use) => {
242
+ if (isCachedConfig(wallet)) {
243
+ debug(`Initializing cached wallet: ${wallet.name} (ID: ${info.id})`);
244
+ const instance = await findCachedExtension(
245
+ context,
246
+ wallet.WalletClass,
247
+ info.id,
248
+ wallet.name,
249
+ wallet.homeUrl
250
+ );
251
+ await use(instance);
252
+ } else {
253
+ debug(`Initializing fresh wallet: ${wallet.name} (ID: ${info.id})`);
254
+ const instance = await initializeExtension(
255
+ context,
256
+ wallet.WalletClass,
257
+ info.id,
258
+ wallet.name
259
+ );
260
+ await use(instance);
261
+ }
262
+ };
263
+ }
264
+ return test.extend(fixtures);
265
+ }
266
+ function cleanUserDataDir(userDataDir) {
267
+ if (import_fs3.default.existsSync(userDataDir)) {
268
+ import_fs3.default.rmSync(userDataDir, { recursive: true });
269
+ }
270
+ }
271
+ function ensureWalletExtensionExists(walletPath, walletName) {
272
+ const manifestPath = import_path3.default.join(walletPath, "manifest.json");
273
+ if (!import_fs3.default.existsSync(manifestPath)) {
274
+ const cliAlias = walletName.toLowerCase();
275
+ throw new Error(
276
+ `Cannot find ${walletName} extension.
277
+ Checked: ${manifestPath}
278
+ Download it: npx w3wallets ${cliAlias}
279
+ Custom dir: npx w3wallets -o <dir> ${cliAlias}`
280
+ );
281
+ }
282
+ }
283
+ async function findCachedExtension(context, ExtensionClass, expectedExtensionId, walletName, homeUrl) {
284
+ const expectedUrl = `chrome-extension://${expectedExtensionId}/`;
285
+ const worker = context.serviceWorkers().find((w) => w.url().startsWith(expectedUrl));
286
+ if (!worker) {
287
+ const availableIds = context.serviceWorkers().map((w) => w.url().split("/")[2]).filter(Boolean);
288
+ throw new Error(
289
+ `Service worker for ${walletName} (ID: ${expectedExtensionId}) not found in cached context.
290
+ Available IDs: [${availableIds.join(", ")}]
291
+ The cache may be stale. Rebuild: npx w3wallets cache --force
292
+ Also check extension path: ${W3WALLETS_DIR}/${walletName}/`
293
+ );
294
+ }
295
+ const page = await context.newPage();
296
+ if (homeUrl) {
297
+ await page.goto(`chrome-extension://${expectedExtensionId}/${homeUrl}`);
298
+ }
299
+ const extension = new ExtensionClass(page, expectedExtensionId);
300
+ return extension;
301
+ }
302
+ async function initializeExtension(context, ExtensionClass, expectedExtensionId, walletName) {
303
+ const expectedUrl = `chrome-extension://${expectedExtensionId}/`;
304
+ const worker = context.serviceWorkers().find((w) => w.url().startsWith(expectedUrl));
305
+ if (!worker) {
306
+ const availableIds = context.serviceWorkers().map((w) => w.url().split("/")[2]).filter(Boolean);
307
+ throw new Error(
308
+ `Service worker for ${walletName} (ID: ${expectedExtensionId}) not found. Available extension IDs: [${availableIds.join(", ")}]`
309
+ );
310
+ }
311
+ const page = await context.newPage();
312
+ const extension = new ExtensionClass(page, expectedExtensionId);
313
+ return extension;
314
+ }
46
315
 
47
- // src/backpack/backpack.ts
48
- var import_test = require("@playwright/test");
316
+ // src/core/types.ts
317
+ function createWallet(config2) {
318
+ return config2;
319
+ }
320
+
321
+ // src/wallets/metamask/metamask.ts
322
+ var import_test3 = require("@playwright/test");
323
+
324
+ // src/config.ts
325
+ var config = {
326
+ /**
327
+ * Timeout for actions like click, fill, waitFor, goto.
328
+ * Set via W3WALLETS_ACTION_TIMEOUT env variable.
329
+ * @default 30000 (30 seconds)
330
+ */
331
+ get actionTimeout() {
332
+ const value = process.env.W3WALLETS_ACTION_TIMEOUT;
333
+ return value ? parseInt(value, 10) : void 0;
334
+ },
335
+ /**
336
+ * Timeout for expect assertions like toBeVisible, toContainText.
337
+ * Set via W3WALLETS_EXPECT_TIMEOUT env variable.
338
+ * @default undefined (uses Playwright's default of 5000ms)
339
+ */
340
+ get expectTimeout() {
341
+ const value = process.env.W3WALLETS_EXPECT_TIMEOUT;
342
+ return value ? parseInt(value, 10) : void 0;
343
+ }
344
+ };
49
345
 
50
- // src/wallet.ts
346
+ // src/core/wallet.ts
51
347
  var Wallet = class {
52
348
  constructor(page, extensionId) {
53
349
  this.page = page;
54
350
  this.extensionId = extensionId;
351
+ if (config.actionTimeout) {
352
+ page.setDefaultTimeout(config.actionTimeout);
353
+ }
55
354
  }
56
355
  };
57
356
 
58
- // src/backpack/backpack.ts
59
- var Backpack = class extends Wallet {
60
- defaultPassword = "11111111";
61
- currentAccountId = 0;
62
- maxAccountId = 0;
357
+ // src/wallets/metamask/metamask.ts
358
+ var Metamask = class extends Wallet {
359
+ defaultPassword = "TestPassword123!";
63
360
  async gotoOnboardPage() {
361
+ await this.page.goto(`chrome-extension://${this.extensionId}/home.html`);
362
+ await (0, import_test3.expect)(
363
+ this.page.getByRole("button", { name: "I have an existing wallet" })
364
+ ).toBeVisible({ timeout: ONBOARD_VISIBLE_TIMEOUT });
365
+ }
366
+ /**
367
+ * Onboard MetaMask with a mnemonic phrase
368
+ * @param mnemonic - 12 or 24 word recovery phrase
369
+ * @param password - Optional password (defaults to TestPassword123!)
370
+ */
371
+ async onboard(mnemonic, password) {
372
+ debug("metamask.onboard: starting");
373
+ const pwd = password ?? this.defaultPassword;
374
+ await this.gotoOnboardPage();
375
+ await this.page.getByRole("button", { name: "I have an existing wallet" }).click();
376
+ await this.page.getByRole("button", { name: "Import using Secret Recovery Phrase" }).click();
377
+ const srpTextarea = this.page.getByTestId("srp-input-import__srp-note");
378
+ await srpTextarea.click();
379
+ const words = mnemonic.split(" ");
380
+ for (let i = 0; i < words.length; i++) {
381
+ await this.page.keyboard.type(words[i], { delay: MNEMONIC_KEY_DELAY });
382
+ if (i < words.length - 1) {
383
+ await this.page.keyboard.press("Space");
384
+ await this.page.waitForTimeout(MNEMONIC_WORD_DELAY);
385
+ }
386
+ }
387
+ const continueBtn = this.page.getByTestId("import-srp-confirm");
388
+ await (0, import_test3.expect)(continueBtn).toBeEnabled({ timeout: config.expectTimeout });
389
+ await continueBtn.click();
390
+ const passwordInputs = this.page.locator('input[type="password"]');
391
+ await passwordInputs.nth(0).fill(pwd);
392
+ await passwordInputs.nth(1).fill(pwd);
393
+ await this.page.getByRole("checkbox").click({ force: true });
394
+ await this.page.getByRole("button", { name: "Create password" }).click();
395
+ const metametricsBtn = this.page.getByTestId("metametrics-i-agree");
396
+ await metametricsBtn.click();
397
+ const openWalletBtn = this.page.getByRole("button", {
398
+ name: /open wallet/i
399
+ });
400
+ await openWalletBtn.click();
401
+ await this.page.goto(`chrome-extension://${this.extensionId}/home.html`);
64
402
  await this.page.goto(
65
- `chrome-extension://${this.extensionId}/onboarding.html`
403
+ `chrome-extension://${this.extensionId}/sidepanel.html`
66
404
  );
67
- await (0, import_test.expect)(this.page.getByText("Welcome to Backpack")).toBeVisible();
405
+ debug("metamask.onboard: complete");
406
+ }
407
+ /**
408
+ * Dismiss MetaMask promotional popups (e.g., "Transaction Shield")
409
+ * that may overlay the confirmation UI.
410
+ */
411
+ async dismissPopups() {
412
+ debug("metamask.dismissPopups: checking for popups");
413
+ const popup = this.page.getByText(/Transaction Shield|free trial/i);
414
+ if (!await popup.first().isVisible({ timeout: POPUP_VISIBILITY_TIMEOUT }).catch(() => false)) {
415
+ return;
416
+ }
417
+ const shieldClose = this.page.getByTestId(
418
+ "shield-entry-modal-close-button"
419
+ );
420
+ if (await shieldClose.isVisible({ timeout: SHIELD_CLOSE_TIMEOUT }).catch(() => false)) {
421
+ await shieldClose.click();
422
+ if (await this.waitForPopupHidden(popup)) return;
423
+ }
424
+ const closeByAria = this.page.locator('button[aria-label="close"]').first();
425
+ if (await closeByAria.isVisible({ timeout: ARIA_CLOSE_TIMEOUT }).catch(() => false)) {
426
+ await closeByAria.click();
427
+ if (await this.waitForPopupHidden(popup)) return;
428
+ }
429
+ await this.page.keyboard.press("Escape");
430
+ await this.waitForPopupHidden(popup);
68
431
  }
69
- async onboard(network, privateKey) {
70
- this.currentAccountId++;
71
- this.maxAccountId++;
72
- return this._importAccount(network, privateKey, true);
432
+ async waitForPopupHidden(popup) {
433
+ try {
434
+ await popup.first().waitFor({ state: "hidden", timeout: POPUP_HIDDEN_TIMEOUT });
435
+ return true;
436
+ } catch {
437
+ return false;
438
+ }
73
439
  }
74
- async addAccount(network, privateKey) {
75
- this.maxAccountId++;
76
- this.currentAccountId = this.maxAccountId;
77
- await this.page.goto(
78
- `chrome-extension://${this.extensionId}/onboarding.html?add-user-account=true`
440
+ /**
441
+ * After unlock, MetaMask may show onboarding screens, queued
442
+ * notifications, or go straight to the wallet UI. Race all possible
443
+ * states in a single wait to avoid sequential timeout penalties.
444
+ */
445
+ async stabilizePostUnlock() {
446
+ debug("metamask.stabilizePostUnlock: racing post-unlock states");
447
+ const metametricsBtn = this.page.getByTestId("metametrics-i-agree");
448
+ const openWalletBtn = this.page.getByRole("button", {
449
+ name: /open wallet/i
450
+ });
451
+ const readyIndicator = this.page.getByTestId("account-options-menu-button");
452
+ const rejectAllBtn = this.page.getByText("Reject all");
453
+ const notificationCancelBtn = this.page.getByTestId(
454
+ "confirmation-cancel-button"
79
455
  );
80
- await this._importAccount(network, privateKey, false);
456
+ const state = await Promise.race([
457
+ metametricsBtn.waitFor({ state: "visible", timeout: POST_UNLOCK_TIMEOUT }).then(() => "metametrics"),
458
+ openWalletBtn.waitFor({ state: "visible", timeout: POST_UNLOCK_TIMEOUT }).then(() => "openWallet"),
459
+ rejectAllBtn.waitFor({ state: "visible", timeout: POST_UNLOCK_TIMEOUT }).then(() => "rejectAll"),
460
+ notificationCancelBtn.waitFor({ state: "visible", timeout: POST_UNLOCK_TIMEOUT }).then(() => "notification"),
461
+ readyIndicator.waitFor({ state: "visible", timeout: POST_UNLOCK_TIMEOUT }).then(() => "ready")
462
+ ]).catch(() => "timeout");
463
+ debug(`metamask.stabilizePostUnlock: state=${state}`);
464
+ if (state === "timeout") {
465
+ debug(
466
+ `metamask.stabilizePostUnlock: timeout after ${POST_UNLOCK_TIMEOUT}ms. URL: ${this.page.url()}. Checked: metametrics-i-agree, open-wallet button, Reject all, confirmation-cancel-button, account-options-menu-button`
467
+ );
468
+ }
469
+ if (state === "metametrics") {
470
+ await metametricsBtn.click();
471
+ if (await openWalletBtn.isVisible({ timeout: POPUP_HIDDEN_TIMEOUT }).catch(() => false)) {
472
+ await openWalletBtn.click();
473
+ }
474
+ }
475
+ if (state === "openWallet") {
476
+ await openWalletBtn.click();
477
+ }
478
+ if (state !== "ready") {
479
+ await this.dismissQueuedNotifications();
480
+ }
81
481
  }
82
482
  /**
83
- * Switch account
84
- * @param id The first added account has id 1, the second 2, and so on
483
+ * Dismiss all queued MetaMask notifications (e.g., Solana/Tron account
484
+ * removal). These use the templated confirmation flow at #/confirmation/...
485
+ * If multiple are queued, "Reject all" appears; if only one, just
486
+ * confirmation-cancel-button is available.
85
487
  */
86
- async switchAccount(id) {
87
- await this.page.getByRole("button", { name: `A${this.currentAccountId}` }).click();
88
- await this.page.getByRole("button", { name: `Account ${id}` }).click();
89
- this.currentAccountId = id;
90
- }
91
- async unlock() {
92
- await this.page.getByPlaceholder("Password").fill(this.defaultPassword);
93
- await this.page.getByRole("button", { name: "Unlock" }).click();
94
- }
95
- async setRPC(network, rpc) {
96
- await this._clickOnAccount();
97
- await this.page.getByRole("button", { name: "Settings" }).click();
98
- await this.page.getByRole("button", { name: network }).click();
99
- await this.page.getByRole("button", { name: "RPC Connection" }).click();
100
- await this.page.getByRole("button", { name: "Custom" }).click();
101
- await this.page.getByPlaceholder("RPC URL").fill(rpc);
102
- await this.page.keyboard.press("Enter");
103
- }
104
- async ignoreAndProceed() {
105
- const ignoreButton = this.page.getByText("Ignore and proceed anyway.");
106
- await ignoreButton.click();
488
+ async dismissQueuedNotifications() {
489
+ const homeUrl = `chrome-extension://${this.extensionId}/home.html`;
490
+ const readyIndicator = this.page.getByTestId("account-options-menu-button");
491
+ const rejectAllBtn = this.page.getByText("Reject all");
492
+ const notificationCancelBtn = this.page.getByTestId(
493
+ "confirmation-cancel-button"
494
+ );
495
+ await this.page.goto(homeUrl);
496
+ for (let i = 0; i < 10; i++) {
497
+ const state = await Promise.race([
498
+ readyIndicator.waitFor({ state: "visible", timeout: NOTIFICATION_CHECK_TIMEOUT }).then(() => "ready"),
499
+ rejectAllBtn.waitFor({ state: "visible", timeout: NOTIFICATION_CHECK_TIMEOUT }).then(() => "rejectAll"),
500
+ notificationCancelBtn.waitFor({ state: "visible", timeout: NOTIFICATION_CHECK_TIMEOUT }).then(() => "notification")
501
+ ]).catch(() => "timeout");
502
+ if (state === "ready") return;
503
+ if (state === "rejectAll") {
504
+ await rejectAllBtn.click();
505
+ await this.page.goto(homeUrl);
506
+ continue;
507
+ }
508
+ if (state === "notification") {
509
+ await notificationCancelBtn.click();
510
+ await this.page.goto(homeUrl);
511
+ continue;
512
+ }
513
+ debug(
514
+ `metamask.dismissQueuedNotifications: timeout at iteration ${i}. URL: ${this.page.url()}`
515
+ );
516
+ break;
517
+ }
518
+ await (0, import_test3.expect)(readyIndicator).toBeVisible({ timeout: POST_UNLOCK_TIMEOUT });
519
+ }
520
+ /**
521
+ * Wait for a target button while handling the Transaction Shield popup.
522
+ * Always navigates to sidepanel.html fresh so MetaMask's
523
+ * ConfirmationHandler can route to the pending approval.
524
+ */
525
+ async waitAndClickButton(btnLocator) {
526
+ debug(`metamask.waitAndClickButton: navigating to sidepanel`);
527
+ const popup = this.page.getByText(/Transaction Shield|free trial/i);
528
+ const sidepanelUrl = `chrome-extension://${this.extensionId}/sidepanel.html`;
529
+ const confirmRoutePattern = /#\/(confirm-transaction|connect|confirmation)\b/;
530
+ const waitForButtonOrPopup = (timeout) => Promise.race([
531
+ btnLocator.first().waitFor({ state: "visible", timeout }).then(() => "button"),
532
+ popup.first().waitFor({ state: "visible", timeout }).then(() => "popup")
533
+ ]).catch(() => "timeout");
534
+ const handlePopupAndClick = async () => {
535
+ await this.dismissPopups();
536
+ await btnLocator.first().waitFor({ state: "visible", timeout: BUTTON_OR_POPUP_TIMEOUT });
537
+ await btnLocator.first().click();
538
+ };
539
+ let routeFound = false;
540
+ for (let attempt = 0; attempt < MAX_ROUTE_ATTEMPTS; attempt++) {
541
+ await this.page.goto(sidepanelUrl);
542
+ try {
543
+ await this.page.waitForURL(confirmRoutePattern, {
544
+ timeout: ROUTE_RETRY_TIMEOUT
545
+ });
546
+ routeFound = true;
547
+ break;
548
+ } catch {
549
+ debug(
550
+ `metamask.waitAndClickButton: route attempt ${attempt + 1}/${MAX_ROUTE_ATTEMPTS} failed. URL: ${this.page.url()}`
551
+ );
552
+ }
553
+ }
554
+ if (!routeFound) {
555
+ console.warn(
556
+ `[w3wallets] confirmation route not found after ${MAX_ROUTE_ATTEMPTS} attempts. URL: ${this.page.url()}`
557
+ );
558
+ }
559
+ const result = await waitForButtonOrPopup(BUTTON_OR_POPUP_TIMEOUT);
560
+ debug(
561
+ `metamask.waitAndClickButton: result=${result}, URL=${this.page.url()}`
562
+ );
563
+ if (result === "button") {
564
+ await btnLocator.first().click();
565
+ await this.page.waitForURL((url) => !confirmRoutePattern.test(url.toString()), {
566
+ timeout: POST_CLICK_TIMEOUT
567
+ }).catch(() => {
568
+ console.warn(
569
+ `[w3wallets] still on confirmation route after click. URL: ${this.page.url()}`
570
+ );
571
+ });
572
+ return;
573
+ }
574
+ if (result === "popup") {
575
+ await handlePopupAndClick();
576
+ await this.page.waitForURL((url) => !confirmRoutePattern.test(url.toString()), {
577
+ timeout: POST_CLICK_TIMEOUT
578
+ }).catch(() => {
579
+ console.warn(
580
+ `[w3wallets] still on confirmation route after popup dismiss. URL: ${this.page.url()}`
581
+ );
582
+ });
583
+ return;
584
+ }
585
+ const isOnConfirmRoute = confirmRoutePattern.test(this.page.url());
586
+ debug(
587
+ `metamask.waitAndClickButton: timeout after ${BUTTON_OR_POPUP_TIMEOUT}ms. URL: ${this.page.url()}, onConfirmRoute: ${isOnConfirmRoute}`
588
+ );
589
+ console.warn(
590
+ `[w3wallets] no button or popup found after ${BUTTON_OR_POPUP_TIMEOUT / 1e3}s. URL: ${this.page.url()}`
591
+ );
592
+ await btnLocator.first().click({ timeout: LAST_RESORT_CLICK_TIMEOUT });
107
593
  }
108
594
  async approve() {
109
- await this.page.getByText("Approve", { exact: true }).click();
595
+ debug("metamask.approve: starting");
596
+ const confirmBtn = this.page.getByTestId("confirm-btn").or(this.page.getByTestId("confirm-footer-button")).or(this.page.getByTestId("page-container-footer-next")).or(this.page.getByRole("button", { name: /^confirm$/i }));
597
+ await this.waitAndClickButton(confirmBtn);
110
598
  }
111
599
  async deny() {
112
- await this.page.getByText("Deny", { exact: true }).click();
600
+ debug("metamask.deny: starting");
601
+ const cancelBtn = this.page.getByTestId("cancel-btn").or(this.page.getByTestId("confirm-footer-cancel-button")).or(this.page.getByTestId("page-container-footer-cancel")).or(this.page.getByRole("button", { name: /^cancel$/i })).or(this.page.getByRole("button", { name: /^reject$/i }));
602
+ await this.waitAndClickButton(cancelBtn);
113
603
  }
114
- async _clickOnAccount() {
115
- return this.page.getByRole("button", { name: `A${this.currentAccountId}`, exact: true }).click();
604
+ /**
605
+ * Lock the MetaMask wallet
606
+ */
607
+ async lock() {
608
+ debug("metamask.lock: starting");
609
+ await this.page.goto(`chrome-extension://${this.extensionId}/home.html`);
610
+ const menuBtn = this.page.getByTestId("account-options-menu-button");
611
+ await menuBtn.waitFor({ state: "visible", timeout: MENU_BUTTON_TIMEOUT });
612
+ await menuBtn.click({ force: true });
613
+ await this.page.locator("text=Log out").click();
116
614
  }
117
- async _importAccount(network, privateKey, isOnboard) {
118
- {
119
- await this.page.waitForTimeout(2e3);
120
- if (await this.page.getByText("You're all good!").isVisible()) {
121
- await this.page.getByLabel("Go back").click();
122
- }
123
- }
124
- await this.page.getByTestId("terms-of-service-checkbox").click();
125
- await this.page.getByText("I already have a wallet").click();
126
- await this.page.getByText(network).click();
127
- await this.page.getByText("Private key").click();
128
- await this.page.getByPlaceholder("Private key").fill(privateKey);
129
- await this.page.waitForTimeout(1e3);
130
- await this.page.getByText("Import", { exact: true }).click();
131
- if (isOnboard) {
132
- await this.page.getByRole("textbox").nth(0).fill(this.defaultPassword);
133
- await this.page.getByRole("textbox").nth(1).fill(this.defaultPassword);
134
- await this.page.getByText("Next", { exact: true }).click();
135
- await (0, import_test.expect)(this.page.getByText("You're all good!")).toBeVisible();
615
+ /**
616
+ * Unlock MetaMask with password.
617
+ * After unlocking, stabilizes the wallet UI by handling post-unlock
618
+ * screens (metametrics, onboarding completion) and dismissing queued
619
+ * notifications. Ends on home.html with the wallet UI ready.
620
+ */
621
+ async unlock(password) {
622
+ debug("metamask.unlock: starting");
623
+ const pwd = password ?? this.defaultPassword;
624
+ await this.page.goto(`chrome-extension://${this.extensionId}/home.html`);
625
+ const passwordInput = this.page.getByTestId("unlock-password");
626
+ await passwordInput.fill(pwd);
627
+ await this.page.getByTestId("unlock-submit").click();
628
+ await this.page.waitForSelector('[data-testid="unlock-password"]', {
629
+ state: "hidden",
630
+ timeout: LOCK_SCREEN_TIMEOUT
631
+ });
632
+ await this.stabilizePostUnlock();
633
+ debug("metamask.unlock: complete");
634
+ }
635
+ /**
636
+ * Switch to an existing network in MetaMask
637
+ * @param networkName - Name of the network to switch to (e.g., "Ethereum Mainnet", "Sepolia")
638
+ */
639
+ async switchNetwork(networkName, networkType = "Popular") {
640
+ debug(`metamask.switchNetwork: ${networkName} (${networkType})`);
641
+ await this.page.getByTestId("sort-by-networks").click();
642
+ if (networkType === "Custom") {
643
+ await this.page.getByRole("tab", { name: "Custom" }).click({ force: true });
136
644
  }
137
- await this.page.goto(`chrome-extension://${this.extensionId}/popup.html`);
138
- await this.page.getByTestId("__CAROUSEL_ITEM_0__").waitFor({ state: "visible" });
645
+ await this.page.getByText(networkName).click();
646
+ await (0, import_test3.expect)(this.page.getByTestId("sort-by-networks")).toHaveText(
647
+ networkName,
648
+ { timeout: config.expectTimeout }
649
+ );
650
+ }
651
+ async switchAccount(accountName) {
652
+ await this.page.getByTestId("account-menu-icon").click();
653
+ await this.page.getByText(accountName, { exact: true }).click();
654
+ }
655
+ /**
656
+ * Add a custom network to MetaMask
657
+ */
658
+ async addNetwork(network) {
659
+ await this.page.goto(
660
+ `chrome-extension://${this.extensionId}/home.html#settings/networks/add-network`
661
+ );
662
+ await this.page.getByTestId("network-form-network-name").fill(network.name);
663
+ await this.page.getByTestId("network-form-rpc-url").fill(network.rpc);
664
+ await this.page.getByTestId("network-form-chain-id").fill(network.chainId.toString());
665
+ await this.page.getByTestId("network-form-ticker-input").fill(network.currencySymbol);
666
+ await this.page.getByRole("button", { name: /save/i }).click();
667
+ }
668
+ async addCustomNetwork(settings) {
669
+ await this.page.getByTestId("account-options-menu-button").click({ force: true });
670
+ await this.page.getByTestId("global-menu-networks").click();
671
+ await this.page.getByRole("button", { name: "Add a custom network" }).click();
672
+ await this.page.getByTestId("network-form-network-name").fill(settings.name);
673
+ await this.page.getByTestId("network-form-chain-id").fill(settings.chainId.toString());
674
+ await this.page.getByTestId("network-form-ticker-input").fill(settings.currencySymbol);
675
+ await this.page.getByTestId("test-add-rpc-drop-down").click();
676
+ await this.page.getByRole("button", { name: "Add RPC URL" }).click();
677
+ await this.page.getByTestId("rpc-url-input-test").fill(settings.rpc);
678
+ await this.page.getByRole("button", { name: "Add URL" }).click();
679
+ await this.page.getByRole("button", { name: "Save" }).click();
680
+ await this.page.goto(`chrome-extension://${this.extensionId}/home.html`);
681
+ await this.page.waitForLoadState("domcontentloaded");
682
+ }
683
+ async enableTestNetworks() {
684
+ await this.page.getByTestId("account-options-menu-button").click({ force: true });
685
+ await this.page.getByTestId("global-menu-networks").click();
686
+ const toggle = this.page.locator(
687
+ "text=Show test networks >> xpath=following-sibling::label"
688
+ );
689
+ await (0, import_test3.expect)(toggle).toBeVisible({ timeout: config.expectTimeout });
690
+ await toggle.click();
691
+ await this.page.goto(`chrome-extension://${this.extensionId}/home.html`);
692
+ }
693
+ async importAccount(privateKey) {
694
+ debug("metamask.importAccount: starting");
695
+ await this.page.getByTestId("account-menu-icon").click();
696
+ await this.page.getByTestId("account-list-add-wallet-button").click();
697
+ await this.page.getByTestId("choose-wallet-type-import-account").click();
698
+ await this.page.locator("#private-key-box").fill(privateKey);
699
+ await this.page.getByTestId("import-account-confirm-button").click();
700
+ await this.page.getByTestId("back-button").click();
701
+ await this.page.locator('[data-testid^="multichain-account-cell-keyring:"]').first().click();
702
+ }
703
+ async accountNameIs(accountName) {
704
+ await (0, import_test3.expect)(this.page.getByTestId("account-menu-icon")).toContainText(
705
+ accountName,
706
+ { timeout: config.expectTimeout }
707
+ );
139
708
  }
140
709
  };
141
710
 
142
- // src/polkadotJS/polkadotJS.ts
143
- var import_test2 = require("@playwright/test");
711
+ // src/wallets/polkadot-js/polkadot-js.ts
712
+ var import_test4 = require("@playwright/test");
144
713
  var PolkadotJS = class extends Wallet {
145
714
  defaultPassword = "11111111";
146
715
  async gotoOnboardPage() {
147
716
  await this.page.goto(`chrome-extension://${this.extensionId}/index.html`);
148
- await (0, import_test2.expect)(
717
+ await (0, import_test4.expect)(
149
718
  this.page.getByText("Before we start, just a couple of notes")
150
- ).toBeVisible();
719
+ ).toBeVisible({ timeout: config.expectTimeout });
151
720
  }
152
721
  async onboard(seed, password, name) {
722
+ debug("polkadotJS.onboard: starting");
723
+ await this.gotoOnboardPage();
153
724
  await this.page.getByRole("button", { name: "Understood, let me continue" }).click();
154
725
  await this.page.getByRole("button", { name: "I Understand" }).click();
155
726
  await this.page.locator(".popupToggle").first().click();
@@ -166,12 +737,13 @@ var PolkadotJS = class extends Wallet {
166
737
  password ?? this.defaultPassword
167
738
  );
168
739
  await this.page.getByRole("button", { name: "Add the account with the supplied seed" }).click();
740
+ debug("polkadotJS.onboard: complete");
169
741
  }
170
742
  async selectAllAccounts() {
171
743
  await this.page.getByText("Select all").click();
172
744
  }
173
745
  async selectAccount(accountId) {
174
- await this.page.locator(".accountWichCheckbox").filter({ hasText: accountId }).locator(".accountTree-checkbox").locator("span").check();
746
+ await this.page.locator(".accountWichCheckbox").filter({ hasText: accountId }).locator(".accountTree-checkbox").click();
175
747
  }
176
748
  async enterPassword(password) {
177
749
  await this._getLabeledInput("Password for this account").fill(
@@ -179,6 +751,7 @@ var PolkadotJS = class extends Wallet {
179
751
  );
180
752
  }
181
753
  async approve() {
754
+ debug("polkadotJS.approve: starting");
182
755
  const connect = this.page.getByRole("button", { name: "Connect" });
183
756
  const signTransaction = this.page.getByRole("button", {
184
757
  name: "Sign the transaction"
@@ -186,6 +759,7 @@ var PolkadotJS = class extends Wallet {
186
759
  await connect.or(signTransaction).click();
187
760
  }
188
761
  async deny() {
762
+ debug("polkadotJS.deny: starting");
189
763
  const reject = this.page.getByRole("button", { name: "Reject" });
190
764
  const cancel = this.page.getByRole("link", { name: "Cancel" });
191
765
  await reject.or(cancel).click();
@@ -197,234 +771,38 @@ var PolkadotJS = class extends Wallet {
197
771
  }
198
772
  };
199
773
 
200
- // src/metamask/metamask.ts
201
- var import_test3 = require("@playwright/test");
202
- var Metamask = class extends Wallet {
203
- defaultPassword = "11111111";
204
- async gotoOnboardPage() {
205
- await this.page.goto(`chrome-extension://${this.extensionId}/home.html`);
206
- }
207
- /**
208
- *
209
- * @param mnemonic 12-word mnemonic seed phrase
210
- */
211
- async onboard(mnemonic, password = this.defaultPassword) {
212
- await this.page.getByTestId("onboarding-import-wallet").click();
213
- await this.page.getByTestId("onboarding-import-with-srp-button").click();
214
- await this.page.getByTestId("srp-input-import__srp-note").pressSequentially(mnemonic, { delay: 5 });
215
- await this.page.getByRole("button", { name: "Continue" }).click();
216
- await this.page.getByTestId("create-password-new-input").fill(password);
217
- await this.page.getByTestId("create-password-confirm-input").fill(password);
218
- await this.page.getByTestId("create-password-terms").click();
219
- await this.page.getByTestId("create-password-submit").click();
220
- await this.page.getByTestId("metametrics-i-agree").click();
221
- await this.page.getByTestId("onboarding-complete-done").click();
222
- await this.clickTopRightCornerToCloseAllTheMarketingBullshit();
223
- }
224
- // async switchAccount(accountAddress: { address: string }): Promise<void>;
225
- async switchAccount(accountNameOrAddress) {
226
- await this.page.getByTestId("account-menu-icon").click();
227
- await this.page.getByText(accountNameOrAddress.name, { exact: true }).click();
228
- }
229
- async importAccount(privateKey) {
230
- await this.page.getByTestId("account-menu-icon").click();
231
- await this.page.getByTestId("account-list-add-wallet-button").click();
232
- await this.page.getByTestId("add-wallet-modal-import-account").click();
233
- await this.page.locator("#private-key-box").fill(privateKey);
234
- await this.page.getByTestId("import-account-confirm-button").click();
235
- await this.page.getByRole("button", { name: "Back" }).click();
236
- }
237
- async addAccount(accountName) {
238
- await this.page.getByTestId("account-menu-icon").click();
239
- await this.page.getByTestId("multichain-account-menu-popover-action-button").click();
240
- await this.page.getByTestId("multichain-account-menu-popover-add-account").click();
241
- if (accountName) {
242
- await this.page.locator("#account-name").fill(accountName);
243
- }
244
- await this.page.getByTestId("submit-add-account-with-name").click();
245
- }
246
- async getAccountName() {
247
- const accountSelect = this.page.getByTestId("account-menu-icon");
248
- await (0, import_test3.expect)(accountSelect).toBeVisible();
249
- const text = await accountSelect.textContent();
250
- if (!text) throw Error("Cannot get account name");
251
- return text;
252
- }
253
- async connectToNetwork(networkName, networkType = "Popular") {
254
- await this.page.getByTestId("sort-by-networks").click();
255
- await this.page.getByRole("tab", { name: networkType, exact: true }).click();
256
- const additionalNetwork = this.page.getByTestId("additional-network-item").getByText(networkName);
257
- await this.page.getByText(networkName).click();
258
- }
259
- async addCustomNetwork(settings) {
260
- await this.page.getByTestId("account-options-menu-button").click();
261
- await this.page.getByTestId("global-menu-networks").click();
262
- await this.page.getByRole("button", { name: "Add a custom network" }).click();
263
- await this.page.getByTestId("network-form-network-name").fill(settings.name);
264
- await this.page.getByTestId("network-form-chain-id").fill(settings.chainId.toString());
265
- await this.page.getByTestId("network-form-ticker-input").fill(settings.currencySymbol);
266
- await this.page.getByTestId("test-add-rpc-drop-down").click();
267
- await this.page.getByRole("button", { name: "Add RPC URL" }).click();
268
- await this.page.getByTestId("rpc-url-input-test").fill(settings.rpc);
269
- await this.page.getByRole("button", { name: "Add URL" }).click();
270
- await this.page.getByRole("button", { name: "Save" }).click();
271
- }
272
- async enableTestNetworks() {
273
- await this.page.getByTestId("account-options-menu-button").click();
274
- await this.page.getByTestId("global-menu-networks").click();
275
- await this.page.locator("text=Show test networks >> xpath=following-sibling::label").click();
276
- await this.page.keyboard.press("Escape");
277
- }
278
- async approve() {
279
- const p = await this.page.context().newPage();
280
- await p.goto(`chrome-extension://${this.extensionId}/notification.html`);
281
- await p.locator(
282
- '[data-testid="confirm-footer-button"], [data-testid="confirm-btn"], [data-testid="page-container-footer-next"], [data-testid="confirmation-submit-button"]'
283
- ).click();
284
- await p.waitForSelector(".multichain-app-header", {
285
- timeout: 1e4
286
- });
287
- await p.close();
288
- }
289
- async deny() {
290
- return this.usingNotificationPage(
291
- (p) => p.getByTestId("cancel-btn").click()
292
- );
293
- }
294
- async usingNotificationPage(action) {
295
- const p = await this.page.context().newPage();
296
- await p.goto(`chrome-extension://${this.extensionId}/notification.html`);
297
- await action(p);
298
- await p.close();
299
- }
300
- async clickTopRightCornerToCloseAllTheMarketingBullshit() {
301
- await this.page.waitForTimeout(500);
302
- await this.page.keyboard.press("Escape");
303
- await this.page.mouse.click(1e3, 10);
304
- }
305
- };
774
+ // src/wallets/index.ts
775
+ var metamask = createWallet({
776
+ name: "metamask",
777
+ extensionDir: "metamask",
778
+ WalletClass: Metamask,
779
+ homeUrl: "home.html"
780
+ });
781
+ var polkadotJS = createWallet({
782
+ name: "polkadotJS",
783
+ extensionDir: "polkadotjs",
784
+ WalletClass: PolkadotJS,
785
+ homeUrl: "index.html"
786
+ });
306
787
 
307
- // src/withWallets.ts
308
- var w3walletsDir = ".w3wallets";
309
- function withWallets(test, ...config) {
310
- const withBackpack = config.includes("backpack");
311
- const withPolkadotJS = config.includes("polkadotJS");
312
- const withMetamask = config.includes("metamask");
313
- const backpackPath = import_path.default.join(process.cwd(), w3walletsDir, "backpack");
314
- const polkadotJSPath = import_path.default.join(process.cwd(), w3walletsDir, "polkadotJS");
315
- const metamaskPath = import_path.default.join(process.cwd(), w3walletsDir, "metamask");
316
- return test.extend({
317
- /**
318
- * Sets up a persistent browser context with the requested extensions loaded.
319
- */
320
- context: async ({}, use, testInfo) => {
321
- const userDataDir = import_path.default.join(
322
- process.cwd(),
323
- ".w3wallets",
324
- ".context",
325
- testInfo.testId
326
- );
327
- cleanUserDataDir(userDataDir);
328
- const extensionPaths = [];
329
- if (withBackpack) {
330
- ensureWalletExtensionExists(backpackPath, "backpack");
331
- extensionPaths.push(backpackPath);
332
- }
333
- if (withPolkadotJS) {
334
- ensureWalletExtensionExists(polkadotJSPath, "polkadotJS");
335
- extensionPaths.push(polkadotJSPath);
336
- }
337
- if (withMetamask) {
338
- ensureWalletExtensionExists(metamaskPath, "metamask");
339
- extensionPaths.push(metamaskPath);
340
- }
341
- const context = await import_test4.chromium.launchPersistentContext(userDataDir, {
342
- headless: testInfo.project.use.headless ?? true,
343
- channel: "chromium",
344
- args: [
345
- `--disable-extensions-except=${extensionPaths.join(",")}`,
346
- `--load-extension=${extensionPaths.join(",")}`
347
- ]
348
- });
349
- while (context.serviceWorkers().length < extensionPaths.length) {
350
- await sleep(1e3);
351
- }
352
- await use(context);
353
- await context.close();
354
- },
355
- backpack: async ({ context }, use) => {
356
- if (!withBackpack) {
357
- throw Error(
358
- "The Backpack wallet hasn't been loaded. Add it to the withWallets function."
359
- );
360
- }
361
- const backpack = await initializeExtension(
362
- context,
363
- Backpack,
364
- "Backpack is not initialized"
365
- );
366
- await use(backpack);
367
- },
368
- polkadotJS: async ({ context }, use) => {
369
- if (!withPolkadotJS) {
370
- throw Error(
371
- "The Polkadot{.js} wallet hasn't been loaded. Add it to the withWallets function."
372
- );
373
- }
374
- const polkadotJS = await initializeExtension(
375
- context,
376
- PolkadotJS,
377
- "Polkadot{.js} is not initialized"
378
- );
379
- await use(polkadotJS);
380
- },
381
- metamask: async ({ context }, use) => {
382
- if (!withMetamask) {
383
- throw Error(
384
- "The Metamask wallet hasn't been loaded. Add it to the withWallets function."
385
- );
386
- }
387
- const metamask = await initializeExtension(
388
- context,
389
- Metamask,
390
- "Metamask is not initialized"
391
- );
392
- await use(metamask);
393
- }
394
- });
395
- }
396
- function cleanUserDataDir(userDataDir) {
397
- if (import_fs.default.existsSync(userDataDir)) {
398
- import_fs.default.rmSync(userDataDir, { recursive: true });
399
- }
400
- }
401
- function ensureWalletExtensionExists(walletPath, walletName) {
402
- if (!import_fs.default.existsSync(import_path.default.join(walletPath, "manifest.json"))) {
403
- throw new Error(
404
- `Cannot find ${walletName}. Please download it via 'npx w3wallets ${walletName}'.`
405
- );
406
- }
407
- }
408
- async function initializeExtension(context, ExtensionClass, notInitializedErrorMessage) {
409
- const serviceWorkers = context.serviceWorkers();
410
- let page = await context.newPage();
411
- for (const worker of serviceWorkers) {
412
- const extensionId = worker.url().split("/")[2];
413
- if (!extensionId) {
414
- continue;
415
- }
416
- const extension = new ExtensionClass(page, extensionId);
417
- try {
418
- await extension.gotoOnboardPage();
419
- return extension;
420
- } catch {
421
- await page.close();
422
- page = await context.newPage();
423
- }
424
- }
425
- throw new Error(notInitializedErrorMessage);
788
+ // src/cache/prepareWallet.ts
789
+ function prepareWallet(walletConfig, setupFn) {
790
+ return {
791
+ ...walletConfig,
792
+ setupFn,
793
+ __cached: true
794
+ };
426
795
  }
427
796
  // Annotate the CommonJS export names for ESM import in node:
428
797
  0 && (module.exports = {
798
+ Metamask,
799
+ PolkadotJS,
800
+ config,
801
+ createWallet,
802
+ debug,
803
+ isCachedConfig,
804
+ metamask,
805
+ polkadotJS,
806
+ prepareWallet,
429
807
  withWallets
430
808
  });