@avalix/chroma 0.0.8 → 0.0.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.
@@ -4,20 +4,28 @@ import path from "node:path";
4
4
  import process from "node:process";
5
5
 
6
6
  //#region src/wallets/polkadot-js.ts
7
+ const VERSION$1 = "0.62.6";
7
8
  const POLKADOT_JS_CONFIG = {
8
- downloadUrl: "https://github.com/polkadot-js/extension/releases/download/v0.61.7/master-chrome-build.zip",
9
- extensionName: "polkadot-extension-0.61.7"
9
+ downloadUrl: `https://github.com/polkadot-js/extension/releases/download/v${VERSION$1}/master-chrome-build.zip`,
10
+ extensionName: `polkadot-extension-${VERSION$1}`
10
11
  };
11
12
  async function findExtensionPopup$1(context, extensionId) {
12
- const pages = context.pages();
13
- for (const p of pages) if (p.url().includes(`chrome-extension://${extensionId}/`)) return p;
13
+ const maxAttempts = 10;
14
+ const retryDelay = 500;
15
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
16
+ const pages = context.pages();
17
+ for (const p of pages) if (p.url().includes(`chrome-extension://${extensionId}/`)) {
18
+ await p.waitForLoadState("domcontentloaded");
19
+ return p;
20
+ }
21
+ if (attempt < maxAttempts - 1) await new Promise((resolve) => setTimeout(resolve, retryDelay));
22
+ }
14
23
  throw new Error(`Extension popup not found for ID: ${extensionId}`);
15
24
  }
16
25
  async function getPolkadotJSExtensionPath() {
17
26
  const extensionsDir = path.resolve(process.cwd(), ".chroma");
18
27
  const extensionDir = path.join(extensionsDir, POLKADOT_JS_CONFIG.extensionName);
19
- if (!fs.existsSync(extensionDir) || fs.readdirSync(extensionDir).length === 0) throw new Error(`Polkadot-JS extension not found at: ${extensionDir}\n\nPlease download the extension first by running:\n npx @avalix/chroma download-extensions\n\nOr if you're using this as a dependency:\n npm run chroma:download\n`);
20
- console.log(`✅ Found Polkadot-JS extension at: ${extensionDir}`);
28
+ if (!fs.existsSync(extensionDir) || fs.readdirSync(extensionDir).length === 0) throw new Error(`Polkadot-JS extension not found at: ${extensionDir}\n\nPlease download the extension first by running:\n npx @avalix/chroma download-extensions\n`);
21
29
  return extensionDir;
22
30
  }
