@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.
- package/README.md +20 -2
- package/dist/index.d.mts +53987 -0
- package/dist/{index.js → index.mjs} +79 -51
- package/package.json +15 -14
- package/scripts/download-extensions.ts +26 -1
- package/src/context-playwright/index.ts +0 -1
- package/src/context-playwright/types.ts +1 -0
- package/src/context-playwright/wallet-factory.ts +17 -0
- package/src/utils/download-extension.ts +33 -6
- package/src/wallets/polkadot-js.ts +49 -19
- package/src/wallets/talisman.ts +62 -38
- package/dist/index.d.ts +0 -64
|
@@ -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:
|
|
9
|
-
extensionName:
|
|
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
|
|
13
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
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:
|
|
70
|
-
extensionName:
|
|
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
|
-
|
|
74
|
-
const
|
|
75
|
-
for (
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
|
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
|
-
|
|
109
|
+
const maxAttempts = 20;
|
|
110
|
+
const retryDelay = 500;
|
|
96
111
|
let extensionPage = null;
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
|
170
|
+
async function rejectTalismanTx(page) {
|
|
149
171
|
const context = page.__extensionContext;
|
|
150
172
|
const extensionId = page.__extensionId;
|
|
151
|
-
await
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
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.
|
|
27
|
+
".": "./dist/index.mjs",
|
|
28
28
|
"./package.json": "./package.json"
|
|
29
29
|
},
|
|
30
|
-
"main": "./dist/index.
|
|
31
|
-
"module": "./dist/index.
|
|
32
|
-
"types": "./dist/index.d.
|
|
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.
|
|
50
|
+
"@playwright/test": "^1.57.0"
|
|
50
51
|
},
|
|
51
52
|
"dependencies": {
|
|
52
|
-
"
|
|
53
|
+
"adm-zip": "^0.5.16"
|
|
53
54
|
},
|
|
54
55
|
"devDependencies": {
|
|
55
|
-
"@antfu/eslint-config": "^5.
|
|
56
|
-
"@playwright/test": "^1.
|
|
57
|
-
"@types/node": "^24.
|
|
58
|
-
"@types/unzipper": "^0.10.
|
|
59
|
-
"eslint": "^9.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
46
|
-
await
|
|
47
|
-
|
|
48
|
-
|
|
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:
|
|
10
|
-
extensionName:
|
|
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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
142
|
+
const extensionPopup = await findExtensionPopup(context, extensionId)
|
|
143
|
+
await extensionPopup.getByRole('link', { name: 'Cancel' }).click()
|
|
114
144
|
}
|