23
31
  async function importPolkadotJSAccount(page, { seed, name = "Test Account", password = "h3llop0lkadot!" }) {
@@ -31,6 +39,7 @@ async function importPolkadotJSAccount(page, { seed, name = "Test Account", pass
31
39
  await understoodButton.click();
32
40
  await extensionPage.waitForTimeout(100);
33
41
  }
42
+ if (await extensionPage.getByRole("button", { name: "I Understand" }).isVisible()) await extensionPage.getByRole("button", { name: "I Understand" }).click();
34
43
  await extensionPage.goto(`${extensionPopupUrl}#/account/import-seed`);
35
44
  await extensionPage.locator("textarea").fill(seed);
36
45
  await extensionPage.locator("button:has-text(\"Next\")").click();
@@ -38,7 +47,6 @@ async function importPolkadotJSAccount(page, { seed, name = "Test Account", pass
38
47
  await extensionPage.locator("input[type=\"password\"]").fill(password);
39
48
  await extensionPage.locator("div").filter({ hasText: /^Repeat password for verification$/ }).getByRole("textbox").fill(password);
40
49
  await extensionPage.getByRole("button", { name: "Add the account with the supplied seed" }).click();
41
- console.log(`✅ Created Polkadot-JS wallet account: ${name}`);
42
50
  } finally {
43
51
  await extensionPage.close();
44
52
  }
@@ -46,69 +54,76 @@ async function importPolkadotJSAccount(page, { seed, name = "Test Account", pass
46
54
  async function authorizePolkadotJS(page) {
47
55
  const context = page.__extensionContext;
48
56
  const extensionId = page.__extensionId;
49
- await new Promise((resolve) => setTimeout(resolve, 1e3));
50
57
  const extensionPopup = await findExtensionPopup$1(context, extensionId);
51
- await extensionPopup.getByText("Select all").click();
58
+ if (await extensionPopup.getByRole("button", { name: "I Understand" }).isVisible()) await extensionPopup.getByRole("button", { name: "I Understand" }).click();
59
+ if (!await extensionPopup.getByText("Select all").locator("..").locator("input[type=\"checkbox\"]").isChecked().catch(() => false)) await extensionPopup.getByText("Select all").click();
52
60
  await extensionPopup.getByRole("button", { name: /Connect \d+ account\(s\)/ }).click();
53
- console.log("✅ Polkadot-JS wallet connected successfully");
54
61
  }
55
62
  async function approvePolkadotJSTx(page, options = {}) {
56
63
  const { password = "h3llop0lkadot!" } = options;
57
64
  const context = page.__extensionContext;
58
65
  const extensionId = page.__extensionId;
59
- await new Promise((resolve) => setTimeout(resolve, 1e3));
60
66
  const extensionPopup = await findExtensionPopup$1(context, extensionId);
61
67
  await extensionPopup.getByRole("textbox").fill(password);
62
68
  await extensionPopup.getByRole("button", { name: "Sign the transaction" }).click();
63
- console.log("✅ Polkadot-JS transaction signed successfully");
69
+ }
70
+ async function rejectPolkadotJSTx(page) {
71
+ const context = page.__extensionContext;
72
+ const extensionId = page.__extensionId;
73
+ await (await findExtensionPopup$1(context, extensionId)).getByRole("link", { name: "Cancel" }).click();
64
74
  }
65
75
 
66
76
  //#endregion
67
77
  //#region src/wallets/talisman.ts
78
+ const VERSION = "3.1.13";
68
79
  const TALISMAN_CONFIG = {
69
- downloadUrl: "https://github.com/avalix-labs/polkadot-wallets/raw/refs/heads/main/talisman/talisman-3.0.5.zip",
70
- extensionName: "talisman-extension-3.0.5"
80
+ downloadUrl: `https://github.com/avalix-labs/polkadot-wallets/raw/refs/heads/main/talisman/talisman-${VERSION}.zip`,
81
+ extensionName: `talisman-extension-${VERSION}`
71
82
  };
72
83
  async function findExtensionPopup(context, extensionId) {
73
- await new Promise((resolve) => setTimeout(resolve, 1e3));
74
- const pages = context.pages();
75
- for (const p of pages) if (p.url().includes(`chrome-extension://${extensionId}/`)) {
76
- p.setViewportSize({
77
- width: 400,
78
- height: 600
79
- });
80
- p.waitForLoadState("domcontentloaded");
81
- return p;
84
+ const maxAttempts = 10;
85
+ const retryDelay = 500;
86
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
87
+ const pages = context.pages();
88
+ for (const p of pages) if (p.url().includes(`chrome-extension://${extensionId}/`)) {
89
+ await p.setViewportSize({
90
+ width: 400,
91
+ height: 600
92
+ });
93
+ await p.waitForLoadState("domcontentloaded");
94
+ return p;
95
+ }
96
+ if (attempt < maxAttempts - 1) await new Promise((resolve) => setTimeout(resolve, retryDelay));
82
97
  }
83
98
  throw new Error(`Extension popup not found for ID: ${extensionId}`);
84
99
  }
85
100
  async function getTalismanExtensionPath() {
86
101
  const extensionsDir = path.resolve(process.cwd(), ".chroma");
87
102
  const extensionDir = path.join(extensionsDir, TALISMAN_CONFIG.extensionName);
88
- if (!fs.existsSync(extensionDir) || fs.readdirSync(extensionDir).length === 0) throw new Error(`Talisman extension not found at: ${extensionDir}\n\nPlease download the extension first by running:\n npx @avalix/chroma download-extensions\n\nOr if you're using this as a dependency:\n npm run chroma:download\n`);
89
- console.log(`✅ Found Talisman extension at: ${extensionDir}`);
103
+ if (!fs.existsSync(extensionDir) || fs.readdirSync(extensionDir).length === 0) throw new Error(`Talisman extension not found at: ${extensionDir}\n\nPlease download the extension first by running:\n npx @avalix/chroma download-extensions\n`);
90
104
  return extensionDir;
91
105
  }
92
106
  async function importEthPrivateKey(page, { seed, name = "Test Account", password = "h3llop0lkadot!" }) {
93
107
  const context = page.__extensionContext;
94
108
  const extensionId = page.__extensionId;
95
- await new Promise((resolve) => setTimeout(resolve, 2e3));
109
+ const maxAttempts = 20;
110
+ const retryDelay = 500;
96
111
  let extensionPage = null;
97
- const pages = context.pages();
98
- for (const page$1 of pages) {
99
- const url = page$1.url();
100
- console.log(`📄 Found page: ${url}`);
101
- if (url.includes("onboarding.html") || url.includes(`chrome-extension://${extensionId}/`)) {
102
- extensionPage = page$1;
103
- console.log(`✅ Found Talisman onboarding page: ${url}`);
104
- break;
112
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
113
+ const pages = context.pages();
114
+ for (const p of pages) {
115
+ const url = p.url();
116
+ if (url.includes("onboarding.html") || url.includes(`chrome-extension://${extensionId}/`)) {
117
+ extensionPage = p;
118
+ break;
119
+ }
105
120
  }
121
+ if (extensionPage) break;
122
+ if (attempt < maxAttempts - 1) await new Promise((resolve) => setTimeout(resolve, retryDelay));
106
123
  }
107
- if (!extensionPage) throw new Error(`Talisman onboarding page not found`);
124
+ if (!extensionPage) throw new Error(`Talisman onboarding page not found after ${maxAttempts} attempts`);
108
125
  try {
109
126
  await extensionPage.bringToFront();
110
- console.log("🔄 Reloading onboarding page for fresh state...");
111
- await extensionPage.reload();
112
127
  await extensionPage.waitForLoadState("domcontentloaded");
113
128
  await extensionPage.getByTestId("onboarding-get-started-button").click();
114
129
  await extensionPage.getByRole("textbox", { name: "Enter password" }).fill(password);
@@ -125,7 +140,6 @@ async function importEthPrivateKey(page, { seed, name = "Test Account", password
125
140
  await extensionPage.getByRole("textbox", { name: "Enter your private key" }).fill(seed);
126
141
  await extensionPage.getByRole("button", { name: "Save" }).click();
127
142
  await extensionPage.close();
128
- console.log("✅ Talisman account import completed");
129
143
  } catch (error) {
130
144
  console.error("❌ Error during Talisman account import:", error);
131
145
  throw error;
@@ -135,21 +149,28 @@ async function authorizeTalisman(page, options = {}) {
135
149
  const { accountName = "Test Account" } = options;
136
150
  const context = page.__extensionContext;
137
151
  const extensionId = page.__extensionId;
138
- await new Promise((resolve) => setTimeout(resolve, 1e3));
139
152
  const extensionPopup = await findExtensionPopup(context, extensionId);
140
153
  await extensionPopup.getByRole("button", { name: accountName }).click();
141
154
  await extensionPopup.getByTestId("connection-connect-button").click();
142
155
  try {
143
156
  await (await findExtensionPopup(context, extensionId)).getByRole("button", { name: "Approve" }).click();
157
+ } catch {}
158
+ }
159
+ async function approveTalismanTx(page) {
160
+ const context = page.__extensionContext;
161
+ const extensionId = page.__extensionId;
162
+ const extensionPopup = await findExtensionPopup(context, extensionId);
163
+ try {
164
+ await extensionPopup.getByRole("button", { name: "Yes" }).click();
144
165
  } catch {
145
166
  console.log("No another popup found, skipping");
146
167
  }
168
+ await extensionPopup.getByRole("button", { name: "Approve" }).click();
147
169
  }
148
- async function approveTalismanTx(page) {
170
+ async function rejectTalismanTx(page) {
149
171
  const context = page.__extensionContext;
150
172
  const extensionId = page.__extensionId;
151
- await new Promise((resolve) => setTimeout(resolve, 1e3));
152
- await (await findExtensionPopup(context, extensionId)).getByRole("button", { name: "Approve" }).click();
173
+ await (await findExtensionPopup(context, extensionId)).getByTestId("connection-reject-button").click();
153
174
  }
154
175
 
155
176
  //#endregion
@@ -169,8 +190,7 @@ function createWalletInstance(walletType, extensionId, context) {
169
190
  const baseInstance = {
170
191
  extensionId,
171
192
  importMnemonic: async (options) => {
172
- const page = context.pages()[0] || await context.newPage();
173
- const extPage = createExtendedPage(page, context, extensionId);
193
+ const extPage = createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId);
174
194
  importedAccountName = options.name || "Test Account";
175
195
  switch (walletType) {
176
196
  case "polkadot-js":
@@ -181,8 +201,7 @@ function createWalletInstance(walletType, extensionId, context) {
181
201
  }
182
202
  },
183
203
  authorize: async (options = {}) => {
184
- const page = context.pages()[0] || await context.newPage();
185
- const extPage = createExtendedPage(page, context, extensionId);
204
+ const extPage = createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId);
186
205
  const accountName = options.accountName || importedAccountName;
187
206
  switch (walletType) {
188
207
  case "polkadot-js":
@@ -195,8 +214,7 @@ function createWalletInstance(walletType, extensionId, context) {
195
214
  }
196
215
  },
197
216
  approveTx: async (options = {}) => {
198
- const page = context.pages()[0] || await context.newPage();
199
- const extPage = createExtendedPage(page, context, extensionId);
217
+ const extPage = createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId);
200
218
  switch (walletType) {
201
219
  case "polkadot-js":
202
220
  await approvePolkadotJSTx(extPage, options);
@@ -206,6 +224,18 @@ function createWalletInstance(walletType, extensionId, context) {
206
224
  break;
207
225
  default: throw new Error(`Unsupported wallet type: ${walletType}`);
208
226
  }
227
+ },
228
+ rejectTx: async () => {
229
+ const extPage = createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId);
230
+ switch (walletType) {
231
+ case "polkadot-js":
232
+ await rejectPolkadotJSTx(extPage);
233
+ break;
234
+ case "talisman":
235
+ await rejectTalismanTx(extPage);
236
+ break;
237
+ default: throw new Error(`Unsupported wallet type: ${walletType}`);
238
+ }
209
239
  }
210
240
  };
211
241
  switch (walletType) {
@@ -217,8 +247,7 @@ function createWalletInstance(walletType, extensionId, context) {
217
247
  ...baseInstance,
218
248
  type: "talisman",
219
249
  importEthPrivateKey: async (options) => {
220
- const page = context.pages()[0] || await context.newPage();
221
- const extPage = createExtendedPage(page, context, extensionId);
250
+ const extPage = createExtendedPage(context.pages()[0] || await context.newPage(), context, extensionId);
222
251
  importedAccountName = options.name || "Test Account";
223
252
  await importEthPrivateKey(extPage, {
224
253
  seed: options.privateKey,
@@ -266,7 +295,6 @@ function createWalletTest(options = {}) {
266
295
  const extensionId = allServiceWorkers[i].url().split("/")[2];
267
296
  const walletType = walletConfigs[i].type;
268
297
  extensionIds.set(walletType, extensionId);
269
- console.log(`✅ Loaded ${walletType} extension with ID: ${extensionId}`);
270
298
  }
271
299
  await use(extensionIds);
272
300
  }, { scope: "worker" }],
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@avalix/chroma",
3
3
  "type": "module",
4
- "version": "0.0.8",
4
+ "version": "0.0.10",
5
5
  "description": "End-to-end testing library for Polkadot wallet interactions",
6
6
  "author": "Avalix Labs",
7
7
  "license": "MIT",
@@ -24,12 +24,12 @@
24
24
  "blockchain"
25
25
  ],
26
26
  "exports": {
27
- ".": "./dist/index.js",
27
+ ".": "./dist/index.mjs",
28
28
  "./package.json": "./package.json"
29
29
  },
30
- "main": "./dist/index.js",
31
- "module": "./dist/index.js",
32
- "types": "./dist/index.d.ts",
30
+ "main": "./dist/index.mjs",
31
+ "module": "./dist/index.mjs",
32
+ "types": "./dist/index.d.mts",
33
33
  "bin": {
34
34
  "chroma": "./scripts/cli.js"
35
35
  },
@@ -43,22 +43,23 @@
43
43
  "dev": "tsdown --watch",
44
44
  "lint": "eslint --fix .",
45
45
  "prepublishOnly": "npm run build",
46
- "download-extensions": "rm -rf .chroma && tsx scripts/download-extensions.ts"
46
+ "download-extensions": "rm -rf .chroma && tsx scripts/download-extensions.ts",
47
+ "test": "playwright test --ui"
47
48
  },
48
49
  "peerDependencies": {
49
- "@playwright/test": "^1.55.0"
50
+ "@playwright/test": "^1.57.0"
50
51
  },
51
52
  "dependencies": {
52
- "unzipper": "^0.12.3"
53
+ "adm-zip": "^0.5.16"
53
54
  },
54
55
  "devDependencies": {
55
- "@antfu/eslint-config": "^5.3.0",
56
- "@playwright/test": "^1.55.0",
57
- "@types/node": "^24.3.3",
58
- "@types/unzipper": "^0.10.10",
59
- "eslint": "^9.35.0",
56
+ "@antfu/eslint-config": "^6.5.1",
57
+ "@playwright/test": "^1.57.0",
58
+ "@types/node": "^24.10.2",
59
+ "@types/unzipper": "^0.10.11",
60
+ "eslint": "^9.39.1",
60
61
  "tsdown": "latest",
61
- "tsx": "^4.20.6"
62
+ "tsx": "^4.21.0"
62
63
  },
63
64
  "publishConfig": {
64
65
  "access": "public"
@@ -1,13 +1,38 @@
1
1
  #!/usr/bin/env node
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
2
4
  import process from 'node:process'
5
+ import { fileURLToPath } from 'node:url'
3
6
  import { downloadAndExtractExtension } from '../src/utils/download-extension.js'
4
7
  import { POLKADOT_JS_CONFIG } from '../src/wallets/polkadot-js.js'
5
8
  import { TALISMAN_CONFIG } from '../src/wallets/talisman.js'
6
9
 
10
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
11
+
12
+ async function getVersion(): Promise<string> {
13
+ const packageJsonPath = path.resolve(__dirname, '../package.json')
14
+ const packageJson = JSON.parse(await fs.promises.readFile(packageJsonPath, 'utf-8'))
15
+ return packageJson.version
16
+ }
17
+
18
+ async function clearChromaDir(): Promise<void> {
19
+ const chromaDir = path.resolve(process.cwd(), '.chroma')
20
+
21
+ if (fs.existsSync(chromaDir)) {
22
+ console.log('🗑️ Clearing existing .chroma directory...')
23
+ await fs.promises.rm(chromaDir, { recursive: true, force: true })
24
+ }
25
+ }
26
+
7
27
  async function main() {
8
- console.log('🚀 Downloading Chroma wallet extensions...\n')
28
+ const version = await getVersion()
29
+ console.log(`🎨 Chroma v${version}\n`)
30
+ console.log('🚀 Downloading wallet extensions...\n')
9
31
 
10
32
  try {
33
+ // Clear existing .chroma directory
34
+ await clearChromaDir()
35
+
11
36
  // Download Polkadot-JS extension
12
37
  await downloadAndExtractExtension({
13
38
  downloadUrl: POLKADOT_JS_CONFIG.downloadUrl,
@@ -97,7 +97,6 @@ export function createWalletTest<const T extends readonly WalletConfig[]>(
97
97
  const extensionId = allServiceWorkers[i].url().split('/')[2]
98
98
  const walletType = walletConfigs[i].type
99
99
  extensionIds.set(walletType, extensionId)
100
- console.log(`✅ Loaded ${walletType} extension with ID: ${extensionId}`)
101
100
  }
102
101
 
103
102
  await use(extensionIds)
@@ -25,6 +25,7 @@ export interface BaseWalletInstance {
25
25
  importMnemonic: (options: WalletAccount) => Promise<void>
26
26
  authorize: (options?: { accountName?: string }) => Promise<void>
27
27
  approveTx: (options?: { password?: string }) => Promise<void>
28
+ rejectTx: () => Promise<void>
28
29
  }
29
30
 
30
31
  // Polkadot-JS specific wallet instance
@@ -10,11 +10,13 @@ import {
10
10
  approvePolkadotJSTx,
11
11
  authorizePolkadotJS,
12
12
  importPolkadotJSAccount,
13
+ rejectPolkadotJSTx,
13
14
  } from '../wallets/polkadot-js.js'
14
15
  import {
15
16
  approveTalismanTx,
16
17
  authorizeTalisman,
17
18
  importEthPrivateKey,
19
+ rejectTalismanTx,
18
20
  } from '../wallets/talisman.js'
19
21
 
20
22
  // Helper to create extended page with wallet context
@@ -87,6 +89,21 @@ export function createWalletInstance(
87
89
  throw new Error(`Unsupported wallet type: ${walletType}`)
88
90
  }
89
91
  },
92
+ rejectTx: async () => {
93
+ const page = context.pages()[0] || await context.newPage()
94
+ const extPage = createExtendedPage(page, context, extensionId)
95
+
96
+ switch (walletType) {
97
+ case 'polkadot-js':
98
+ await rejectPolkadotJSTx(extPage)
99
+ break
100
+ case 'talisman':
101
+ await rejectTalismanTx(extPage)
102
+ break
103
+ default:
104
+ throw new Error(`Unsupported wallet type: ${walletType}`)
105
+ }
106
+ },
90
107
  }
91
108
 
92
109
  // Return wallet-specific instance with type discriminator
@@ -2,7 +2,7 @@ import fs, { createWriteStream } from 'node:fs'
2
2
  import path from 'node:path'
3
3
  import process from 'node:process'
4
4
  import { pipeline } from 'node:stream/promises'
5
- import { Extract } from 'unzipper'
5
+ import AdmZip from 'adm-zip'
6
6
 
7
7
  export interface DownloadExtensionOptions {
8
8
  downloadUrl: string
@@ -10,6 +10,11 @@ export interface DownloadExtensionOptions {
10
10
  targetDir?: string
11
11
  }
12
12
 
13
+ function unzipFile(zipPath: string, destDir: string): void {
14
+ const zip = new AdmZip(zipPath)
15
+ zip.extractAllTo(destDir, true)
16
+ }
17
+
13
18
  export async function downloadAndExtractExtension(options: DownloadExtensionOptions): Promise<string> {
14
19
  const { downloadUrl, extensionName, targetDir } = options
15
20
 
@@ -17,6 +22,7 @@ export async function downloadAndExtractExtension(options: DownloadExtensionOpti
17
22
  const extensionsDir = targetDir || path.resolve(process.cwd(), '.chroma')
18
23
  const extensionDir = path.join(extensionsDir, extensionName)
19
24
  const zipPath = path.join(extensionsDir, `${extensionName}.zip`)
25
+ const tempExtractDir = path.join(extensionsDir, `${extensionName}-temp`)
20
26
 
21
27
  // Create extensions directory if it doesn't exist
22
28
  await fs.promises.mkdir(extensionsDir, { recursive: true })
@@ -42,11 +48,29 @@ export async function downloadAndExtractExtension(options: DownloadExtensionOpti
42
48
 
43
49
  console.log('📦 Extracting extension...')
44
50
 
45
- // Standard zip extraction
46
- await pipeline(
47
- fs.createReadStream(zipPath),
48
- Extract({ path: extensionDir }),
49
- )
51
+ // Extract to temp directory first
52
+ await fs.promises.mkdir(tempExtractDir, { recursive: true })
53
+ unzipFile(zipPath, tempExtractDir)
54
+
55
+ // Check if it's a nested zip (contains another .zip file)
56
+ const files = await fs.promises.readdir(tempExtractDir)
57
+ const nestedZip = files.find(f => f.endsWith('.zip'))
58
+
59
+ if (nestedZip) {
60
+ console.log(`📦 Found nested zip: ${nestedZip}, extracting...`)
61
+ const nestedZipPath = path.join(tempExtractDir, nestedZip)
62
+
63
+ // Extract the nested zip to final location
64
+ await fs.promises.mkdir(extensionDir, { recursive: true })
65
+ unzipFile(nestedZipPath, extensionDir)
66
+
67
+ // Clean up temp directory
68
+ await fs.promises.rm(tempExtractDir, { recursive: true, force: true })
69
+ }
70
+ else {
71
+ // No nested zip, just rename temp to final
72
+ await fs.promises.rename(tempExtractDir, extensionDir)
73
+ }
50
74
 
51
75
  // Clean up ZIP file
52
76
  await fs.promises.unlink(zipPath)
@@ -62,6 +86,9 @@ export async function downloadAndExtractExtension(options: DownloadExtensionOpti
62
86
  if (fs.existsSync(extensionDir)) {
63
87
  await fs.promises.rm(extensionDir, { recursive: true, force: true }).catch(() => {})
64
88
  }
89
+ if (fs.existsSync(tempExtractDir)) {
90
+ await fs.promises.rm(tempExtractDir, { recursive: true, force: true }).catch(() => {})
91
+ }
65
92
 
66
93
  throw new Error(`Failed to download/extract ${extensionName}: ${error instanceof Error ? error.message : String(error)}`)
67
94
  }
@@ -5,19 +5,34 @@ import path from 'node:path'
5
5
  import process from 'node:process'
6
6
 
7
7
  // Polkadot-JS specific configuration
8
+ // https://github.com/polkadot-js/extension/releases
9
+ const VERSION = '0.62.6'
8
10
  export const POLKADOT_JS_CONFIG = {
9
- downloadUrl: 'https://github.com/polkadot-js/extension/releases/download/v0.61.7/master-chrome-build.zip',
10
- extensionName: 'polkadot-extension-0.61.7',
11
+ downloadUrl: `https://github.com/polkadot-js/extension/releases/download/v${VERSION}/master-chrome-build.zip`,
12
+ extensionName: `polkadot-extension-${VERSION}`,
11
13
  } as const
12
14
 
13
15
  // Helper function to find extension popup
14
16
  async function findExtensionPopup(context: BrowserContext, extensionId: string): Promise<Page> {
15
- const pages = context.pages()
16
- for (const p of pages) {
17
- if (p.url().includes(`chrome-extension://${extensionId}/`)) {
18
- return p
17
+ // Wait for extension popup to appear with retry logic
18
+ const maxAttempts = 10
19
+ const retryDelay = 500
20
+
21
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
22
+ const pages = context.pages()
23
+ for (const p of pages) {
24
+ if (p.url().includes(`chrome-extension://${extensionId}/`)) {
25
+ await p.waitForLoadState('domcontentloaded')
26
+ return p
27
+ }
28
+ }
29
+
30
+ // If not found, wait a bit before retrying
31
+ if (attempt < maxAttempts - 1) {
32
+ await new Promise(resolve => setTimeout(resolve, retryDelay))
19
33
  }
20
34
  }
35
+
21
36
  throw new Error(`Extension popup not found for ID: ${extensionId}`)
22
37
  }
23
38
 
@@ -31,13 +46,10 @@ export async function getPolkadotJSExtensionPath(): Promise<string> {
31
46
  throw new Error(
32
47
  `Polkadot-JS extension not found at: ${extensionDir}\n\n`
33
48
  + `Please download the extension first by running:\n`
34
- + ` npx @avalix/chroma download-extensions\n\n`
35
- + `Or if you're using this as a dependency:\n`
36
- + ` npm run chroma:download\n`,
49
+ + ` npx @avalix/chroma download-extensions\n`,
37
50
  )
38
51
  }
39
52
 
40
- console.log(`✅ Found Polkadot-JS extension at: ${extensionDir}`)
41
53
  return extensionDir
42
54
  }
43
55
 
@@ -62,6 +74,10 @@ export async function importPolkadotJSAccount(
62
74
  await extensionPage.waitForTimeout(100)
63
75
  }
64
76
 
77
+ if (await extensionPage.getByRole('button', { name: 'I Understand' }).isVisible()) {
78
+ await extensionPage.getByRole('button', { name: 'I Understand' }).click()
79
+ }
80
+
65
81
  // Navigate to import seed page
66
82
  await extensionPage.goto(`${extensionPopupUrl}#/account/import-seed`)
67
83
 
@@ -72,8 +88,6 @@ export async function importPolkadotJSAccount(
72
88
  await extensionPage.locator('input[type="password"]').fill(password)
73
89
  await extensionPage.locator('div').filter({ hasText: /^Repeat password for verification$/ }).getByRole('textbox').fill(password)
74
90
  await extensionPage.getByRole('button', { name: 'Add the account with the supplied seed' }).click()
75
-
76
- console.log(`✅ Created Polkadot-JS wallet account: ${name}`)
77
91
  }
78
92
  finally {
79
93
  await extensionPage.close()
@@ -86,13 +100,22 @@ export async function authorizePolkadotJS(
86
100
  ): Promise<void> {
87
101
  const context = page.__extensionContext
88
102
  const extensionId = page.__extensionId
89
- await new Promise(resolve => setTimeout(resolve, 1000))
90
103
 
91
104
  const extensionPopup = await findExtensionPopup(context, extensionId)
92
- await extensionPopup.getByText('Select all').click()
93
- await extensionPopup.getByRole('button', { name: /Connect \d+ account\(s\)/ }).click()
94
105
 
95
- console.log(' Polkadot-JS wallet connected successfully')
106
+ if (await extensionPopup.getByRole('button', { name: 'I Understand' }).isVisible()) {
107
+ await extensionPopup.getByRole('button', { name: 'I Understand' }).click()
108
+ }
109
+
110
+ // Check if "Select all" checkbox is already checked
111
+ const selectAllCheckbox = extensionPopup.getByText('Select all').locator('..').locator('input[type="checkbox"]')
112
+ const isChecked = await selectAllCheckbox.isChecked().catch(() => false)
113
+
114
+ if (!isChecked) {
115
+ await extensionPopup.getByText('Select all').click()
116
+ }
117
+
118
+ await extensionPopup.getByRole('button', { name: /Connect \d+ account\(s\)/ }).click()
96
119
  }
97
120
 
98
121
  // Polkadot-JS specific transaction approval implementation
@@ -104,11 +127,18 @@ export async function approvePolkadotJSTx(
104
127
  const context = page.__extensionContext
105
128
  const extensionId = page.__extensionId
106
129
 
107
- await new Promise(resolve => setTimeout(resolve, 1000))
108
130
  const extensionPopup = await findExtensionPopup(context, extensionId)
109
-
110
131
  await extensionPopup.getByRole('textbox').fill(password)
111
132
  await extensionPopup.getByRole('button', { name: 'Sign the transaction' }).click()
133
+ }
134
+
135
+ // Polkadot-JS specific transaction rejection implementation
136
+ export async function rejectPolkadotJSTx(
137
+ page: Page & { __extensionContext: BrowserContext, __extensionId: string },
138
+ ): Promise<void> {
139
+ const context = page.__extensionContext
140
+ const extensionId = page.__extensionId
112
141
 
113
- console.log('✅ Polkadot-JS transaction signed successfully')
142
+ const extensionPopup = await findExtensionPopup(context, extensionId)
143
+ await extensionPopup.getByRole('link', { name: 'Cancel' }).click()
114
144
  }